Initial commit of [CQ 5286] Tomcat 7.0.12 Version: Subset (PB CQ5094)
diff --git a/bundles/org.apache.tomcat/src/javax/el/ArrayELResolver.java b/bundles/org.apache.tomcat/src/javax/el/ArrayELResolver.java
new file mode 100644
index 0000000..a319d60
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/el/ArrayELResolver.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 javax.el;
+
+import java.beans.FeatureDescriptor;
+import java.lang.reflect.Array;
+import java.util.Arrays;
+import java.util.Iterator;
+
+public class ArrayELResolver extends ELResolver {
+
+    private final boolean readOnly;
+
+    public ArrayELResolver() {
+        this.readOnly = false;
+    }
+
+    public ArrayELResolver(boolean readOnly) {
+        this.readOnly = readOnly;
+    }
+
+    @Override
+    public Object getValue(ELContext context, Object base, Object property)
+            throws NullPointerException, PropertyNotFoundException, ELException {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base != null && base.getClass().isArray()) {
+            context.setPropertyResolved(true);
+            int idx = coerce(property);
+            if (idx < 0 || idx >= Array.getLength(base)) {
+                return null;
+            }
+            return Array.get(base, idx);
+        }
+
+        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 && base.getClass().isArray()) {
+            context.setPropertyResolved(true);
+            int idx = coerce(property);
+            checkBounds(base, idx);
+            return base.getClass().getComponentType();
+        }
+
+        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 && base.getClass().isArray()) {
+            context.setPropertyResolved(true);
+
+            if (this.readOnly) {
+                throw new PropertyNotWritableException(message(context,
+                        "resolverNotWriteable", new Object[] { base.getClass()
+                                .getName() }));
+            }
+
+            int idx = coerce(property);
+            checkBounds(base, idx);
+            if (value != null &&
+                    !base.getClass().getComponentType().isAssignableFrom(
+                            value.getClass())) {
+                throw new ClassCastException(message(context,
+                        "objectNotAssignable",
+                        new Object[] {value.getClass().getName(),
+                        base.getClass().getComponentType().getName()}));
+            }
+            Array.set(base, idx, value);
+        }
+    }
+
+    @Override
+    public boolean isReadOnly(ELContext context, Object base, Object property)
+            throws NullPointerException, PropertyNotFoundException, ELException {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base != null && base.getClass().isArray()) {
+            context.setPropertyResolved(true);
+            int idx = coerce(property);
+            checkBounds(base, idx);
+        }
+
+        return this.readOnly;
+    }
+
+    @Override
+    public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
+        if (base != null && base.getClass().isArray()) {
+            FeatureDescriptor[] descs = new FeatureDescriptor[Array.getLength(base)];
+            for (int i = 0; i < descs.length; i++) {
+                descs[i] = new FeatureDescriptor();
+                descs[i].setDisplayName("["+i+"]");
+                descs[i].setExpert(false);
+                descs[i].setHidden(false);
+                descs[i].setName(""+i);
+                descs[i].setPreferred(true);
+                descs[i].setValue(RESOLVABLE_AT_DESIGN_TIME, Boolean.FALSE);
+                descs[i].setValue(TYPE, Integer.class);
+            }
+            return Arrays.asList(descs).iterator();
+        }
+        return null;
+    }
+
+    @Override
+    public Class<?> getCommonPropertyType(ELContext context, Object base) {
+        if (base != null && base.getClass().isArray()) {
+            return Integer.class;
+        }
+        return null;
+    }
+
+    private static final void checkBounds(Object base, int idx) {
+        if (idx < 0 || idx >= Array.getLength(base)) {
+            throw new PropertyNotFoundException(
+                    new ArrayIndexOutOfBoundsException(idx).getMessage());
+        }
+    }
+
+    private static final int coerce(Object property) {
+        if (property instanceof Number) {
+            return ((Number) property).intValue();
+        }
+        if (property instanceof Character) {
+            return ((Character) property).charValue();
+        }
+        if (property instanceof Boolean) {
+            return (((Boolean) property).booleanValue() ? 1 : 0);
+        }
+        if (property instanceof String) {
+            return Integer.parseInt((String) property);
+        }
+        throw new IllegalArgumentException(property != null ?
+                property.toString() : "null");
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/javax/el/BeanELResolver.java b/bundles/org.apache.tomcat/src/javax/el/BeanELResolver.java
new file mode 100644
index 0000000..7ba9ce0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/el/BeanELResolver.java
@@ -0,0 +1,480 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.el;
+
+import java.beans.BeanInfo;
+import java.beans.FeatureDescriptor;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class BeanELResolver extends ELResolver {
+
+    private static final int CACHE_SIZE;
+    private static final String CACHE_SIZE_PROP =
+        "org.apache.el.BeanELResolver.CACHE_SIZE";
+
+    static {
+        if (System.getSecurityManager() == null) {
+            CACHE_SIZE = Integer.parseInt(
+                    System.getProperty(CACHE_SIZE_PROP, "1000"));
+        } else {
+            CACHE_SIZE = AccessController.doPrivileged(
+                    new PrivilegedAction<Integer>() {
+
+                    @Override
+                    public Integer run() {
+                        return Integer.valueOf(
+                                System.getProperty(CACHE_SIZE_PROP, "1000"));
+                    }
+                }).intValue();
+        }
+    }
+
+    private final boolean readOnly;
+
+    private final ConcurrentCache<String, BeanProperties> cache =
+        new ConcurrentCache<String, BeanProperties>(CACHE_SIZE);
+
+    public BeanELResolver() {
+        this.readOnly = false;
+    }
+
+    public BeanELResolver(boolean readOnly) {
+        this.readOnly = readOnly;
+    }
+
+    @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) {
+            return null;
+        }
+
+        context.setPropertyResolved(true);
+        Method m = this.property(context, base, property).read(context);
+        try {
+            return m.invoke(base, (Object[]) null);
+        } catch (IllegalAccessException e) {
+            throw new ELException(e);
+        } catch (InvocationTargetException e) {
+            throw new ELException(message(context, "propertyReadError",
+                    new Object[] { base.getClass().getName(),
+                            property.toString() }), e.getCause());
+        } catch (Exception e) {
+            throw new ELException(e);
+        }
+    }
+
+    @Override
+    public Class<?> getType(ELContext context, Object base, Object property)
+            throws NullPointerException, PropertyNotFoundException, ELException {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+        if (base == null || property == null) {
+            return null;
+        }
+
+        context.setPropertyResolved(true);
+        return this.property(context, base, property).getPropertyType();
+    }
+
+    @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) {
+            return;
+        }
+
+        context.setPropertyResolved(true);
+
+        if (this.readOnly) {
+            throw new PropertyNotWritableException(message(context,
+                    "resolverNotWriteable", new Object[] { base.getClass()
+                            .getName() }));
+        }
+
+        Method m = this.property(context, base, property).write(context);
+        try {
+            m.invoke(base, value);
+        } catch (IllegalAccessException e) {
+            throw new ELException(e);
+        } catch (InvocationTargetException e) {
+            throw new ELException(message(context, "propertyWriteError",
+                    new Object[] { base.getClass().getName(),
+                            property.toString() }), e.getCause());
+        } catch (Exception e) {
+            throw new ELException(e);
+        }
+    }
+
+    @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) {
+            return false;
+        }
+
+        context.setPropertyResolved(true);
+        return this.readOnly
+                || this.property(context, base, property).isReadOnly();
+    }
+
+    @Override
+    public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base == null) {
+            return null;
+        }
+
+        try {
+            BeanInfo info = Introspector.getBeanInfo(base.getClass());
+            PropertyDescriptor[] pds = info.getPropertyDescriptors();
+            for (int i = 0; i < pds.length; i++) {
+                pds[i].setValue(RESOLVABLE_AT_DESIGN_TIME, Boolean.TRUE);
+                pds[i].setValue(TYPE, pds[i].getPropertyType());
+            }
+            return Arrays.asList((FeatureDescriptor[]) pds).iterator();
+        } catch (IntrospectionException e) {
+            //
+        }
+
+        return null;
+    }
+
+    @Override
+    public Class<?> getCommonPropertyType(ELContext context, Object base) {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base != null) {
+            return Object.class;
+        }
+
+        return null;
+    }
+
+    protected static final class BeanProperties {
+        private final Map<String, BeanProperty> properties;
+
+        private final Class<?> type;
+
+        public BeanProperties(Class<?> type) throws ELException {
+            this.type = type;
+            this.properties = new HashMap<String, BeanProperty>();
+            try {
+                BeanInfo info = Introspector.getBeanInfo(this.type);
+                PropertyDescriptor[] pds = info.getPropertyDescriptors();
+                for (int i = 0; i < pds.length; i++) {
+                    this.properties.put(pds[i].getName(), new BeanProperty(
+                            type, pds[i]));
+                }
+            } catch (IntrospectionException ie) {
+                throw new ELException(ie);
+            }
+        }
+
+        private BeanProperty get(ELContext ctx, String name) {
+            BeanProperty property = this.properties.get(name);
+            if (property == null) {
+                throw new PropertyNotFoundException(message(ctx,
+                        "propertyNotFound",
+                        new Object[] { type.getName(), name }));
+            }
+            return property;
+        }
+
+        public BeanProperty getBeanProperty(String name) {
+            return get(null, name);
+        }
+        
+        private Class<?> getType() {
+            return type;
+        }
+    }
+
+    protected static final class BeanProperty {
+        private final Class<?> type;
+
+        private final Class<?> owner;
+
+        private final PropertyDescriptor descriptor;
+
+        private Method read;
+
+        private Method write;
+
+        public BeanProperty(Class<?> owner, PropertyDescriptor descriptor) {
+            this.owner = owner;
+            this.descriptor = descriptor;
+            this.type = descriptor.getPropertyType();
+        }
+
+        // Can't use Class<?> because API needs to match specification
+        @SuppressWarnings("rawtypes")
+        public Class getPropertyType() {
+            return this.type;
+        }
+
+        public boolean isReadOnly() {
+            return this.write == null
+                && (null == (this.write = getMethod(this.owner, descriptor.getWriteMethod())));
+        }
+
+        public Method getWriteMethod() {
+            return write(null);
+        }
+
+        public Method getReadMethod() {
+            return this.read(null);
+        }
+
+        private Method write(ELContext ctx) {
+            if (this.write == null) {
+                this.write = getMethod(this.owner, descriptor.getWriteMethod());
+                if (this.write == null) {
+                    throw new PropertyNotFoundException(message(ctx,
+                            "propertyNotWritable", new Object[] {
+                                    type.getName(), descriptor.getName() }));
+                }
+            }
+            return this.write;
+        }
+
+        private Method read(ELContext ctx) {
+            if (this.read == null) {
+                this.read = getMethod(this.owner, descriptor.getReadMethod());
+                if (this.read == null) {
+                    throw new PropertyNotFoundException(message(ctx,
+                            "propertyNotReadable", new Object[] {
+                                    type.getName(), descriptor.getName() }));
+                }
+            }
+            return this.read;
+        }
+    }
+
+    private final BeanProperty property(ELContext ctx, Object base,
+            Object property) {
+        Class<?> type = base.getClass();
+        String prop = property.toString();
+
+        BeanProperties props = this.cache.get(type.getName());
+        if (props == null || type != props.getType()) {
+            props = new BeanProperties(type);
+            this.cache.put(type.getName(), props);
+        }
+
+        return props.get(ctx, prop);
+    }
+
+    private static final Method getMethod(Class<?> type, Method m) {
+        if (m == null || Modifier.isPublic(type.getModifiers())) {
+            return m;
+        }
+        Class<?>[] inf = type.getInterfaces();
+        Method mp = null;
+        for (int i = 0; i < inf.length; i++) {
+            try {
+                mp = inf[i].getMethod(m.getName(), m.getParameterTypes());
+                mp = getMethod(mp.getDeclaringClass(), mp);
+                if (mp != null) {
+                    return mp;
+                }
+            } catch (NoSuchMethodException e) {
+                // Ignore
+            }
+        }
+        Class<?> sup = type.getSuperclass();
+        if (sup != null) {
+            try {
+                mp = sup.getMethod(m.getName(), m.getParameterTypes());
+                mp = getMethod(mp.getDeclaringClass(), mp);
+                if (mp != null) {
+                    return mp;
+                }
+            } catch (NoSuchMethodException e) {
+                // Ignore
+            }
+        }
+        return null;
+    }
+    
+    private static final class ConcurrentCache<K,V> {
+
+        private final int size;
+        private final Map<K,V> eden;
+        private final Map<K,V> longterm;
+        
+        public ConcurrentCache(int size) {
+            this.size = size;
+            this.eden = new ConcurrentHashMap<K,V>(size);
+            this.longterm = new WeakHashMap<K,V>(size);
+        }
+        
+        public V get(K key) {
+            V value = this.eden.get(key);
+            if (value == null) {
+                synchronized (longterm) {
+                    value = this.longterm.get(key);
+                }
+                if (value != null) {
+                    this.eden.put(key, value);
+                }
+            }
+            return value;
+        }
+        
+        public void put(K key, V value) {
+            if (this.eden.size() >= this.size) {
+                synchronized (longterm) {
+                    this.longterm.putAll(this.eden);
+                }
+                this.eden.clear();
+            }
+            this.eden.put(key, value);
+        }
+
+    }
+    
+    /**
+     * @since EL 2.2
+     */
+    @Override
+    public Object invoke(ELContext context, Object base, Object method,
+            Class<?>[] paramTypes, Object[] params) {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+        if (base == null || method == null) {
+            return null;
+        }
+
+        ExpressionFactory factory = ExpressionFactory.newInstance();
+        
+        String methodName = (String) factory.coerceToType(method, String.class);
+        
+        // Find the matching method
+        Method matchingMethod = null;
+        Class<?> clazz = base.getClass();
+        if (paramTypes != null) {
+            try {
+                matchingMethod = clazz.getMethod(methodName, paramTypes);
+            } catch (NoSuchMethodException e) {
+                throw new MethodNotFoundException(e);
+            }
+        } else {
+            int paramCount = 0;
+            if (params != null) {
+                paramCount = params.length;
+            }
+            Method[] methods = clazz.getMethods();
+            for (Method m : methods) {
+                if (methodName.equals(m.getName()) && 
+                        m.getParameterTypes().length == paramCount) {
+                    // Same number of parameters - use the first match
+                    matchingMethod = m;
+                    break;
+                }
+                if (m.isVarArgs()) {
+                    matchingMethod = m;
+                }
+            }
+            if (matchingMethod == null) {
+                throw new MethodNotFoundException(
+                        "Unable to find method [" + methodName + "] with ["
+                        + paramCount + "] parameters");
+            }
+        }
+
+        Class<?>[] parameterTypes = matchingMethod.getParameterTypes();
+        Object[] parameters = null;
+        if (parameterTypes.length > 0) {
+            parameters = new Object[parameterTypes.length];
+            @SuppressWarnings("null")  // params.length >= parameterTypes.length
+            int paramCount = params.length;
+            if (matchingMethod.isVarArgs()) {
+                int varArgIndex = parameterTypes.length - 1;
+                // First argCount-1 parameters are standard
+                for (int i = 0; (i < varArgIndex - 1); i++) {
+                    parameters[i] = factory.coerceToType(params[i],
+                            parameterTypes[i]);
+                }
+                // Last parameter is the varags
+                Class<?> varArgClass =
+                    parameterTypes[varArgIndex].getComponentType();
+                for (int i = (varArgIndex); i < paramCount; i++) {
+                    Object varargs = Array.newInstance(
+                            parameterTypes[paramCount],
+                            (paramCount - varArgIndex));
+                    Array.set(varargs, i,
+                            factory.coerceToType(params[i], varArgClass));
+                    parameters[varArgIndex] = varargs;
+                }
+            } else {
+                parameters = new Object[parameterTypes.length];
+                for (int i = 0; i < parameterTypes.length; i++) {
+                    parameters[i] = factory.coerceToType(params[i],
+                            parameterTypes[i]);
+                }
+            }
+        }
+        Object result = null;
+        try {
+            result = matchingMethod.invoke(base, parameters);
+        } catch (IllegalArgumentException e) {
+            throw new ELException(e);
+        } catch (IllegalAccessException e) {
+            throw new ELException(e);
+        } catch (InvocationTargetException e) {
+            throw new ELException(e.getCause());
+        }
+        
+        context.setPropertyResolved(true);
+        return result;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/javax/el/CompositeELResolver.java b/bundles/org.apache.tomcat/src/javax/el/CompositeELResolver.java
new file mode 100644
index 0000000..7d9ed11
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/el/CompositeELResolver.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 javax.el;
+
+import java.beans.FeatureDescriptor;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+public class CompositeELResolver extends ELResolver {
+
+    private static final Class<?> SCOPED_ATTRIBUTE_EL_RESOLVER;
+    static {
+        Class<?> clazz = null;
+        try {
+            clazz =
+                Class.forName("javax.servlet.jsp.el.ScopedAttributeELResolver");
+        } catch (ClassNotFoundException e) {
+            // Ignore. This is expected if using the EL stand-alone 
+        }
+        SCOPED_ATTRIBUTE_EL_RESOLVER = clazz;
+    }
+
+    private int size;
+
+    private ELResolver[] resolvers;
+
+    public CompositeELResolver() {
+        this.size = 0;
+        this.resolvers = new ELResolver[8];
+    }
+
+    public void add(ELResolver elResolver) {
+        if (elResolver == null) {
+            throw new NullPointerException();
+        }
+
+        if (this.size >= this.resolvers.length) {
+            ELResolver[] nr = new ELResolver[this.size * 2];
+            System.arraycopy(this.resolvers, 0, nr, 0, this.size);
+            this.resolvers = nr;
+        }
+        this.resolvers[this.size++] = elResolver;
+    }
+
+    @Override
+    public Object getValue(ELContext context, Object base, Object property)
+            throws NullPointerException, PropertyNotFoundException, ELException {
+        context.setPropertyResolved(false);
+        int sz = this.size;
+        Object result = null;
+        for (int i = 0; i < sz; i++) {
+            result = this.resolvers[i].getValue(context, base, property);
+            if (context.isPropertyResolved()) {
+                return result;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void setValue(ELContext context, Object base, Object property,
+            Object value) throws NullPointerException,
+            PropertyNotFoundException, PropertyNotWritableException,
+            ELException {
+        context.setPropertyResolved(false);
+        int sz = this.size;
+        for (int i = 0; i < sz; i++) {
+            this.resolvers[i].setValue(context, base, property, value);
+            if (context.isPropertyResolved()) {
+                return;
+            }
+        }
+    }
+
+    @Override
+    public boolean isReadOnly(ELContext context, Object base, Object property)
+            throws NullPointerException, PropertyNotFoundException, ELException {
+        context.setPropertyResolved(false);
+        int sz = this.size;
+        boolean readOnly = false;
+        for (int i = 0; i < sz; i++) {
+            readOnly = this.resolvers[i].isReadOnly(context, base, property);
+            if (context.isPropertyResolved()) {
+                return readOnly;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
+        return new FeatureIterator(context, base, this.resolvers, this.size);
+    }
+
+    @Override
+    public Class<?> getCommonPropertyType(ELContext context, Object base) {
+        int sz = this.size;
+        Class<?> commonType = null, type = null;
+        for (int i = 0; i < sz; i++) {
+            type = this.resolvers[i].getCommonPropertyType(context, base);
+            if (type != null
+                    && (commonType == null || commonType.isAssignableFrom(type))) {
+                commonType = type;
+            }
+        }
+        return commonType;
+    }
+
+    @Override
+    public Class<?> getType(ELContext context, Object base, Object property)
+            throws NullPointerException, PropertyNotFoundException, ELException {
+        context.setPropertyResolved(false);
+        int sz = this.size;
+        Class<?> type;
+        for (int i = 0; i < sz; i++) {
+            type = this.resolvers[i].getType(context, base, property);
+            if (context.isPropertyResolved()) {
+                if (SCOPED_ATTRIBUTE_EL_RESOLVER != null &&
+                        SCOPED_ATTRIBUTE_EL_RESOLVER.isAssignableFrom(
+                                resolvers[i].getClass())) {
+                    // Special case since
+                    // javax.servlet.jsp.el.ScopedAttributeELResolver will
+                    // always return Object.class for type
+                    Object value =
+                        resolvers[i].getValue(context, base, property);
+                    if (value != null) {
+                        return value.getClass();
+                    }
+                }
+                return type;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @since EL 2.2
+     */
+    @Override
+    public Object invoke(ELContext context, Object base, Object method,
+            Class<?>[] paramTypes, Object[] params) {
+        context.setPropertyResolved(false);
+        int sz = this.size;
+        Object obj;
+        for (int i = 0; i < sz; i++) {
+            obj = this.resolvers[i].invoke(context, base, method, paramTypes,
+                    params);
+            if (context.isPropertyResolved()) {
+                return obj;
+            }
+        }
+        return null;
+    }
+
+    private static final class FeatureIterator implements Iterator<FeatureDescriptor> {
+
+        private final ELContext context;
+
+        private final Object base;
+
+        private final ELResolver[] resolvers;
+
+        private final int size;
+
+        private Iterator<FeatureDescriptor> itr;
+
+        private int idx;
+
+        private FeatureDescriptor next;
+
+        public FeatureIterator(ELContext context, Object base,
+                ELResolver[] resolvers, int size) {
+            this.context = context;
+            this.base = base;
+            this.resolvers = resolvers;
+            this.size = size;
+
+            this.idx = 0;
+            this.guaranteeIterator();
+        }
+        
+        private void guaranteeIterator() {
+            while (this.itr == null && this.idx < this.size) {
+                this.itr = this.resolvers[this.idx].getFeatureDescriptors(
+                        this.context, this.base);
+                this.idx++;
+            }
+        }
+
+        @Override
+        public boolean hasNext() {          
+            if (this.next != null)
+                return true;
+            if (this.itr != null){
+                while (this.next == null && itr.hasNext()) {
+                    this.next = itr.next();
+                }
+            } else {
+                return false;
+            }
+            if (this.next == null) {
+                this.itr = null;
+                this.guaranteeIterator();
+            }
+            return hasNext();
+        }
+
+        @Override
+        public FeatureDescriptor next() {
+            if (!hasNext())
+                throw new NoSuchElementException();
+            FeatureDescriptor result = this.next;
+            this.next = null;
+            return result;
+
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/javax/el/ELContext.java b/bundles/org.apache.tomcat/src/javax/el/ELContext.java
new file mode 100644
index 0000000..1cdf5c7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/el/ELContext.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.el;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ *
+ */
+public abstract class ELContext {
+
+    private Locale locale;
+    
+    private Map<Class<?>, Object> map;
+    
+    private boolean resolved;
+    
+    /**
+     * 
+     */
+    public ELContext() {
+        this.resolved = false;
+    }
+    
+    // Can't use Class<?> because API needs to match specification
+    public Object getContext(@SuppressWarnings("rawtypes") Class key) {
+        if (this.map == null) {
+            return null;
+        }
+        return this.map.get(key);
+    }
+    
+    // Can't use Class<?> because API needs to match specification
+    public void putContext(@SuppressWarnings("rawtypes") Class key,
+            Object contextObject) throws NullPointerException {
+        if (key == null || contextObject == null) {
+            throw new NullPointerException();
+        }
+        
+        if (this.map == null) {
+            this.map = new HashMap<Class<?>, Object>();
+        }
+        
+        this.map.put(key, contextObject);
+    }
+    
+    public void setPropertyResolved(boolean resolved) {
+        this.resolved = resolved;
+    }
+    
+    public boolean isPropertyResolved() {
+        return this.resolved;
+    }
+    
+    public abstract ELResolver getELResolver();
+
+    public abstract FunctionMapper getFunctionMapper();
+    
+    public abstract VariableMapper getVariableMapper();
+    
+    public Locale getLocale() {
+        return this.locale;
+    }
+    
+    public void setLocale(Locale locale) {
+        this.locale = locale;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/el/ELContextEvent.java b/bundles/org.apache.tomcat/src/javax/el/ELContextEvent.java
new file mode 100644
index 0000000..a667525
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/el/ELContextEvent.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.el;
+
+import java.util.EventObject;
+
+/**
+ *
+ */
+public class ELContextEvent extends EventObject {
+
+    private static final long serialVersionUID = 1255131906285426769L;
+
+    /**
+     * @param source
+     */
+    public ELContextEvent(ELContext source) {
+        super(source);
+    }
+    
+    public ELContext getELContext() {
+        return (ELContext) this.getSource();
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/javax/el/ELContextListener.java b/bundles/org.apache.tomcat/src/javax/el/ELContextListener.java
new file mode 100644
index 0000000..a21038b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/el/ELContextListener.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.el;
+
+/**
+ * @author Jacob Hookom [jacob/hookom.net]
+ *
+ */
+public interface ELContextListener extends java.util.EventListener {
+    
+    public void contextCreated(ELContextEvent event);
+
+}
diff --git a/bundles/org.apache.tomcat/src/javax/el/ELException.java b/bundles/org.apache.tomcat/src/javax/el/ELException.java
new file mode 100644
index 0000000..22efff7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/el/ELException.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.el;
+
+/**
+ * Represents any of the exception conditions that can arise during expression
+ * evaluation.
+ * 
+ * @since 2.1
+ */
+public class ELException extends RuntimeException {
+
+    private static final long serialVersionUID = -6228042809457459161L;
+
+    /**
+     * Creates an ELException with no detail message
+     */
+    public ELException() {
+        super();
+    }
+
+    /**
+     * Creates an ELException with the provided detail message.
+     * 
+     * @param message
+     *            the detail message
+     */
+    public ELException(String message) {
+        super(message);
+    }
+
+    /**
+     * Creates an ELException with the given detail message and root cause.
+     * 
+     * @param message
+     *            the detail message
+     * @param cause
+     *            the originating cause of this exception
+     */
+    public ELException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Creates an ELException with the given cause
+     * 
+     * @param cause
+     *            the originating cause of this exception
+     */
+    public ELException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/el/ELResolver.java b/bundles/org.apache.tomcat/src/javax/el/ELResolver.java
new file mode 100644
index 0000000..4c90503
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/el/ELResolver.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.el;
+
+import java.text.MessageFormat;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+/**
+ * @author Jacob Hookom [jacob/hookom.net]
+ *
+ */
+public abstract class ELResolver {
+    
+    static String message(ELContext context, String name, Object[] props) {
+        Locale locale = context.getLocale();
+        if (locale == null) {
+            locale = Locale.getDefault();
+            if (locale == null) {
+                return "";
+            }
+        }
+        ResourceBundle bundle = ResourceBundle.getBundle(
+                "javax.el.LocalStrings", locale);
+        try {
+            String template = bundle.getString(name);
+            if (props != null) {
+                template = MessageFormat.format(template, props);
+            }
+            return template;
+        } catch (MissingResourceException e) {
+            return "Missing Resource: '" + name + "' for Locale "
+                    + locale.getDisplayName();
+        }
+    }
+
+    public static final String RESOLVABLE_AT_DESIGN_TIME = "resolvableAtDesignTime";
+    
+    public static final String TYPE = "type";
+    
+    public abstract Object getValue(ELContext context, Object base, Object property) throws NullPointerException, PropertyNotFoundException, ELException;
+    
+    public abstract Class<?> getType(ELContext context, Object base, Object property) throws NullPointerException, PropertyNotFoundException, ELException;
+    
+    public abstract void setValue(ELContext context, Object base, Object property, Object value) throws NullPointerException, PropertyNotFoundException, PropertyNotWritableException, ELException;
+
+    public abstract boolean isReadOnly(ELContext context, Object base, Object property) throws NullPointerException, PropertyNotFoundException, ELException;
+    
+    public abstract Iterator<java.beans.FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base);
+    
+    public abstract Class<?> getCommonPropertyType(ELContext context, Object base);
+    
+    /**
+     * @since EL 2.2
+     */
+    public Object invoke(@SuppressWarnings("unused") ELContext context,
+            @SuppressWarnings("unused") Object base,
+            @SuppressWarnings("unused") Object method,
+            @SuppressWarnings("unused") Class<?>[] paramTypes,
+            @SuppressWarnings("unused") Object[] params) {
+        return null;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/el/Expression.java b/bundles/org.apache.tomcat/src/javax/el/Expression.java
new file mode 100644
index 0000000..40fbd57
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/el/Expression.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 javax.el;
+
+import java.io.Serializable;
+
+/**
+ *
+ */
+public abstract class Expression implements Serializable {
+
+    private static final long serialVersionUID = -6663767980471823812L;
+
+    @Override
+    public abstract boolean equals(Object obj);
+
+    @Override
+    public abstract int hashCode();
+    
+    public abstract String getExpressionString();
+    
+    public abstract boolean isLiteralText();
+
+}
diff --git a/bundles/org.apache.tomcat/src/javax/el/ExpressionFactory.java b/bundles/org.apache.tomcat/src/javax/el/ExpressionFactory.java
new file mode 100644
index 0000000..7f4a240
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/el/ExpressionFactory.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.el;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Properties;
+
+/**
+ * 
+ * @since 2.1
+ */
+public abstract class ExpressionFactory {
+    
+    private static final boolean IS_SECURITY_ENABLED =
+        (System.getSecurityManager() != null);
+
+    private static final String SERVICE_RESOURCE_NAME =
+        "META-INF/services/javax.el.ExpressionFactory";
+
+    private static final String PROPERTY_NAME = "javax.el.ExpressionFactory";
+
+    private static final String SEP;
+    private static final String PROPERTY_FILE;
+
+    static {
+        if (IS_SECURITY_ENABLED) {
+            SEP = AccessController.doPrivileged(
+                    new PrivilegedAction<String>(){
+                        @Override
+                        public String run() {
+                            return System.getProperty("file.separator");
+                        }
+
+                    }
+            );
+            PROPERTY_FILE = AccessController.doPrivileged(
+                    new PrivilegedAction<String>(){
+                        @Override
+                        public String run() {
+                            return System.getProperty("java.home") + SEP +
+                                    "lib" + SEP + "el.properties";
+                        }
+
+                    }
+            );
+        } else {
+            SEP = System.getProperty("file.separator");
+            PROPERTY_FILE = System.getProperty("java.home") + SEP + "lib" +
+                    SEP + "el.properties";
+        }
+    }
+
+    public abstract Object coerceToType(Object obj, Class<?> expectedType)
+            throws ELException;
+
+    public abstract ValueExpression createValueExpression(ELContext context,
+            String expression, Class<?> expectedType)
+            throws NullPointerException, ELException;
+
+    public abstract ValueExpression createValueExpression(Object instance,
+            Class<?> expectedType);
+
+    public abstract MethodExpression createMethodExpression(ELContext context,
+            String expression, Class<?> expectedReturnType,
+            Class<?>[] expectedParamTypes) throws ELException,
+            NullPointerException;
+
+    /**
+     * Create a new {@link ExpressionFactory}. The class to use is determined by
+     * the following search order:
+     * <ol>
+     * <li>services API (META-INF/services/javax.el.ExpressionFactory)</li>
+     * <li>$JRE_HOME/lib/el.properties - key javax.el.ExpressionFactory</li>
+     * <li>javax.el.ExpressionFactory</li>
+     * <li>Platform default implementation -
+     *     org.apache.el.ExpressionFactoryImpl</li>
+     * </ol>
+     * @return the new ExpressionFactory
+     */
+    public static ExpressionFactory newInstance() {
+        return newInstance(null);
+    }
+
+    /**
+     * Create a new {@link ExpressionFactory} passing in the provided
+     * {@link Properties}. Search order is the same as {@link #newInstance()}.
+     * 
+     * @param properties the properties to be passed to the new instance (may be null)
+     * @return the new ExpressionFactory
+     */
+    public static ExpressionFactory newInstance(Properties properties) {
+        String className = null;
+        ExpressionFactory result = null;
+        
+        ClassLoader tccl = Thread.currentThread().getContextClassLoader();
+
+        // First services API
+        className = getClassNameServices(tccl);
+        if (className == null) {
+            if (IS_SECURITY_ENABLED) {
+                className = AccessController.doPrivileged(
+                        new PrivilegedAction<String>() {
+                            @Override
+                            public String run() {
+                                return getClassNameJreDir();
+                            }
+                        }
+                );
+            } else {
+                // Second el.properties file
+                className = getClassNameJreDir();
+            }
+        }
+        if (className == null) {
+            if (IS_SECURITY_ENABLED) {
+                className = AccessController.doPrivileged(
+                        new PrivilegedAction<String>() {
+                            @Override
+                            public String run() {
+                                return getClassNameSysProp();
+                            }
+                        }
+                );
+            } else {
+                // Third system property 
+                className = getClassNameSysProp();
+            }
+        }
+        if (className == null) {
+            // Fourth - default
+            className = "org.apache.el.ExpressionFactoryImpl";
+        }
+        
+        try {
+            Class<?> clazz = null;
+            if (tccl == null) {
+                clazz = Class.forName(className);
+            } else {
+                clazz = tccl.loadClass(className);
+            }
+            Constructor<?> constructor = null;
+            // Do we need to look for a constructor that will take properties?
+            if (properties != null) {
+                try {
+                    constructor = clazz.getConstructor(Properties.class);
+                } catch (SecurityException se) {
+                    throw new ELException(se);
+                } catch (NoSuchMethodException nsme) {
+                    // This can be ignored
+                    // This is OK for this constructor not to exist
+                }
+            }
+            if (constructor == null) {
+                result = (ExpressionFactory) clazz.newInstance();
+            } else {
+                result =
+                    (ExpressionFactory) constructor.newInstance(properties);
+            }
+            
+        } catch (ClassNotFoundException e) {
+            throw new ELException(
+                    "Unable to find ExpressionFactory of type: " + className,
+                    e);
+        } catch (InstantiationException e) {
+            throw new ELException(
+                    "Unable to create ExpressionFactory of type: " + className,
+                    e);
+        } catch (IllegalAccessException e) {
+            throw new ELException(
+                    "Unable to create ExpressionFactory of type: " + className,
+                    e);
+        } catch (IllegalArgumentException e) {
+            throw new ELException(
+                    "Unable to create ExpressionFactory of type: " + className,
+                    e);
+        } catch (InvocationTargetException e) {
+            throw new ELException(
+                    "Unable to create ExpressionFactory of type: " + className,
+                    e);
+        }
+        
+        return result;
+    }
+    
+    private static String getClassNameServices(ClassLoader tccl) {
+        InputStream is = null;
+        
+        if (tccl == null) {
+            is = ClassLoader.getSystemResourceAsStream(SERVICE_RESOURCE_NAME);
+        } else {
+            is = tccl.getResourceAsStream(SERVICE_RESOURCE_NAME);
+        }
+
+        if (is != null) {
+            String line = null;
+            BufferedReader br = null;
+            try {
+                br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
+                line = br.readLine();
+                if (line != null && line.trim().length() > 0) {
+                    return line.trim();
+                }
+            } catch (UnsupportedEncodingException e) {
+                // Should never happen with UTF-8
+                // If it does - ignore & return null
+            } catch (IOException e) {
+                throw new ELException("Failed to read " + SERVICE_RESOURCE_NAME,
+                        e);
+            } finally {
+                try {
+                    if (br != null) {
+                        br.close();
+                    }
+                } catch (IOException ioe) {/*Ignore*/}
+                try {
+                    is.close();
+                } catch (IOException ioe) {/*Ignore*/}
+            }
+        }
+        
+        return null;
+    }
+    
+    private static String getClassNameJreDir() {
+        File file = new File(PROPERTY_FILE);
+        if (file.canRead()) {
+            InputStream is = null;
+            try {
+                is = new FileInputStream(file);
+                Properties props = new Properties();
+                props.load(is);
+                String value = props.getProperty(PROPERTY_NAME);
+                if (value != null && value.trim().length() > 0) {
+                    return value.trim();
+                }
+            } catch (FileNotFoundException e) {
+                // Should not happen - ignore it if it does
+            } catch (IOException e) {
+                throw new ELException("Failed to read " + PROPERTY_FILE, e);
+            } finally {
+                if (is != null) {
+                    try {
+                        is.close();
+                    } catch (IOException e) {
+                        // Ignore
+                    }
+                }
+            }
+        }
+        return null;
+    }
+    
+    private static final String getClassNameSysProp() {
+        String value = System.getProperty(PROPERTY_NAME);
+        if (value != null && value.trim().length() > 0) {
+            return value.trim();
+        }
+        return null;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/javax/el/FunctionMapper.java b/bundles/org.apache.tomcat/src/javax/el/FunctionMapper.java
new file mode 100644
index 0000000..666a76c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/el/FunctionMapper.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.el;
+
+import java.lang.reflect.Method;
+
+/**
+ *
+ */
+public abstract class FunctionMapper {
+
+    public abstract Method resolveFunction(String prefix, String localName);
+    
+}
diff --git a/bundles/org.apache.tomcat/src/javax/el/ListELResolver.java b/bundles/org.apache.tomcat/src/javax/el/ListELResolver.java
new file mode 100644
index 0000000..989fcb5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/el/ListELResolver.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 javax.el;
+
+import java.beans.FeatureDescriptor;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+public class ListELResolver extends ELResolver {
+
+    private final boolean readOnly;
+
+    private static final Class<?> UNMODIFIABLE =
+        Collections.unmodifiableList(new ArrayList<Object>()).getClass();
+
+    public ListELResolver() {
+        this.readOnly = false;
+    }
+
+    public ListELResolver(boolean readOnly) {
+        this.readOnly = readOnly;
+    }
+
+    @Override
+    public Object getValue(ELContext context, Object base, Object property)
+            throws NullPointerException, PropertyNotFoundException, ELException {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base instanceof List<?>) {
+            context.setPropertyResolved(true);
+            List<?> list = (List<?>) base;
+            int idx = coerce(property);
+            if (idx < 0 || idx >= list.size()) {
+                return null;
+            }
+            return list.get(idx);
+        }
+
+        return null;
+    }
+
+    @Override
+    public Class<?> getType(ELContext context, Object base, Object property)
+            throws NullPointerException, PropertyNotFoundException, ELException {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base instanceof List<?>) {
+            context.setPropertyResolved(true);
+            List<?> list = (List<?>) base;
+            int idx = coerce(property);
+            if (idx < 0 || idx >= list.size()) {
+                throw new PropertyNotFoundException(
+                        new ArrayIndexOutOfBoundsException(idx).getMessage());
+            }
+            Object obj = list.get(idx);
+            return (obj != null) ? obj.getClass() : null;
+        }
+
+        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 instanceof List<?>) {
+            context.setPropertyResolved(true);
+            @SuppressWarnings("unchecked") // Must be OK to cast to Object
+            List<Object> list = (List<Object>) base;
+
+            if (this.readOnly) {
+                throw new PropertyNotWritableException(message(context,
+                        "resolverNotWriteable", new Object[] { base.getClass()
+                                .getName() }));
+            }
+
+            int idx = coerce(property);
+            try {
+                list.set(idx, value);
+            } catch (UnsupportedOperationException e) {
+                throw new PropertyNotWritableException(e);
+            } catch (IndexOutOfBoundsException e) {
+                throw new PropertyNotFoundException(e);
+            }
+        }
+    }
+
+    @Override
+    public boolean isReadOnly(ELContext context, Object base, Object property)
+            throws NullPointerException, PropertyNotFoundException, ELException {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base instanceof List<?>) {
+            context.setPropertyResolved(true);
+            List<?> list = (List<?>) base;
+            int idx = coerce(property);
+            if (idx < 0 || idx >= list.size()) {
+                throw new PropertyNotFoundException(
+                        new ArrayIndexOutOfBoundsException(idx).getMessage());
+            }
+            return this.readOnly || UNMODIFIABLE.equals(list.getClass());
+        }
+
+        return this.readOnly;
+    }
+
+    @Override
+    public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
+        if (base instanceof List<?>) {
+            FeatureDescriptor[] descs = new FeatureDescriptor[((List<?>) base).size()];
+            for (int i = 0; i < descs.length; i++) {
+                descs[i] = new FeatureDescriptor();
+                descs[i].setDisplayName("["+i+"]");
+                descs[i].setExpert(false);
+                descs[i].setHidden(false);
+                descs[i].setName(""+i);
+                descs[i].setPreferred(true);
+                descs[i].setValue(RESOLVABLE_AT_DESIGN_TIME, Boolean.FALSE);
+                descs[i].setValue(TYPE, Integer.class);
+            }
+            return Arrays.asList(descs).iterator();
+        }
+        return null;
+    }
+
+    @Override
+    public Class<?> getCommonPropertyType(ELContext context, Object base) {
+        if (base instanceof List<?>) { // implies base != null
+            return Integer.class;
+        }
+        return null;
+    }
+
+    private static final int coerce(Object property) {
+        if (property instanceof Number) {
+            return ((Number) property).intValue();
+        }
+        if (property instanceof Character) {
+            return ((Character) property).charValue();
+        }
+        if (property instanceof Boolean) {
+            return (((Boolean) property).booleanValue() ? 1 : 0);
+        }
+        if (property instanceof String) {
+            return Integer.parseInt((String) property);
+        }
+        throw new IllegalArgumentException(property != null ?
+                property.toString() : "null");
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/el/LocalStrings.properties b/bundles/org.apache.tomcat/src/javax/el/LocalStrings.properties
new file mode 100644
index 0000000..2a753d7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/el/LocalStrings.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.
+
+# Default localized string information
+# Localized for Locale en_US
+
+propertyNotFound=Property ''{1}'' not found on type {0}
+propertyNotReadable=Property ''{1}'' not readable on type {0}
+propertyNotWritable=Property ''{1}'' not writable on type {0}
+propertyReadError=Error reading ''{1}'' on type {0}
+propertyWriteError=Error writing ''{1}'' on type {0}
+resolverNotWritable=ELResolver not writable for type {0}
+objectNotAssignable=Unable to add an object of type [{0}] to an array of objects of type [{1}]
diff --git a/bundles/org.apache.tomcat/src/javax/el/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/javax/el/LocalStrings_es.properties
new file mode 100644
index 0000000..68cfad2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/el/LocalStrings_es.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.
+
+propertyNotFound = Propiedad ''{1}'' no hallada en el tipo {0}
+propertyNotReadable = Propiedad ''{1}'' no legible para el tipo {0}
+propertyNotWritable = Propiedad ''{1}'' no grabable para el tipo {0}
+propertyReadError = Error reading ''{1}'' en el tipo {0}
+propertyWriteError = Error writing ''{1}'' en el tipo {0}
+resolverNotWritable = ELResolver no grabable para el tipo {0}
diff --git a/bundles/org.apache.tomcat/src/javax/el/MapELResolver.java b/bundles/org.apache.tomcat/src/javax/el/MapELResolver.java
new file mode 100644
index 0000000..e7aa462
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/el/MapELResolver.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.el;
+
+import java.beans.FeatureDescriptor;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+public class MapELResolver extends ELResolver {
+
+    private static final Class<?> UNMODIFIABLE = Collections.unmodifiableMap(
+            new HashMap<Object, Object>()).getClass();
+
+    private final boolean readOnly;
+
+    public MapELResolver() {
+        this.readOnly = false;
+    }
+
+    public MapELResolver(boolean readOnly) {
+        this.readOnly = readOnly;
+    }
+
+    @Override
+    public Object getValue(ELContext context, Object base, Object property)
+            throws NullPointerException, PropertyNotFoundException, ELException {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base instanceof Map<?,?>) {
+            context.setPropertyResolved(true);
+            return ((Map<?,?>) base).get(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 instanceof Map<?,?>) {
+            context.setPropertyResolved(true);
+            Object obj = ((Map<?,?>) base).get(property);
+            return (obj != null) ? obj.getClass() : null;
+        }
+        
+        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 instanceof Map<?, ?>) {
+            context.setPropertyResolved(true);
+
+            if (this.readOnly) {
+                throw new PropertyNotWritableException(message(context,
+                        "resolverNotWriteable", new Object[] { base.getClass()
+                                .getName() }));
+            }
+
+            try {
+                @SuppressWarnings("unchecked") // Must be OK
+                Map<Object, Object> map = ((Map<Object, Object>) base);
+                map.put(property, value);
+            } catch (UnsupportedOperationException e) {
+                throw new PropertyNotWritableException(e);
+            }
+        }
+    }
+
+    @Override
+    public boolean isReadOnly(ELContext context, Object base, Object property)
+            throws NullPointerException, PropertyNotFoundException, ELException {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base instanceof Map<?, ?>) {
+            context.setPropertyResolved(true);
+            return this.readOnly || UNMODIFIABLE.equals(base.getClass());
+        }
+        
+        return this.readOnly;
+    }
+
+    @Override
+    public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
+        if (base instanceof Map<?, ?>) {
+            Iterator<?> itr = ((Map<?, ?>) base).keySet().iterator();
+            List<FeatureDescriptor> feats = new ArrayList<FeatureDescriptor>();
+            Object key;
+            FeatureDescriptor desc;
+            while (itr.hasNext()) {
+                key = itr.next();
+                desc = new FeatureDescriptor();
+                desc.setDisplayName(key.toString());
+                desc.setExpert(false);
+                desc.setHidden(false);
+                desc.setName(key.toString());
+                desc.setPreferred(true);
+                desc.setValue(RESOLVABLE_AT_DESIGN_TIME, Boolean.FALSE);
+                desc.setValue(TYPE, key.getClass());
+                feats.add(desc);
+            }
+            return feats.iterator();
+        }
+        return null;
+    }
+
+    @Override
+    public Class<?> getCommonPropertyType(ELContext context, Object base) {
+        if (base instanceof Map<?, ?>) {
+            return Object.class;
+        }
+        return null;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/javax/el/MethodExpression.java b/bundles/org.apache.tomcat/src/javax/el/MethodExpression.java
new file mode 100644
index 0000000..6835a41
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/el/MethodExpression.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.el;
+
+/**
+ *
+ */
+public abstract class MethodExpression extends Expression {
+
+    private static final long serialVersionUID = 8163925562047324656L;
+
+    public abstract MethodInfo getMethodInfo(ELContext context) throws NullPointerException, PropertyNotFoundException, MethodNotFoundException, ELException;
+    
+    public abstract Object invoke(ELContext context, Object[] params) throws NullPointerException, PropertyNotFoundException, MethodNotFoundException, ELException;
+    
+    /**
+     * @since EL 2.2
+     * 
+     * Note: The spelling mistake is deliberate.
+     * isParmetersProvided()  - Specification definition
+     * isParametersProvided() - Corrected spelling
+     */
+    public boolean isParmetersProvided() {
+        // Expected to be over-ridden by implementation
+        return false;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/el/MethodInfo.java b/bundles/org.apache.tomcat/src/javax/el/MethodInfo.java
new file mode 100644
index 0000000..8047e25
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/el/MethodInfo.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 javax.el;
+
+/**
+ *
+ */
+public class MethodInfo {
+
+    private final String name;
+    
+    private final Class<?>[] paramTypes;
+    
+    private final Class<?> returnType;
+    
+    /**
+     * 
+     */
+    public MethodInfo(String name, Class<?> returnType, Class<?>[] paramTypes) {
+        this.name = name;
+        this.returnType = returnType;
+        this.paramTypes = paramTypes;
+    }
+    
+    public String getName() {
+        return this.name;
+    }
+    
+    public Class<?>[] getParamTypes() {
+        return this.paramTypes;
+    }
+    
+    public Class<?> getReturnType() {
+        return this.returnType;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/javax/el/MethodNotFoundException.java b/bundles/org.apache.tomcat/src/javax/el/MethodNotFoundException.java
new file mode 100644
index 0000000..be09681
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/el/MethodNotFoundException.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.el;
+
+/**
+ *
+ */
+public class MethodNotFoundException extends ELException {
+
+    private static final long serialVersionUID = -3631968116081480328L;
+
+    /**
+     * 
+     */
+    public MethodNotFoundException() {
+        super();
+    }
+
+    /**
+     * @param message
+     */
+    public MethodNotFoundException(String message) {
+        super(message);
+    }
+
+    /**
+     * @param message
+     * @param cause
+     */
+    public MethodNotFoundException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * @param cause
+     */
+    public MethodNotFoundException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/el/PropertyNotFoundException.java b/bundles/org.apache.tomcat/src/javax/el/PropertyNotFoundException.java
new file mode 100644
index 0000000..e19127a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/el/PropertyNotFoundException.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.el;
+
+/**
+ *
+ */
+public class PropertyNotFoundException extends ELException {
+
+    private static final long serialVersionUID = -3799200961303506745L;
+
+    /**
+     * 
+     */
+    public PropertyNotFoundException() {
+        super();
+    }
+
+    /**
+     * @param message
+     */
+    public PropertyNotFoundException(String message) {
+        super(message);
+    }
+
+    /**
+     * @param message
+     * @param cause
+     */
+    public PropertyNotFoundException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * @param cause
+     */
+    public PropertyNotFoundException(Throwable cause) {
+        super(cause);
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/javax/el/PropertyNotWritableException.java b/bundles/org.apache.tomcat/src/javax/el/PropertyNotWritableException.java
new file mode 100644
index 0000000..6beddb7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/el/PropertyNotWritableException.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.el;
+
+/**
+ *
+ */
+public class PropertyNotWritableException extends ELException {
+
+    private static final long serialVersionUID = 827987155471214717L;
+
+    /**
+     * 
+     */
+    public PropertyNotWritableException() {
+        super();
+    }
+
+    /**
+     * @param message
+     */
+    public PropertyNotWritableException(String message) {
+        super(message);
+    }
+
+    /**
+     * @param message
+     * @param cause
+     */
+    public PropertyNotWritableException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * @param cause
+     */
+    public PropertyNotWritableException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/el/ResourceBundleELResolver.java b/bundles/org.apache.tomcat/src/javax/el/ResourceBundleELResolver.java
new file mode 100644
index 0000000..f10ae41
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/el/ResourceBundleELResolver.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.el;
+
+import java.beans.FeatureDescriptor;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+public class ResourceBundleELResolver extends ELResolver {
+
+    public ResourceBundleELResolver() {
+        super();
+    }
+
+    @Override
+    public Object getValue(ELContext context, Object base, Object property)
+            throws NullPointerException, PropertyNotFoundException, ELException {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+        
+        if (base instanceof ResourceBundle) {
+            if (property != null) {
+                try {
+                    Object result = ((ResourceBundle) base).getObject(property
+                            .toString());
+                    context.setPropertyResolved(true);
+                    return result;
+                } catch (MissingResourceException mre) {
+                    return "???" + property.toString() + "???";
+                }
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public Class<?> getType(ELContext context, Object base, Object property)
+            throws NullPointerException, PropertyNotFoundException, ELException {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+        
+        if (base instanceof ResourceBundle) {
+            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 instanceof ResourceBundle) {
+            context.setPropertyResolved(true);
+            throw new PropertyNotWritableException(message(context,
+                    "resolverNotWriteable", new Object[] { base.getClass()
+                            .getName() }));
+        }
+    }
+
+    @Override
+    public boolean isReadOnly(ELContext context, Object base, Object property)
+            throws NullPointerException, PropertyNotFoundException, ELException {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+        
+        if (base instanceof ResourceBundle) {
+            context.setPropertyResolved(true);
+        }
+        
+        return true;
+    }
+
+    @Override
+    // Can't use Iterator<FeatureDescriptor> because API needs to match
+    // specification
+    @SuppressWarnings({ "unchecked", "rawtypes" }) 
+    public Iterator getFeatureDescriptors(
+            ELContext context, Object base) {
+        if (base instanceof ResourceBundle) {
+            List<FeatureDescriptor> feats = new ArrayList<FeatureDescriptor>();
+            Enumeration<String> e = ((ResourceBundle) base).getKeys();
+            FeatureDescriptor feat;
+            String key;
+            while (e.hasMoreElements()) {
+                key = e.nextElement();
+                feat = new FeatureDescriptor();
+                feat.setDisplayName(key);
+                feat.setExpert(false);
+                feat.setHidden(false);
+                feat.setName(key);
+                feat.setPreferred(true);
+                feat.setValue(RESOLVABLE_AT_DESIGN_TIME, Boolean.TRUE);
+                feat.setValue(TYPE, String.class);
+                feats.add(feat);
+            }
+            return feats.iterator();
+        }
+        return null;
+    }
+
+    @Override
+    public Class<?> getCommonPropertyType(ELContext context, Object base) {
+        if (base instanceof ResourceBundle) {
+            return String.class;
+        }
+        return null;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/javax/el/ValueExpression.java b/bundles/org.apache.tomcat/src/javax/el/ValueExpression.java
new file mode 100644
index 0000000..ad78548
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/el/ValueExpression.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.el;
+
+/**
+ *
+ */
+public abstract class ValueExpression extends Expression {
+
+    private static final long serialVersionUID = 8577809572381654673L;
+
+    public abstract Class<?> getExpectedType();
+    
+    public abstract Class<?> getType(ELContext context) throws NullPointerException, PropertyNotFoundException, ELException;
+    
+    public abstract boolean isReadOnly(ELContext context) throws NullPointerException, PropertyNotFoundException, ELException;
+    
+    public abstract void setValue(ELContext context, Object value) throws NullPointerException, PropertyNotFoundException, PropertyNotWritableException, ELException;
+    
+    public abstract Object getValue(ELContext context) throws NullPointerException, PropertyNotFoundException, ELException;
+
+    /**
+     * @since EL 2.2
+     */
+    public ValueReference getValueReference(@SuppressWarnings("unused") ELContext context) {
+     // Expected to be over-ridden by implementation
+        return null;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/el/ValueReference.java b/bundles/org.apache.tomcat/src/javax/el/ValueReference.java
new file mode 100644
index 0000000..61807fe
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/el/ValueReference.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.el;
+
+import java.io.Serializable;
+
+/**
+ * @since EL 2.2
+ */
+public class ValueReference implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+    
+    private final Object base;
+    private final Object property;
+    
+    public ValueReference(Object base, Object property) {
+        this.base = base;
+        this.property = property;
+    }
+
+    public Object getBase() {
+        return base;
+    }
+    
+    public Object getProperty() {
+        return property;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/el/VariableMapper.java b/bundles/org.apache.tomcat/src/javax/el/VariableMapper.java
new file mode 100644
index 0000000..84c3682
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/el/VariableMapper.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.el;
+
+/**
+ *
+ */
+public abstract class VariableMapper {
+
+    public abstract ValueExpression resolveVariable(String variable);
+    
+    public abstract ValueExpression setVariable(String variable, ValueExpression expression);
+}
diff --git a/bundles/org.apache.tomcat/src/javax/mail/internet/InternetAddress.java b/bundles/org.apache.tomcat/src/javax/mail/internet/InternetAddress.java
new file mode 100644
index 0000000..4daa35d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/mail/internet/InternetAddress.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.internet;
+
+@SuppressWarnings("unused") // Dummy implementation
+public class InternetAddress {
+    public InternetAddress(String from) {
+        // Dummy implementation
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/mail/internet/MimeMessage.java b/bundles/org.apache.tomcat/src/javax/mail/internet/MimeMessage.java
new file mode 100644
index 0000000..2587730
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/mail/internet/MimeMessage.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 javax.mail.internet;
+
+import javax.mail.Session;
+
+@SuppressWarnings("unused") // Dummy implementation
+public class MimeMessage implements MimePart {
+    public MimeMessage(Session session) {
+        // Dummy implementation
+    }
+    public void setFrom(InternetAddress from) {
+        // Dummy implementation
+    }
+    public void setSubject(String subject) {
+        // Dummy implementation
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/mail/internet/MimePart.java b/bundles/org.apache.tomcat/src/javax/mail/internet/MimePart.java
new file mode 100644
index 0000000..4b57b20
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/mail/internet/MimePart.java
@@ -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.
+ */
+package javax.mail.internet;
+
+public interface MimePart {
+    // Dummy implementation
+}
diff --git a/bundles/org.apache.tomcat/src/javax/mail/internet/MimePartDataSource.java b/bundles/org.apache.tomcat/src/javax/mail/internet/MimePartDataSource.java
new file mode 100644
index 0000000..28a3f39
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/mail/internet/MimePartDataSource.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.internet;
+
+@SuppressWarnings("unused") // Dummy implementation
+public class MimePartDataSource {
+    public MimePartDataSource(MimePart part) {
+        // Dummy implementation
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/persistence/PersistenceContext.java b/bundles/org.apache.tomcat/src/javax/persistence/PersistenceContext.java
new file mode 100644
index 0000000..e8417e2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/persistence/PersistenceContext.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 javax.persistence;
+
+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 PersistenceContext {
+   String name() default "";
+   String unitName() default "";
+   PersistenceContextType type() default PersistenceContextType.TRANSACTION;
+   PersistenceProperty[] properties() default {};
+}
diff --git a/bundles/org.apache.tomcat/src/javax/persistence/PersistenceContextType.java b/bundles/org.apache.tomcat/src/javax/persistence/PersistenceContextType.java
new file mode 100644
index 0000000..500c692
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/persistence/PersistenceContextType.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.persistence;
+
+public enum PersistenceContextType {
+    TRANSACTION,
+    EXTENDED
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/javax/persistence/PersistenceContexts.java b/bundles/org.apache.tomcat/src/javax/persistence/PersistenceContexts.java
new file mode 100644
index 0000000..d451f24
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/persistence/PersistenceContexts.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.persistence;
+
+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 PersistenceContexts {
+   PersistenceContext[] value();
+}
diff --git a/bundles/org.apache.tomcat/src/javax/persistence/PersistenceProperty.java b/bundles/org.apache.tomcat/src/javax/persistence/PersistenceProperty.java
new file mode 100644
index 0000000..20a42c5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/persistence/PersistenceProperty.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 javax.persistence;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({})
+@Retention(RetentionPolicy.RUNTIME)
+
+public @interface PersistenceProperty {
+   String name();
+   String value();
+}
diff --git a/bundles/org.apache.tomcat/src/javax/persistence/PersistenceUnit.java b/bundles/org.apache.tomcat/src/javax/persistence/PersistenceUnit.java
new file mode 100644
index 0000000..e42a3b2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/persistence/PersistenceUnit.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 javax.persistence;
+
+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 PersistenceUnit {
+   String name() default "";
+   String unitName() default "";
+}
diff --git a/bundles/org.apache.tomcat/src/javax/persistence/PersistenceUnits.java b/bundles/org.apache.tomcat/src/javax/persistence/PersistenceUnits.java
new file mode 100644
index 0000000..3f51845
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/persistence/PersistenceUnits.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.persistence;
+
+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 PersistenceUnits {
+   PersistenceUnit[] value();
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/BodyContent.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/BodyContent.java
new file mode 100644
index 0000000..f700cb6
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/BodyContent.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 javax.servlet.jsp.tagext;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+
+import javax.servlet.jsp.JspWriter;
+
+/**
+ * An encapsulation of the evaluation of the body of an action so it is
+ * available to a tag handler. BodyContent is a subclass of JspWriter.
+ * <p>
+ * Note that the content of BodyContent is the result of evaluation, so it will
+ * not contain actions and the like, but the result of their invocation.
+ * <p>
+ * BodyContent has methods to convert its contents into a String, to read its
+ * contents, and to clear the contents.
+ * <p>
+ * The buffer size of a BodyContent object is unbounded. A BodyContent object
+ * cannot be in autoFlush mode. It is not possible to invoke flush on a
+ * BodyContent object, as there is no backing stream.
+ * <p>
+ * Instances of BodyContent are created by invoking the pushBody and popBody
+ * methods of the PageContext class. A BodyContent is enclosed within another
+ * JspWriter (maybe another BodyContent object) following the structure of their
+ * associated actions.
+ * <p>
+ * A BodyContent is made available to a BodyTag through a setBodyContent() call.
+ * The tag handler can use the object until after the call to doEndTag().
+ */
+public abstract class BodyContent extends JspWriter {
+
+    /**
+     * Protected constructor. Unbounded buffer, no autoflushing.
+     * 
+     * @param e
+     *            the enclosing JspWriter
+     */
+    protected BodyContent(JspWriter e) {
+        super(UNBOUNDED_BUFFER, false);
+        this.enclosingWriter = e;
+    }
+
+    /**
+     * Redefined flush() so it is not legal.
+     * <p>
+     * It is not valid to flush a BodyContent because there is no backing stream
+     * behind it.
+     * 
+     * @throws IOException
+     *             always thrown
+     */
+    @Override
+    public void flush() throws IOException {
+        throw new IOException("Illegal to flush within a custom tag");
+    }
+
+    /**
+     * Clear the body without throwing any exceptions.
+     */
+    public void clearBody() {
+        try {
+            this.clear();
+        } catch (IOException ex) {
+            // TODO -- clean this one up.
+            throw new Error("internal error!;");
+        }
+    }
+
+    /**
+     * Return the value of this BodyContent as a Reader.
+     * 
+     * @return the value of this BodyContent as a Reader
+     */
+    public abstract Reader getReader();
+
+    /**
+     * Return the value of the BodyContent as a String.
+     * 
+     * @return the value of the BodyContent as a String
+     */
+    public abstract String getString();
+
+    /**
+     * Write the contents of this BodyContent into a Writer. Subclasses may
+     * optimize common invocation patterns.
+     * 
+     * @param out
+     *            The writer into which to place the contents of this body
+     *            evaluation
+     * @throws IOException
+     *             if an I/O error occurred while writing the contents of this
+     *             BodyContent to the given Writer
+     */
+    public abstract void writeOut(Writer out) throws IOException;
+
+    /**
+     * Get the enclosing JspWriter.
+     * 
+     * @return the enclosing JspWriter passed at construction time
+     */
+    public JspWriter getEnclosingWriter() {
+        return enclosingWriter;
+    }
+
+    // private fields
+
+    private final JspWriter enclosingWriter;
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/BodyTag.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/BodyTag.java
new file mode 100644
index 0000000..6452bd5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/BodyTag.java
@@ -0,0 +1,162 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.servlet.jsp.tagext;
+
+import javax.servlet.jsp.JspException;
+
+
+/**
+ * The BodyTag interface extends IterationTag by defining additional methods
+ * that let a tag handler manipulate the content of evaluating its body.
+ * <p>
+ * It is the responsibility of the tag handler to manipulate the body content.
+ * For example the tag handler may take the body content, convert it into a
+ * String using the bodyContent.getString method and then use it. Or the tag
+ * handler may take the body content and write it out into its enclosing
+ * JspWriter using the bodyContent.writeOut method.
+ * <p>
+ * A tag handler that implements BodyTag is treated as one that implements
+ * IterationTag, except that the doStartTag method can return SKIP_BODY,
+ * EVAL_BODY_INCLUDE or EVAL_BODY_BUFFERED.
+ * <p>
+ * If EVAL_BODY_INCLUDE is returned, then evaluation happens as in IterationTag.
+ * <p>
+ * If EVAL_BODY_BUFFERED is returned, then a BodyContent object will be created
+ * (by code generated by the JSP compiler) to capture the body evaluation. The
+ * code generated by the JSP compiler obtains the BodyContent object by calling
+ * the pushBody method of the current pageContext, which additionally has the
+ * effect of saving the previous out value. The page compiler returns this
+ * object by calling the popBody method of the PageContext class; the call also
+ * restores the value of out.
+ * <p>
+ * The interface provides one new property with a setter method and one new
+ * action method.
+ * <p>
+ * <B>Properties</B>
+ * <p>
+ * There is a new property: bodyContent, to contain the BodyContent object,
+ * where the JSP Page implementation object will place the evaluation (and
+ * reevaluation, if appropriate) of the body. The setter method (setBodyContent)
+ * will only be invoked if doStartTag() returns EVAL_BODY_BUFFERED and the
+ * corresponding action element does not have an empty body.
+ * <p>
+ * <B>Methods</B>
+ * <p>
+ * In addition to the setter method for the bodyContent property, there is a new
+ * action method: doInitBody(), which is invoked right after setBodyContent()
+ * and before the body evaluation. This method is only invoked if doStartTag()
+ * returns EVAL_BODY_BUFFERED.
+ * <p>
+ * <B>Lifecycle</B>
+ * <p>
+ * Lifecycle details are described by the transition diagram below. Exceptions
+ * that are thrown during the computation of doStartTag(), setBodyContent(),
+ * doInitBody(), BODY, doAfterBody() interrupt the execution sequence and are
+ * propagated up the stack, unless the tag handler implements the
+ * TryCatchFinally interface; see that interface for details.
+ * <p>
+ * <IMG src="doc-files/BodyTagProtocol.gif"
+ * alt="Lifecycle Details Transition Diagram for BodyTag"/>
+ * <p>
+ * <B>Empty and Non-Empty Action</B>
+ * <p>
+ * If the TagLibraryDescriptor file indicates that the action must always have
+ * an empty element body, by an &lt;body-content&gt; entry of "empty", then the
+ * doStartTag() method must return SKIP_BODY. Otherwise, the doStartTag() method
+ * may return SKIP_BODY, EVAL_BODY_INCLUDE, or EVAL_BODY_BUFFERED.
+ * <p>
+ * Note that which methods are invoked after the doStartTag() depends on both
+ * the return value and on if the custom action element is empty or not in the
+ * JSP page, not how it's declared in the TLD.
+ * <p>
+ * If SKIP_BODY is returned the body is not evaluated, and doEndTag() is
+ * invoked.
+ * <p>
+ * If EVAL_BODY_INCLUDE is returned, and the custom action element is not empty,
+ * setBodyContent() is not invoked, doInitBody() is not invoked, the body is
+ * evaluated and "passed through" to the current out, doAfterBody() is invoked
+ * and then, after zero or more iterations, doEndTag() is invoked. If the custom
+ * action element is empty, only doStart() and doEndTag() are invoked.
+ * <p>
+ * If EVAL_BODY_BUFFERED is returned, and the custom action element is not
+ * empty, setBodyContent() is invoked, doInitBody() is invoked, the body is
+ * evaluated, doAfterBody() is invoked, and then, after zero or more iterations,
+ * doEndTag() is invoked. If the custom action element is empty, only doStart()
+ * and doEndTag() are invoked.
+ */
+public interface BodyTag extends IterationTag {
+
+    /**
+     * Deprecated constant that has the same value as EVAL_BODY_BUFFERED and
+     * EVAL_BODY_AGAIN. This name has been marked as deprecated to encourage the
+     * use of the two different terms, which are much more descriptive.
+     * 
+     * @deprecated As of Java JSP API 1.2, use BodyTag.EVAL_BODY_BUFFERED or
+     *             IterationTag.EVAL_BODY_AGAIN.
+     */
+    @SuppressWarnings("dep-ann")
+    // TCK signature test fails with annotation
+    public static final int EVAL_BODY_TAG = 2;
+
+    /**
+     * Request the creation of new buffer, a BodyContent on which to evaluate
+     * the body of this tag. Returned from doStartTag when it implements
+     * BodyTag. This is an illegal return value for doStartTag when the class
+     * does not implement BodyTag.
+     */
+    public static final int EVAL_BODY_BUFFERED = 2;
+
+    /**
+     * Set the bodyContent property. This method is invoked by the JSP page
+     * implementation object at most once per action invocation. This method
+     * will be invoked before doInitBody. This method will not be invoked for
+     * empty tags or for non-empty tags whose doStartTag() method returns
+     * SKIP_BODY or EVAL_BODY_INCLUDE.
+     * <p>
+     * When setBodyContent is invoked, the value of the implicit object out has
+     * already been changed in the pageContext object. The BodyContent object
+     * passed will have not data on it but may have been reused (and cleared)
+     * from some previous invocation.
+     * <p>
+     * The BodyContent object is available and with the appropriate content
+     * until after the invocation of the doEndTag method, at which case it may
+     * be reused.
+     * 
+     * @param b
+     *            the BodyContent
+     * @see #doInitBody
+     * @see #doAfterBody
+     */
+    void setBodyContent(BodyContent b);
+
+    /**
+     * Prepare for evaluation of the body. This method is invoked by the JSP
+     * page implementation object after setBodyContent and before the first time
+     * the body is to be evaluated. This method will not be invoked for empty
+     * tags or for non-empty tags whose doStartTag() method returns SKIP_BODY or
+     * EVAL_BODY_INCLUDE.
+     * <p>
+     * The JSP container will resynchronize the values of any AT_BEGIN and
+     * NESTED variables (defined by the associated TagExtraInfo or TLD) after
+     * the invocation of doInitBody().
+     * 
+     * @throws JspException
+     *             if an error occurred while processing this tag
+     * @see #doAfterBody
+     */
+    void doInitBody() throws JspException;
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/BodyTagSupport.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/BodyTagSupport.java
new file mode 100644
index 0000000..740a73b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/BodyTagSupport.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 javax.servlet.jsp.tagext;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.JspWriter;
+
+/**
+ * A base class for defining tag handlers implementing BodyTag.
+ * <p>
+ * The BodyTagSupport class implements the BodyTag interface and adds additional
+ * convenience methods including getter methods for the bodyContent property and
+ * methods to get at the previous out JspWriter.
+ * <p>
+ * Many tag handlers will extend BodyTagSupport and only redefine a few methods.
+ */
+public class BodyTagSupport extends TagSupport implements BodyTag {
+
+    private static final long serialVersionUID = -7235752615580319833L;
+
+    /**
+     * Default constructor, all subclasses are required to only define a public
+     * constructor with the same signature, and to call the superclass
+     * constructor. This constructor is called by the code generated by the JSP
+     * translator.
+     */
+    public BodyTagSupport() {
+        super();
+    }
+
+    /**
+     * Default processing of the start tag returning EVAL_BODY_BUFFERED.
+     * 
+     * @return EVAL_BODY_BUFFERED
+     * @throws JspException
+     *             if an error occurred while processing this tag
+     * @see BodyTag#doStartTag
+     */
+    @Override
+    public int doStartTag() throws JspException {
+        return EVAL_BODY_BUFFERED;
+    }
+
+    /**
+     * Default processing of the end tag returning EVAL_PAGE.
+     * 
+     * @return EVAL_PAGE
+     * @throws JspException
+     *             if an error occurred while processing this tag
+     * @see Tag#doEndTag
+     */
+    @Override
+    public int doEndTag() throws JspException {
+        return super.doEndTag();
+    }
+
+    // Actions related to body evaluation
+
+    /**
+     * Prepare for evaluation of the body: stash the bodyContent away.
+     * 
+     * @param b
+     *            the BodyContent
+     * @see #doAfterBody
+     * @see #doInitBody()
+     * @see BodyTag#setBodyContent
+     */
+    @Override
+    public void setBodyContent(BodyContent b) {
+        this.bodyContent = b;
+    }
+
+    /**
+     * Prepare for evaluation of the body just before the first body evaluation:
+     * no action.
+     * 
+     * @throws JspException
+     *             if an error occurred while processing this tag
+     * @see #setBodyContent
+     * @see #doAfterBody
+     * @see BodyTag#doInitBody
+     */
+    @Override
+    public void doInitBody() throws JspException {
+        // NOOP by default
+    }
+
+    /**
+     * After the body evaluation: do not reevaluate and continue with the page.
+     * By default nothing is done with the bodyContent data (if any).
+     * 
+     * @return SKIP_BODY
+     * @throws JspException
+     *             if an error occurred while processing this tag
+     * @see #doInitBody
+     * @see BodyTag#doAfterBody
+     */
+    @Override
+    public int doAfterBody() throws JspException {
+        return SKIP_BODY;
+    }
+
+    /**
+     * Release state.
+     * 
+     * @see Tag#release
+     */
+    @Override
+    public void release() {
+        bodyContent = null;
+
+        super.release();
+    }
+
+    /**
+     * Get current bodyContent.
+     * 
+     * @return the body content.
+     */
+    public BodyContent getBodyContent() {
+        return bodyContent;
+    }
+
+    /**
+     * Get surrounding out JspWriter.
+     * 
+     * @return the enclosing JspWriter, from the bodyContent.
+     */
+    public JspWriter getPreviousOut() {
+        return bodyContent.getEnclosingWriter();
+    }
+
+    // protected fields
+
+    /**
+     * The current BodyContent for this BodyTag.
+     */
+    protected transient BodyContent bodyContent;
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/DynamicAttributes.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/DynamicAttributes.java
new file mode 100644
index 0000000..be77f64
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/DynamicAttributes.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 javax.servlet.jsp.tagext;
+
+import javax.servlet.jsp.JspException;
+
+/**
+ * For a tag to declare that it accepts dynamic attributes, it must implement
+ * this interface.  The entry for the tag in the Tag Library Descriptor must 
+ * also be configured to indicate dynamic attributes are accepted.
+ * <br>
+ * For any attribute that is not declared in the Tag Library Descriptor for
+ * this tag, instead of getting an error at translation time, the 
+ * <code>setDynamicAttribute()</code> method is called, with the name and
+ * value of the attribute.  It is the responsibility of the tag to 
+ * remember the names and values of the dynamic attributes.
+ *
+ * @since 2.0
+ */
+public interface DynamicAttributes {
+    
+    /**
+     * Called when a tag declared to accept dynamic attributes is passed
+     * an attribute that is not declared in the Tag Library Descriptor.
+     * 
+     * @param uri the namespace of the attribute, or null if in the default
+     *     namespace.
+     * @param localName the name of the attribute being set.
+     * @param value the value of the attribute
+     * @throws JspException if the tag handler wishes to
+     *     signal that it does not accept the given attribute.  The 
+     *     container must not call doStartTag() or doTag() for this tag.
+     */
+    public void setDynamicAttribute(
+        String uri, String localName, Object value ) 
+        throws JspException;
+    
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/FunctionInfo.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/FunctionInfo.java
new file mode 100644
index 0000000..56e2341
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/FunctionInfo.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 javax.servlet.jsp.tagext;
+
+/**
+ * Information for a function in a Tag Library.
+ * This class is instantiated from the Tag Library Descriptor file (TLD)
+ * and is available only at translation time.
+ * 
+ * @since 2.0
+ */
+public class FunctionInfo {
+
+    /**
+     * Constructor for FunctionInfo.
+     *
+     * @param name The name of the function
+     * @param klass The class of the function
+     * @param signature The signature of the function
+     */
+
+    public FunctionInfo(String name, String klass, String signature) {
+        this.name = name;
+        this.functionClass = klass;
+        this.functionSignature = signature;
+    }
+
+    /**
+     * The name of the function.
+     *
+     * @return The name of the function
+     */
+
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * The class of the function.
+     *
+     * @return The class of the function
+     */
+
+    public String getFunctionClass() {
+        return functionClass;
+    }
+
+    /**
+     * The signature of the function.
+     *
+     * @return The signature of the function
+     */
+
+    public String getFunctionSignature() {
+        return functionSignature;
+    }
+
+    /*
+     * fields
+     */
+    private final String name;
+    private final String functionClass;
+    private final String functionSignature;
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/IterationTag.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/IterationTag.java
new file mode 100644
index 0000000..f2aaef5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/IterationTag.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 javax.servlet.jsp.tagext;
+
+import javax.servlet.jsp.JspException;
+
+
+/**
+ * The IterationTag interface extends Tag by defining one additional
+ * method that controls the reevaluation of its body.
+ *
+ * <p> A tag handler that implements IterationTag is treated as one that
+ * implements Tag regarding  the doStartTag() and doEndTag() methods.
+ * IterationTag provides a new method: <code>doAfterBody()</code>.
+ *
+ * <p> The doAfterBody() method is invoked after every body evaluation
+ * to control whether the body will be reevaluated or not.  If doAfterBody()
+ * returns IterationTag.EVAL_BODY_AGAIN, then the body will be reevaluated.
+ * If doAfterBody() returns Tag.SKIP_BODY, then the body will be skipped
+ * and doEndTag() will be evaluated instead.
+ *
+ * <p><B>Properties</B>
+ * There are no new properties in addition to those in Tag.
+ *
+ * <p><B>Methods</B>
+ * There is one new methods: doAfterBody().
+ *
+ * <p><B>Lifecycle</B>
+ *
+ * <p> Lifecycle details are described by the transition diagram
+ * below.  Exceptions that are thrown during the computation of
+ * doStartTag(), BODY and doAfterBody() interrupt the execution
+ * sequence and are propagated up the stack, unless the tag handler
+ * implements the TryCatchFinally interface; see that interface for
+ * details.
+ *
+ * <p>
+ * <IMG src="doc-files/IterationTagProtocol.gif"
+ *      alt="Lifecycle Details Transition Diagram for IterationTag"/>
+ *
+ * <p><B>Empty and Non-Empty Action</B>
+ * <p> If the TagLibraryDescriptor file indicates that the action must
+ * always have an empty element body, by a &lt;body-content&gt; entry of 
+ * "empty", then the doStartTag() method must return SKIP_BODY.
+ *
+ * <p>Note that which methods are invoked after the doStartTag() depends on
+ * both the return value and on if the custom action element is empty
+ * or not in the JSP page, not on how it's declared in the TLD.
+ *
+ * <p>
+ * If SKIP_BODY is returned the body is not evaluated, and then doEndTag()
+ * is invoked.
+ *
+ * <p>
+ * If EVAL_BODY_INCLUDE is returned, and the custom action element is not
+ * empty, the body is evaluated and "passed through" to the current out, 
+ * then doAfterBody() is invoked and, after zero or more iterations, 
+ * doEndTag() is invoked.
+ */
+
+public interface IterationTag extends Tag {
+
+    /**
+     * Request the reevaluation of some body.
+     * Returned from doAfterBody.
+     *
+     * For compatibility with JSP 1.1, the value is carefully selected
+     * to be the same as the, now deprecated, BodyTag.EVAL_BODY_TAG,
+     * 
+     */
+ 
+    public static final int EVAL_BODY_AGAIN = 2;
+
+    /**
+     * Process body (re)evaluation.  This method is invoked by the
+     * JSP Page implementation object after every evaluation of
+     * the body into the BodyEvaluation object. The method is
+     * not invoked if there is no body evaluation.
+     *
+     * <p>
+     * If doAfterBody returns EVAL_BODY_AGAIN, a new evaluation of the
+     * body will happen (followed by another invocation of doAfterBody).
+     * If doAfterBody returns SKIP_BODY, no more body evaluations will occur,
+     * and the doEndTag method will be invoked.
+     *
+     * <p>
+     * If this tag handler implements BodyTag and doAfterBody returns
+     * SKIP_BODY, the value of out will be restored using the popBody 
+     * method in pageContext prior to invoking doEndTag.
+     *
+     * <p>
+     * The method re-invocations may be lead to different actions because
+     * there might have been some changes to shared state, or because
+     * of external computation.
+     *
+     * <p>
+     * The JSP container will resynchronize the values of any AT_BEGIN and
+     * NESTED variables (defined by the associated TagExtraInfo or TLD) after
+     * the invocation of doAfterBody().
+     *
+     * @return whether additional evaluations of the body are desired
+     * @throws JspException if an error occurred while processing this tag
+     */
+
+    int doAfterBody() throws JspException;
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/JspFragment.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/JspFragment.java
new file mode 100644
index 0000000..c5b3b08
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/JspFragment.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.tagext;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.servlet.jsp.JspContext;
+import javax.servlet.jsp.JspException;
+
+/**
+ * Encapsulates a portion of JSP code in an object that 
+ * can be invoked as many times as needed.  JSP Fragments are defined 
+ * using JSP syntax as the body of a tag for an invocation to a SimpleTag 
+ * handler, or as the body of a &lt;jsp:attribute&gt; standard action
+ * specifying the value of an attribute that is declared as a fragment,
+ * or to be of type JspFragment in the TLD.
+ * <p>
+ * The definition of the JSP fragment must only contain template 
+ * text and JSP action elements.  In other words, it must not contain
+ * scriptlets or scriptlet expressions.  At translation time, the 
+ * container generates an implementation of the JspFragment abstract class
+ * capable of executing the defined fragment.
+ * <p>
+ * A tag handler can invoke the fragment zero or more times, or 
+ * pass it along to other tags, before returning.  To communicate values
+ * to/from a JSP fragment, tag handlers store/retrieve values in 
+ * the JspContext associated with the fragment.
+ * <p>
+ * Note that tag library developers and page authors should not generate
+ * JspFragment implementations manually.
+ * <p>
+ * <i>Implementation Note</i>: It is not necessary to generate a 
+ * separate class for each fragment.  One possible implementation is 
+ * to generate a single helper class for each page that implements 
+ * JspFragment. Upon construction, a discriminator can be passed to 
+ * select which fragment that instance will execute.
+ *
+ * @since 2.0
+ */
+public abstract class JspFragment {
+
+    /**
+     * Executes the fragment and directs all output to the given Writer,
+     * or the JspWriter returned by the getOut() method of the JspContext
+     * associated with the fragment if out is null.
+     *
+     * @param out The Writer to output the fragment to, or null if 
+     *     output should be sent to JspContext.getOut().
+     * @throws javax.servlet.jsp.JspException Thrown if an error occurred
+     *     while invoking this fragment.
+     * @throws javax.servlet.jsp.SkipPageException Thrown if the page
+     *     that (either directly or indirectly) invoked the tag handler that
+     *     invoked this fragment is to cease evaluation.  The container
+     *     must throw this exception if a Classic Tag Handler returned
+     *     Tag.SKIP_PAGE or if a Simple Tag Handler threw SkipPageException.
+     * @throws java.io.IOException If there was an error writing to the 
+     *     stream.
+     */
+    public abstract void invoke( Writer out )
+        throws JspException, IOException;
+
+    /**
+     * Returns the JspContext that is bound to this JspFragment.
+     *
+     * @return The JspContext used by this fragment at invocation time.
+     */
+    public abstract JspContext getJspContext();
+
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/JspIdConsumer.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/JspIdConsumer.java
new file mode 100644
index 0000000..f3d49bf
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/JspIdConsumer.java
@@ -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.
+ */
+package javax.servlet.jsp.tagext;
+
+public interface JspIdConsumer {
+    public void setJspId(String jspId);
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/JspTag.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/JspTag.java
new file mode 100644
index 0000000..3c8d138
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/JspTag.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 javax.servlet.jsp.tagext;
+
+/**
+ * Serves as a base class for Tag and SimpleTag.  
+ * This is mostly for organizational and type-safety purposes.
+ *
+ * @since 2.0
+ */
+public interface JspTag {
+    // No methods even through there are some common methods
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/PageData.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/PageData.java
new file mode 100644
index 0000000..9f6343c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/PageData.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 javax.servlet.jsp.tagext;
+
+import java.io.InputStream;
+
+/**
+ * Translation-time information on a JSP page.  The information
+ * corresponds to the XML view of the JSP page.
+ *
+ * <p>
+ * Objects of this type are generated by the JSP translator, e.g.
+ * when being passed to a TagLibraryValidator instance.
+ */
+
+public abstract class PageData {
+
+    /**
+     * Sole constructor. (For invocation by subclass constructors, 
+     * typically implicit.)
+     */
+    public PageData() {
+        // NOOP by default
+    }
+    
+    /**
+     * Returns an input stream on the XML view of a JSP page.
+     * The stream is encoded in UTF-8.  Recall that the XML view of a 
+     * JSP page has the include directives expanded.
+     * 
+     * @return An input stream on the document.
+     */
+   public abstract InputStream getInputStream();
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/SimpleTag.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/SimpleTag.java
new file mode 100644
index 0000000..25569a7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/SimpleTag.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 javax.servlet.jsp.tagext;
+
+import javax.servlet.jsp.JspContext;
+
+/**
+ * Interface for defining Simple Tag Handlers.
+ * 
+ * <p>Simple Tag Handlers differ from Classic Tag Handlers in that instead 
+ * of supporting <code>doStartTag()</code> and <code>doEndTag()</code>, 
+ * the <code>SimpleTag</code> interface provides a simple 
+ * <code>doTag()</code> method, which is called once and only once for any 
+ * given tag invocation.  All tag logic, iteration, body evaluations, etc. 
+ * are to be performed in this single method.  Thus, simple tag handlers 
+ * have the equivalent power of <code>BodyTag</code>, but with a much 
+ * simpler lifecycle and interface.</p>
+ *
+ * <p>To support body content, the <code>setJspBody()</code> 
+ * method is provided.  The container invokes the <code>setJspBody()</code> 
+ * method with a <code>JspFragment</code> object encapsulating the body of 
+ * the tag.  The tag handler implementation can call 
+ * <code>invoke()</code> on that fragment to evaluate the body as
+ * many times as it needs.</p>
+ *
+ * <p>A SimpleTag handler must have a public no-args constructor.  Most
+ * SimpleTag handlers should extend SimpleTagSupport.</p>
+ * 
+ * <p><b>Lifecycle</b></p>
+ *
+ * <p>The following is a non-normative, brief overview of the 
+ * SimpleTag lifecycle.  Refer to the JSP Specification for details.</p>
+ *
+ * <ol>
+ *   <li>A new tag handler instance is created each time by the container 
+ *       by calling the provided zero-args constructor.  Unlike classic
+ *       tag handlers, simple tag handlers are never cached and reused by
+ *       the JSP container.</li>
+ *   <li>The <code>setJspContext()</code> and <code>setParent()</code> 
+ *       methods are called by the container.  The <code>setParent()</code>
+ *       method is only called if the element is nested within another tag 
+ *       invocation.</li>
+ *   <li>The setters for each attribute defined for this tag are called
+ *       by the container.</li>
+ *   <li>If a body exists, the <code>setJspBody()</code> method is called 
+ *       by the container to set the body of this tag, as a 
+ *       <code>JspFragment</code>.  If the action element is empty in
+ *       the page, this method is not called at all.</li>
+ *   <li>The <code>doTag()</code> method is called by the container.  All
+ *       tag logic, iteration, body evaluations, etc. occur in this 
+ *       method.</li>
+ *   <li>The <code>doTag()</code> method returns and all variables are
+ *       synchronized.</li>
+ * </ol>
+ * 
+ * @see SimpleTagSupport
+ * @since 2.0
+ */
+public interface SimpleTag extends JspTag {
+    
+    /** 
+     * Called by the container to invoke this tag.
+     * The implementation of this method is provided by the tag library
+     * developer, and handles all tag processing, body iteration, etc.
+     *
+     * <p>
+     * The JSP container will resynchronize any AT_BEGIN and AT_END
+     * variables (defined by the associated tag file, TagExtraInfo, or TLD)
+     * after the invocation of doTag().
+     * 
+     * @throws javax.servlet.jsp.JspException If an error occurred 
+     *     while processing this tag.
+     * @throws javax.servlet.jsp.SkipPageException If the page that
+     *     (either directly or indirectly) invoked this tag is to
+     *     cease evaluation.  A Simple Tag Handler generated from a 
+     *     tag file must throw this exception if an invoked Classic 
+     *     Tag Handler returned SKIP_PAGE or if an invoked Simple
+     *     Tag Handler threw SkipPageException or if an invoked Jsp Fragment
+     *     threw a SkipPageException.
+     * @throws java.io.IOException If there was an error writing to the
+     *     output stream.
+     */ 
+    public void doTag() 
+        throws javax.servlet.jsp.JspException, java.io.IOException;
+    
+    /**
+     * Sets the parent of this tag, for collaboration purposes.
+     * <p>
+     * The container invokes this method only if this tag invocation is 
+     * nested within another tag invocation.
+     *
+     * @param parent the tag that encloses this tag
+     */
+    public void setParent( JspTag parent );
+    
+    /**
+     * Returns the parent of this tag, for collaboration purposes.
+     *
+     * @return the parent of this tag
+     */ 
+    public JspTag getParent();
+    
+    /**
+     * Called by the container to provide this tag handler with
+     * the <code>JspContext</code> for this invocation.
+     * An implementation should save this value.
+     * 
+     * @param pc the page context for this invocation
+     * @see Tag#setPageContext
+     */
+    public void setJspContext( JspContext pc );
+                
+    /** 
+     * Provides the body of this tag as a JspFragment object, able to be 
+     * invoked zero or more times by the tag handler. 
+     * <p>
+     * This method is invoked by the JSP page implementation 
+     * object prior to <code>doTag()</code>.  If the action element is
+     * empty in the page, this method is not called at all.
+     * 
+     * @param jspBody The fragment encapsulating the body of this tag.
+     */ 
+    public void setJspBody( JspFragment jspBody );
+
+    
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/SimpleTagSupport.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/SimpleTagSupport.java
new file mode 100644
index 0000000..43967ec
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/SimpleTagSupport.java
@@ -0,0 +1,217 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES 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.tagext;
+
+import java.io.IOException;
+
+import javax.servlet.jsp.JspContext;
+import javax.servlet.jsp.JspException;
+
+/**
+ * A base class for defining tag handlers implementing SimpleTag.
+ * <p>
+ * The SimpleTagSupport class is a utility class intended to be used
+ * as the base class for new simple tag handlers.  The SimpleTagSupport
+ * class implements the SimpleTag interface and adds additional
+ * convenience methods including getter methods for the properties in
+ * SimpleTag.
+ *
+ * @since 2.0
+ */
+public class SimpleTagSupport implements SimpleTag {
+    /** Reference to the enclosing tag. */
+    private JspTag parentTag;
+    
+    /** The JSP context for the upcoming tag invocation. */
+    private JspContext jspContext;
+    
+    /** The body of the tag. */
+    private JspFragment jspBody;
+    
+    /**
+     * Sole constructor. (For invocation by subclass constructors, 
+     * typically implicit.)
+     */
+    public SimpleTagSupport() {
+        // NOOP by default
+    }
+    
+    /** 
+     * Default processing of the tag does nothing.
+     *
+     * @throws JspException Subclasses can throw JspException to indicate
+     *     an error occurred while processing this tag.
+     * @throws javax.servlet.jsp.SkipPageException If the page that
+     *     (either directly or indirectly) invoked this tag is to
+     *     cease evaluation.  A Simple Tag Handler generated from a 
+     *     tag file must throw this exception if an invoked Classic 
+     *     Tag Handler returned SKIP_PAGE or if an invoked Simple
+     *     Tag Handler threw SkipPageException or if an invoked Jsp Fragment
+     *     threw a SkipPageException.
+     * @throws IOException Subclasses can throw IOException if there was
+     *     an error writing to the output stream
+     * @see SimpleTag#doTag()
+     */ 
+    @Override
+    public void doTag() throws JspException, IOException {
+        // NOOP by default
+    }
+    
+    /**
+     * Sets the parent of this tag, for collaboration purposes.
+     * <p>
+     * The container invokes this method only if this tag invocation is
+     * nested within another tag invocation.
+     *
+     * @param parent the tag that encloses this tag
+     */
+    @Override
+    public void setParent( JspTag parent ) {
+        this.parentTag = parent;
+    }
+    
+    /**
+     * Returns the parent of this tag, for collaboration purposes.
+     *
+     * @return the parent of this tag
+     */ 
+    @Override
+    public JspTag getParent() {
+        return this.parentTag;
+    }
+    
+    /**
+     * Stores the provided JSP context in the private jspContext field.
+     * Subclasses can access the <code>JspContext</code> via 
+     * <code>getJspContext()</code>.
+     * 
+     * @param pc the page context for this invocation
+     * @see SimpleTag#setJspContext
+     */
+    @Override
+    public void setJspContext( JspContext pc ) {
+        this.jspContext = pc;
+    }
+    
+    /**
+     * Returns the page context passed in by the container via 
+     * setJspContext.
+     *
+     * @return the page context for this invocation
+     */
+    protected JspContext getJspContext() {
+        return this.jspContext;
+    }
+                
+    /** 
+     * Stores the provided JspFragment.
+     *
+     * @param jspBody The fragment encapsulating the body of this tag.
+     *     If the action element is empty in the page, this method is 
+     *     not called at all.
+     * @see SimpleTag#setJspBody
+     */ 
+    @Override
+    public void setJspBody( JspFragment jspBody ) {
+        this.jspBody = jspBody;
+    }
+    
+    /**
+     * Returns the body passed in by the container via setJspBody.
+     *
+     * @return the fragment encapsulating the body of this tag, or
+     *    null if the action element is empty in the page.
+     */
+    protected JspFragment getJspBody() {
+        return this.jspBody;
+    }
+
+    /**
+     * Find the instance of a given class type that is closest to a given
+     * instance.
+     * This method uses the getParent method from the Tag and/or SimpleTag
+     * interfaces.  This method is used for coordination among 
+     * cooperating tags.
+     *
+     * <p> For every instance of TagAdapter
+     * encountered while traversing the ancestors, the tag handler returned by
+     * <tt>TagAdapter.getAdaptee()</tt> - instead of the TagAdpater itself -
+     * is compared to <tt>klass</tt>. If the tag handler matches, it - and
+     * not its TagAdapter - is returned.
+     *
+     * <p>
+     * The current version of the specification only provides one formal
+     * way of indicating the observable type of a tag handler: its
+     * tag handler implementation class, described in the tag-class
+     * subelement of the tag element.  This is extended in an
+     * informal manner by allowing the tag library author to
+     * indicate in the description subelement an observable type.
+     * The type should be a subtype of the tag handler implementation
+     * class or void.
+     * This additional constraint can be exploited by a
+     * specialized container that knows about that specific tag library,
+     * as in the case of the JSP standard tag library.
+     *
+     * <p>
+     * When a tag library author provides information on the
+     * observable type of a tag handler, client programmatic code
+     * should adhere to that constraint.  Specifically, the Class
+     * passed to findAncestorWithClass should be a subtype of the
+     * observable type.
+     * 
+     *
+     * @param from The instance from where to start looking.
+     * @param klass The subclass of JspTag or interface to be matched
+     * @return the nearest ancestor that implements the interface
+     * or is an instance of the class specified
+     */
+    public static final JspTag findAncestorWithClass(
+        JspTag from, Class<?> klass) 
+    {
+        boolean isInterface = false;
+
+        if (from == null || klass == null
+                || (!JspTag.class.isAssignableFrom(klass)
+                    && !(isInterface = klass.isInterface()))) {
+            return null;
+        }
+
+        for (;;) {
+            JspTag parent = null;
+            if( from instanceof SimpleTag ) {
+                parent = ((SimpleTag)from).getParent();
+            }
+            else if( from instanceof Tag ) {
+                parent = ((Tag)from).getParent();
+            }
+            if (parent == null) {
+                return null;
+            }
+
+            if (parent instanceof TagAdapter) {
+                parent = ((TagAdapter) parent).getAdaptee();
+            }
+
+            if ((isInterface && klass.isInstance(parent))
+                    || klass.isAssignableFrom(parent.getClass())) {
+                return parent;
+            }
+
+            from = parent;
+        }
+    }    
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/Tag.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/Tag.java
new file mode 100644
index 0000000..fdd3b07
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/Tag.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 javax.servlet.jsp.tagext;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.PageContext;
+
+
+/**
+ * The interface of a classic tag handler that does not want to manipulate 
+ * its body.  The Tag interface defines the basic protocol between a Tag 
+ * handler and JSP page implementation class.  It defines the life cycle 
+ * and the methods to be invoked at start and end tag.
+ *
+ * <p><B>Properties</B></p>
+ *
+ * <p>The Tag interface specifies the setter and getter methods for the core
+ * pageContext and parent properties.</p>
+ *
+ * <p>The JSP page implementation object invokes setPageContext and
+ * setParent, in that order, before invoking doStartTag() or doEndTag().</p>
+ *
+ * <p><B>Methods</B></p>
+ *
+ * <p>There are two main actions: doStartTag and doEndTag.  Once all
+ * appropriate properties have been initialized, the doStartTag and
+ * doEndTag methods can be invoked on the tag handler.  Between these
+ * invocations, the tag handler is assumed to hold a state that must
+ * be preserved.  After the doEndTag invocation, the tag handler is
+ * available for further invocations (and it is expected to have
+ * retained its properties).</p>
+ *
+ * <p><B>Lifecycle</B></p>
+ *
+ * <p>Lifecycle details are described by the transition diagram below,
+ * with the following comments:
+ * <ul>
+ * <li> [1] This transition is intended to be for releasing long-term data.
+ * no guarantees are assumed on whether any properties have been retained
+ * or not.
+ * <li> [2] This transition happens if and only if the tag ends normally
+ * without raising an exception
+ * <li> [3] Some setters may be called again before a tag handler is 
+ * reused.  For instance, <code>setParent()</code> is called if it's 
+ * reused within the same page but at a different level, 
+ * <code>setPageContext()</code> is called if it's used in another page, 
+ * and attribute setters are called if the values differ or are expressed 
+ * as request-time attribute values.
+ * <li> Check the TryCatchFinally interface for additional details related
+ * to exception handling and resource management.
+ * </ul></p>
+ *
+ * <IMG src="doc-files/TagProtocol.gif"
+ *      alt="Lifecycle Details Transition Diagram for Tag"/>
+ * 
+ * <p>Once all invocations on the tag handler
+ * are completed, the release method is invoked on it.  Once a release
+ * method is invoked <em>all</em> properties, including parent and
+ * pageContext, are assumed to have been reset to an unspecified value.
+ * The page compiler guarantees that release() will be invoked on the Tag
+ * handler before the handler is released to the GC.</p>
+ *
+ * <p><B>Empty and Non-Empty Action</B></p>
+ * <p>If the TagLibraryDescriptor file indicates that the action must
+ * always have an empty action, by an &lt;body-content&gt; entry of "empty",
+ * then the doStartTag() method must return SKIP_BODY.</p>
+ *
+ * <p>Otherwise, the doStartTag() method may return SKIP_BODY or
+ * EVAL_BODY_INCLUDE.</p>
+ *
+ * <p>If SKIP_BODY is returned the body, if present, is not evaluated.</p>
+ * 
+ * <p>If EVAL_BODY_INCLUDE is returned, the body is evaluated and
+ * "passed through" to the current out.</p>
+*/
+
+public interface Tag extends JspTag {
+
+    /**
+     * Skip body evaluation.
+     * Valid return value for doStartTag and doAfterBody.
+     */
+ 
+    public static final int SKIP_BODY = 0;
+ 
+    /**
+     * Evaluate body into existing out stream.
+     * Valid return value for doStartTag.
+     */
+ 
+    public static final int EVAL_BODY_INCLUDE = 1;
+
+    /**
+     * Skip the rest of the page.
+     * Valid return value for doEndTag.
+     */
+
+    public static final int SKIP_PAGE = 5;
+
+    /**
+     * Continue evaluating the page.
+     * Valid return value for doEndTag().
+     */
+
+    public static final int EVAL_PAGE = 6;
+
+    // Setters for Tag handler data
+
+
+    /**
+     * Set the current page context.
+     * This method is invoked by the JSP page implementation object
+     * prior to doStartTag().
+     * <p>
+     * This value is *not* reset by doEndTag() and must be explicitly reset
+     * by a page implementation if it changes between calls to doStartTag().
+     *
+     * @param pc The page context for this tag handler.
+     */
+
+    void setPageContext(PageContext pc);
+
+
+    /**
+     * Set the parent (closest enclosing tag handler) of this tag handler.
+     * Invoked by the JSP page implementation object prior to doStartTag().
+     * <p>
+     * This value is *not* reset by doEndTag() and must be explicitly reset
+     * by a page implementation.
+     *
+     * @param t The parent tag, or null.
+     */
+
+
+    void setParent(Tag t);
+
+
+    /**
+     * Get the parent (closest enclosing tag handler) for this tag handler.
+     *
+     * <p>
+     * The getParent() method can be used to navigate the nested tag
+     * handler structure at runtime for cooperation among custom actions;
+     * for example, the findAncestorWithClass() method in TagSupport
+     * provides a convenient way of doing this.
+     *
+     * <p>
+     * The current version of the specification only provides one formal
+     * way of indicating the observable type of a tag handler: its
+     * tag handler implementation class, described in the tag-class
+     * sub-element of the tag element.  This is extended in an
+     * informal manner by allowing the tag library author to
+     * indicate in the description sub-element an observable type.
+     * The type should be a sub-type of the tag handler implementation
+     * class or void.
+     * This additional constraint can be exploited by a
+     * specialized container that knows about that specific tag library,
+     * as in the case of the JSP standard tag library.
+     *
+     * @return the current parent, or null if none.
+     * @see TagSupport#findAncestorWithClass
+     */
+
+    Tag getParent();
+
+
+    // Actions for basic start/end processing.
+
+
+    /**
+     * Process the start tag for this instance.
+     * This method is invoked by the JSP page implementation object.
+     *
+     * <p>
+     * The doStartTag method assumes that the properties pageContext and
+     * parent have been set. It also assumes that any properties exposed as
+     * attributes have been set too.  When this method is invoked, the body
+     * has not yet been evaluated.
+     *
+     * <p>
+     * This method returns Tag.EVAL_BODY_INCLUDE or
+     * BodyTag.EVAL_BODY_BUFFERED to indicate
+     * that the body of the action should be evaluated or SKIP_BODY to
+     * indicate otherwise.
+     *
+     * <p>
+     * When a Tag returns EVAL_BODY_INCLUDE the result of evaluating
+     * the body (if any) is included into the current "out" JspWriter as it
+     * happens and then doEndTag() is invoked.
+     *
+     * <p>
+     * BodyTag.EVAL_BODY_BUFFERED is only valid  if the tag handler
+     * implements BodyTag.
+     *
+     * <p>
+     * The JSP container will resynchronize the values of any AT_BEGIN and
+     * NESTED variables (defined by the associated TagExtraInfo or TLD)
+     * after the invocation of doStartTag(), except for a tag handler
+     * implementing BodyTag whose doStartTag() method returns
+     * BodyTag.EVAL_BODY_BUFFERED.
+     *
+     * @return EVAL_BODY_INCLUDE if the tag wants to process body, SKIP_BODY 
+     *     if it does not want to process it.
+     * @throws JspException if an error occurred while processing this tag
+     * @see BodyTag
+     */
+ 
+    int doStartTag() throws JspException;
+ 
+
+    /**
+     * Process the end tag for this instance.
+     * This method is invoked by the JSP page implementation object
+     * on all Tag handlers.
+     *
+     * <p>
+     * This method will be called after returning from doStartTag. The
+     * body of the action may or may not have been evaluated, depending on
+     * the return value of doStartTag.
+     *
+     * <p>
+     * If this method returns EVAL_PAGE, the rest of the page continues
+     * to be evaluated.  If this method returns SKIP_PAGE, the rest of
+     * the page is not evaluated, the request is completed, and 
+     * the doEndTag() methods of enclosing tags are not invoked.  If this
+     * request was forwarded or included from another page (or Servlet),
+     * only the current page evaluation is stopped.
+     *
+     * <p>
+     * The JSP container will resynchronize the values of any AT_BEGIN and
+     * AT_END variables (defined by the associated TagExtraInfo or TLD)
+     * after the invocation of doEndTag().
+     *
+     * @return indication of whether to continue evaluating the JSP page.
+     * @throws JspException if an error occurred while processing this tag
+     */
+
+    int doEndTag() throws JspException;
+
+    /**
+     * Called on a Tag handler to release state.
+     * The page compiler guarantees that JSP page implementation
+     * objects will invoke this method on all tag handlers,
+     * but there may be multiple invocations on doStartTag and doEndTag in between.
+     */
+
+    void release();
+
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagAdapter.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagAdapter.java
new file mode 100644
index 0000000..3d2f19c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagAdapter.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.tagext;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.PageContext;
+
+/**
+ * Wraps any SimpleTag and exposes it using a Tag interface. This is used to
+ * allow collaboration between classic Tag handlers and SimpleTag handlers.
+ * <p>
+ * Because SimpleTag does not extend Tag, and because Tag.setParent() only
+ * accepts a Tag instance, a classic tag handler (one that implements Tag)
+ * cannot have a SimpleTag as its parent. To remedy this, a TagAdapter is
+ * created to wrap the SimpleTag parent, and the adapter is passed to
+ * setParent() instead. A classic Tag Handler can call getAdaptee() to retrieve
+ * the encapsulated SimpleTag instance.
+ * 
+ * @since 2.0
+ */
+public class TagAdapter implements Tag {
+    /** The simple tag that's being adapted. */
+    private SimpleTag simpleTagAdaptee;
+
+    /** The parent, of this tag, converted (if necessary) to be of type Tag. */
+    private Tag parent;
+
+    // Flag indicating whether we have already determined the parent
+    private boolean parentDetermined;
+
+    /**
+     * Creates a new TagAdapter that wraps the given SimpleTag and returns the
+     * parent tag when getParent() is called.
+     * 
+     * @param adaptee
+     *            The SimpleTag being adapted as a Tag.
+     */
+    public TagAdapter(SimpleTag adaptee) {
+        if (adaptee == null) {
+            // Cannot wrap a null adaptee.
+            throw new IllegalArgumentException();
+        }
+        this.simpleTagAdaptee = adaptee;
+    }
+
+    /**
+     * Must not be called.
+     * 
+     * @param pc
+     *            ignored.
+     * @throws UnsupportedOperationException
+     *             Must not be called
+     */
+    @Override
+    public void setPageContext(PageContext pc) {
+        throw new UnsupportedOperationException(
+                "Illegal to invoke setPageContext() on TagAdapter wrapper");
+    }
+
+    /**
+     * Must not be called. The parent of this tag is always
+     * getAdaptee().getParent().
+     * 
+     * @param parentTag
+     *            ignored.
+     * @throws UnsupportedOperationException
+     *             Must not be called.
+     */
+    @Override
+    public void setParent(Tag parentTag) {
+        throw new UnsupportedOperationException(
+                "Illegal to invoke setParent() on TagAdapter wrapper");
+    }
+
+    /**
+     * Returns the parent of this tag, which is always getAdaptee().getParent().
+     * This will either be the enclosing Tag (if getAdaptee().getParent()
+     * implements Tag), or an adapter to the enclosing Tag (if
+     * getAdaptee().getParent() does not implement Tag).
+     * 
+     * @return The parent of the tag being adapted.
+     */
+    @Override
+    public Tag getParent() {
+        if (!parentDetermined) {
+            JspTag adapteeParent = simpleTagAdaptee.getParent();
+            if (adapteeParent != null) {
+                if (adapteeParent instanceof Tag) {
+                    this.parent = (Tag) adapteeParent;
+                } else {
+                    // Must be SimpleTag - no other types defined.
+                    this.parent = new TagAdapter((SimpleTag) adapteeParent);
+                }
+            }
+            parentDetermined = true;
+        }
+
+        return this.parent;
+    }
+
+    /**
+     * Gets the tag that is being adapted to the Tag interface. This should be
+     * an instance of SimpleTag in JSP 2.0, but room is left for other kinds of
+     * tags in future spec versions.
+     * 
+     * @return the tag that is being adapted
+     */
+    public JspTag getAdaptee() {
+        return this.simpleTagAdaptee;
+    }
+
+    /**
+     * Must not be called.
+     * 
+     * @return always throws UnsupportedOperationException
+     * @throws UnsupportedOperationException
+     *             Must not be called
+     * @throws JspException
+     *             never thrown
+     */
+    @Override
+    public int doStartTag() throws JspException {
+        throw new UnsupportedOperationException(
+                "Illegal to invoke doStartTag() on TagAdapter wrapper");
+    }
+
+    /**
+     * Must not be called.
+     * 
+     * @return always throws UnsupportedOperationException
+     * @throws UnsupportedOperationException
+     *             Must not be called
+     * @throws JspException
+     *             never thrown
+     */
+    @Override
+    public int doEndTag() throws JspException {
+        throw new UnsupportedOperationException(
+                "Illegal to invoke doEndTag() on TagAdapter wrapper");
+    }
+
+    /**
+     * Must not be called.
+     * 
+     * @throws UnsupportedOperationException
+     *             Must not be called
+     */
+    @Override
+    public void release() {
+        throw new UnsupportedOperationException(
+                "Illegal to invoke release() on TagAdapter wrapper");
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagAttributeInfo.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagAttributeInfo.java
new file mode 100644
index 0000000..3888de2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagAttributeInfo.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 javax.servlet.jsp.tagext;
+
+/**
+ * Information on the attributes of a Tag, available at translation time. This
+ * class is instantiated from the Tag Library Descriptor file (TLD).
+ * 
+ * <p>
+ * Only the information needed to generate code is included here. Other
+ * information like SCHEMA for validation belongs elsewhere.
+ */
+
+public class TagAttributeInfo {
+    /**
+     * "id" is wired in to be ID. There is no real benefit in having it be
+     * something else IDREFs are not handled any differently.
+     */
+
+    public static final String ID = "id";
+
+    /**
+     * Constructor for TagAttributeInfo. This class is to be instantiated only
+     * from the TagLibrary code under request from some JSP code that is parsing
+     * a TLD (Tag Library Descriptor).
+     * 
+     * @param name
+     *            The name of the attribute.
+     * @param required
+     *            If this attribute is required in tag instances.
+     * @param type
+     *            The name of the type of the attribute.
+     * @param reqTime
+     *            Whether this attribute holds a request-time Attribute.
+     */
+
+    public TagAttributeInfo(String name, boolean required, String type,
+            boolean reqTime) {
+        this(name, required, type, reqTime, false);
+    }
+
+    /**
+     * JSP 2.0 Constructor for TagAttributeInfo. This class is to be
+     * instantiated only from the TagLibrary code under request from some JSP
+     * code that is parsing a TLD (Tag Library Descriptor).
+     * 
+     * @param name
+     *            The name of the attribute.
+     * @param required
+     *            If this attribute is required in tag instances.
+     * @param type
+     *            The name of the type of the attribute.
+     * @param reqTime
+     *            Whether this attribute holds a request-time Attribute.
+     * @param fragment
+     *            Whether this attribute is of type JspFragment
+     * 
+     * @since 2.0
+     */
+
+    public TagAttributeInfo(String name, boolean required, String type,
+            boolean reqTime, boolean fragment) {
+        this(name, required, type, reqTime, fragment, null, false, false, null, null);
+    }
+
+    /**
+     * @since JSP 2.1
+     */
+    public TagAttributeInfo(String name, boolean required, String type,
+            boolean reqTime, boolean fragment, String description,
+            boolean deferredValue, boolean deferredMethod,
+            String expectedTypeName, String methodSignature) {
+        this.name = name;
+        this.required = required;
+        this.type = type;
+        this.reqTime = reqTime;
+        this.fragment = fragment;
+        this.description = description;
+        this.deferredValue = deferredValue;
+        this.deferredMethod = deferredMethod;
+        this.expectedTypeName = expectedTypeName;
+        this.methodSignature = methodSignature;
+    }
+
+    /**
+     * The name of this attribute.
+     * 
+     * @return the name of the attribute
+     */
+
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * The type (as a String) of this attribute.
+     * 
+     * @return the type of the attribute
+     */
+
+    public String getTypeName() {
+        return type;
+    }
+
+    /**
+     * Whether this attribute can hold a request-time value.
+     * 
+     * @return if the attribute can hold a request-time value.
+     */
+
+    public boolean canBeRequestTime() {
+        return reqTime;
+    }
+
+    /**
+     * Whether this attribute is required.
+     * 
+     * @return if the attribute is required.
+     */
+    public boolean isRequired() {
+        return required;
+    }
+
+    /**
+     * Convenience static method that goes through an array of TagAttributeInfo
+     * objects and looks for "id".
+     * 
+     * @param a
+     *            An array of TagAttributeInfo
+     * @return The TagAttributeInfo reference with name "id"
+     */
+    public static TagAttributeInfo getIdAttribute(TagAttributeInfo a[]) {
+        for (int i = 0; i < a.length; i++) {
+            if (a[i].getName().equals(ID)) {
+                return a[i];
+            }
+        }
+        return null; // no such attribute
+    }
+
+    /**
+     * Whether this attribute is of type JspFragment.
+     * 
+     * @return if the attribute is of type JspFragment
+     * 
+     * @since 2.0
+     */
+    public boolean isFragment() {
+        return fragment;
+    }
+
+    /**
+     * Returns a String representation of this TagAttributeInfo, suitable for
+     * debugging purposes.
+     * 
+     * @return a String representation of this TagAttributeInfo
+     */
+    @Override
+    public String toString() {
+        StringBuilder b = new StringBuilder(64);
+        b.append("name = " + name + " ");
+        b.append("type = " + type + " ");
+        b.append("reqTime = " + reqTime + " ");
+        b.append("required = " + required + " ");
+        b.append("fragment = " + fragment + " ");
+        b.append("deferredValue = " + deferredValue + " ");
+        b.append("expectedTypeName = " + expectedTypeName + " ");
+        b.append("deferredMethod = " + deferredMethod + " ");
+        b.append("methodSignature = " + methodSignature);
+        return b.toString();
+    }
+
+    /*
+     * private fields
+     */
+    private final String name;
+
+    private final String type;
+
+    private final boolean reqTime;
+
+    private final boolean required;
+
+    /*
+     * private fields for JSP 2.0
+     */
+    private final boolean fragment;
+
+    /*
+     * private fields for JSP 2.1
+     */
+    private final String description;
+
+    private final boolean deferredValue;
+
+    private final boolean deferredMethod;
+
+    private final String expectedTypeName;
+
+    private final String methodSignature;
+
+    public boolean isDeferredMethod() {
+        return deferredMethod;
+    }
+
+    public boolean isDeferredValue() {
+        return deferredValue;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public String getExpectedTypeName() {
+        return expectedTypeName;
+    }
+
+    public String getMethodSignature() {
+        return methodSignature;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagData.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagData.java
new file mode 100644
index 0000000..72908a4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagData.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 javax.servlet.jsp.tagext;
+
+import java.util.Hashtable;
+
+/**
+ * The (translation-time only) attribute/value information for a tag instance.
+ *
+ * <p>
+ * TagData is only used as an argument to the isValid, validate, and 
+ * getVariableInfo methods of TagExtraInfo, which are invoked at 
+ * translation time.
+ */
+
+public class TagData implements Cloneable {
+
+    /**
+     * Distinguished value for an attribute to indicate its value
+     * is a request-time expression (which is not yet available because
+     * TagData instances are used at translation-time).
+     */
+
+    public static final Object REQUEST_TIME_VALUE = new Object();
+
+
+    /**
+     * Constructor for TagData.
+     *
+     * <p>
+     * A typical constructor may be
+     * <pre>
+     * static final Object[][] att = {{"connection", "conn0"}, {"id", "query0"}};
+     * static final TagData td = new TagData(att);
+     * </pre>
+     *
+     * All values must be Strings except for those holding the
+     * distinguished object REQUEST_TIME_VALUE.
+
+     * @param atts the static attribute and values.  May be null.
+     */
+    public TagData(Object[] atts[]) {
+        if (atts == null) {
+            attributes = new Hashtable<String, Object>();
+        } else {
+            attributes = new Hashtable<String, Object>(atts.length);
+        }
+
+        if (atts != null) {
+            for (int i = 0; i < atts.length; i++) {
+                attributes.put((String) atts[i][0], atts[i][1]);
+            }
+        }
+    }
+
+    /**
+     * Constructor for a TagData.
+     *
+     * If you already have the attributes in a hashtable, use this
+     * constructor. 
+     *
+     * @param attrs A hashtable to get the values from.
+     */
+    public TagData(Hashtable<String, Object> attrs) {
+        this.attributes = attrs;
+    }
+
+    /**
+     * The value of the tag's id attribute.
+     *
+     * @return the value of the tag's id attribute, or null if no such
+     *     attribute was specified.
+     */
+
+    public String getId() {
+        return getAttributeString(TagAttributeInfo.ID);
+    }
+
+    /**
+     * The value of the attribute.
+     * If a static value is specified for an attribute that accepts a
+     * request-time attribute expression then that static value is returned,
+     * even if the value is provided in the body of a <jsp:attribute> action.
+     * The distinguished object REQUEST_TIME_VALUE is only returned if
+     * the value is specified as a request-time attribute expression
+     * or via the &lt;jsp:attribute&gt; action with a body that contains
+     * dynamic content (scriptlets, scripting expressions, EL expressions, 
+     * standard actions, or custom actions).  Returns null if the attribute 
+     * is not set. 
+     *
+     * @param attName the name of the attribute
+     * @return the attribute's value
+     */
+
+    public Object getAttribute(String attName) {
+        return attributes.get(attName);
+    }
+
+    /**
+     * Set the value of an attribute.
+     *
+     * @param attName the name of the attribute
+     * @param value the value.
+     */
+    public void setAttribute(String attName,
+                             Object value) {
+        attributes.put(attName, value);
+    }
+
+    /**
+     * Get the value for a given attribute.
+     *
+     * @param attName the name of the attribute
+     * @return the attribute value string
+     * @throws ClassCastException if attribute value is not a String
+     */
+
+    public String getAttributeString(String attName) {
+        Object o = attributes.get(attName);
+        if (o == null) {
+            return null;
+        }
+        return (String) o;
+    }
+
+    /**
+     * Enumerates the attributes.
+     *
+     *@return An enumeration of the attributes in a TagData
+     */
+    public java.util.Enumeration<String> getAttributes() {
+        return attributes.keys();
+    }
+
+    // private data
+
+    private final Hashtable<String, Object> attributes;        // the tagname/value map
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagExtraInfo.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagExtraInfo.java
new file mode 100644
index 0000000..b6c298a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagExtraInfo.java
@@ -0,0 +1,145 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES 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.tagext;
+
+/**
+ * Optional class provided by the tag library author to describe additional
+ * translation-time information not described in the TLD.
+ * The TagExtraInfo class is mentioned in the Tag Library Descriptor file (TLD).
+ *
+ * <p>
+ * This class can be used:
+ * <ul>
+ * <li> to indicate that the tag defines scripting variables
+ * <li> to perform translation-time validation of the tag attributes.
+ * </ul>
+ *
+ * <p>
+ * It is the responsibility of the JSP translator that the initial value
+ * to be returned by calls to getTagInfo() corresponds to a TagInfo
+ * object for the tag being translated. If an explicit call to
+ * setTagInfo() is done, then the object passed will be returned in
+ * subsequent calls to getTagInfo().
+ * 
+ * <p>
+ * The only way to affect the value returned by getTagInfo()
+ * is through a setTagInfo() call, and thus, TagExtraInfo.setTagInfo() is
+ * to be called by the JSP translator, with a TagInfo object that
+ * corresponds to the tag being translated. The call should happen before
+ * any invocation on validate() and before any invocation on
+ * getVariableInfo().
+ *
+ * <p>
+ * <tt>NOTE:</tt> It is a (translation time) error for a tag definition
+ * in a TLD with one or more variable subelements to have an associated
+ * TagExtraInfo implementation that returns a VariableInfo array with
+ * one or more elements from a call to getVariableInfo().
+ */
+
+public abstract class TagExtraInfo {
+
+    /**
+     * Sole constructor. (For invocation by subclass constructors, 
+     * typically implicit.)
+     */
+    public TagExtraInfo() {
+        // NOOP by default
+    }
+    
+    /**
+     * information on scripting variables defined by the tag associated with
+     * this TagExtraInfo instance.
+     * Request-time attributes are indicated as such in the TagData parameter.
+     *
+     * @param data The TagData instance.
+     * @return An array of VariableInfo data, or null or a zero length array
+     *         if no scripting variables are to be defined.
+     */
+    public VariableInfo[] getVariableInfo(TagData data) {
+        return ZERO_VARIABLE_INFO;
+    }
+
+    /**
+     * Translation-time validation of the attributes. 
+     * Request-time attributes are indicated as such in the TagData parameter.
+     * Note that the preferred way to do validation is with the validate()
+     * method, since it can return more detailed information.
+     *
+     * @param data The TagData instance.
+     * @return Whether this tag instance is valid.
+     * @see TagExtraInfo#validate
+     */
+
+    public boolean isValid(TagData data) {
+        return true;
+    }
+
+    /**
+     * Translation-time validation of the attributes.
+     * Request-time attributes are indicated as such in the TagData parameter.
+     * Because of the higher quality validation messages possible, 
+     * this is the preferred way to do validation (although isValid() 
+     * still works).  
+     * 
+     * <p>JSP 2.0 and higher containers call validate() instead of isValid().
+     * The default implementation of this method is to call isValid().  If 
+     * isValid() returns false, a generic ValidationMessage[] is returned
+     * indicating isValid() returned false.</p>
+     *
+     * @param data The TagData instance.
+     * @return A null object, or zero length array if no errors, an 
+     *     array of ValidationMessages otherwise.
+     * @since 2.0
+     */
+    public ValidationMessage[] validate( TagData data ) {
+        ValidationMessage[] result = null;
+
+        if( !isValid( data ) ) {
+            result = new ValidationMessage[] {
+                new ValidationMessage( data.getId(), "isValid() == false" ) };
+        }
+
+        return result;
+    }
+
+    /**
+     * Set the TagInfo for this class.
+     *
+     * @param tagInfo The TagInfo this instance is extending
+     */
+    public final void setTagInfo(TagInfo tagInfo) {
+        this.tagInfo = tagInfo;
+    }
+
+    /**
+     * Get the TagInfo for this class.
+     *
+     * @return the taginfo instance this instance is extending
+     */
+    public final TagInfo getTagInfo() {
+        return tagInfo;
+    }
+    
+    // private data
+    private  TagInfo tagInfo;
+
+    // zero length VariableInfo array
+    private static final VariableInfo[] ZERO_VARIABLE_INFO = { };
+}
+
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagFileInfo.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagFileInfo.java
new file mode 100644
index 0000000..e825c91
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagFileInfo.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 javax.servlet.jsp.tagext;
+
+/**
+ * Tag information for a tag file in a Tag Library;
+ * This class is instantiated from the Tag Library Descriptor file (TLD)
+ * and is available only at translation time.
+ *
+ * @since 2.0
+ */
+public class TagFileInfo {
+
+    /**
+     * Constructor for TagFileInfo from data in the JSP 2.0 format for TLD.
+     * This class is to be instantiated only from the TagLibrary code
+     * under request from some JSP code that is parsing a
+     * TLD (Tag Library Descriptor).
+     *
+     * Note that, since TagLibraryInfo reflects both TLD information
+     * and taglib directive information, a TagFileInfo instance is
+     * dependent on a taglib directive.  This is probably a
+     * design error, which may be fixed in the future.
+     *
+     * @param name The unique action name of this tag
+     * @param path Where to find the .tag file implementing this 
+     *     action, relative to the location of the TLD file.
+     * @param tagInfo The detailed information about this tag, as parsed
+     *     from the directives in the tag file.
+     */
+    public TagFileInfo( String name, String path, TagInfo tagInfo ) {
+        this.name = name;
+        this.path = path;
+        this.tagInfo = tagInfo;
+    }
+
+    /**
+     * The unique action name of this tag.
+     *
+     * @return The (short) name of the tag.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Where to find the .tag file implementing this action.
+     *
+     * @return The path of the tag file, relative to the TLD, or "." if 
+     *     the tag file was defined in an implicit tag file.
+     */
+    public String getPath() {
+        return path;
+    }
+
+    /**
+     * Returns information about this tag, parsed from the directives 
+     * in the tag file.
+     *
+     * @return a TagInfo object containing information about this tag
+     */
+    public TagInfo getTagInfo() {
+        return tagInfo;
+    }
+
+    // private fields for 2.0 info
+    private final String name;
+    private final String path;
+    private final TagInfo tagInfo;
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagInfo.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagInfo.java
new file mode 100644
index 0000000..fcafa2b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagInfo.java
@@ -0,0 +1,447 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES 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.tagext;
+
+/**
+ * Tag information for a tag in a Tag Library;
+ * This class is instantiated from the Tag Library Descriptor file (TLD)
+ * and is available only at translation time.
+ *
+ * 
+*/
+
+public class TagInfo {
+
+    /**
+     * Static constant for getBodyContent() when it is JSP.
+     */
+
+    public static final String BODY_CONTENT_JSP = "JSP";
+
+    /**
+     * Static constant for getBodyContent() when it is Tag dependent.
+     */
+
+    public static final String BODY_CONTENT_TAG_DEPENDENT = "tagdependent";
+
+
+    /**
+     * Static constant for getBodyContent() when it is empty.
+     */
+
+    public static final String BODY_CONTENT_EMPTY = "empty";
+    
+    /**
+     * Static constant for getBodyContent() when it is scriptless.
+     * 
+     * @since 2.0
+     */ 
+    public static final String BODY_CONTENT_SCRIPTLESS = "scriptless";
+
+    /**
+     * Constructor for TagInfo from data in the JSP 1.1 format for TLD.
+     * This class is to be instantiated only from the TagLibrary code
+     * under request from some JSP code that is parsing a
+     * TLD (Tag Library Descriptor).
+     *
+     * Note that, since TagLibibraryInfo reflects both TLD information
+     * and taglib directive information, a TagInfo instance is
+     * dependent on a taglib directive.  This is probably a
+     * design error, which may be fixed in the future.
+     *
+     * @param tagName The name of this tag
+     * @param tagClassName The name of the tag handler class
+     * @param bodycontent Information on the body content of these tags
+     * @param infoString The (optional) string information for this tag
+     * @param taglib The instance of the tag library that contains us.
+     * @param tagExtraInfo The instance providing extra Tag info.  May be null
+     * @param attributeInfo An array of AttributeInfo data from descriptor.
+     * May be null;
+     *
+     */
+    public TagInfo(String tagName,
+            String tagClassName,
+            String bodycontent,
+            String infoString,
+            TagLibraryInfo taglib,
+            TagExtraInfo tagExtraInfo,
+            TagAttributeInfo[] attributeInfo) {
+        this.tagName       = tagName;
+        this.tagClassName  = tagClassName;
+        this.bodyContent   = bodycontent;
+        this.infoString    = infoString;
+        this.tagLibrary    = taglib;
+        this.tagExtraInfo  = tagExtraInfo;
+        this.attributeInfo = attributeInfo;
+
+        if (tagExtraInfo != null)
+            tagExtraInfo.setTagInfo(this);
+    }
+                         
+    /**
+     * Constructor for TagInfo from data in the JSP 1.2 format for TLD.
+     * This class is to be instantiated only from the TagLibrary code
+     * under request from some JSP code that is parsing a
+     * TLD (Tag Library Descriptor).
+     *
+     * Note that, since TagLibibraryInfo reflects both TLD information
+     * and taglib directive information, a TagInfo instance is
+     * dependent on a taglib directive.  This is probably a
+     * design error, which may be fixed in the future.
+     *
+     * @param tagName The name of this tag
+     * @param tagClassName The name of the tag handler class
+     * @param bodycontent Information on the body content of these tags
+     * @param infoString The (optional) string information for this tag
+     * @param taglib The instance of the tag library that contains us.
+     * @param tagExtraInfo The instance providing extra Tag info.  May be null
+     * @param attributeInfo An array of AttributeInfo data from descriptor.
+     * May be null;
+     * @param displayName A short name to be displayed by tools
+     * @param smallIcon Path to a small icon to be displayed by tools
+     * @param largeIcon Path to a large icon to be displayed by tools
+     * @param tvi An array of a TagVariableInfo (or null)
+     */
+    public TagInfo(String tagName,
+            String tagClassName,
+            String bodycontent,
+            String infoString,
+            TagLibraryInfo taglib,
+            TagExtraInfo tagExtraInfo,
+            TagAttributeInfo[] attributeInfo,
+            String displayName,
+            String smallIcon,
+            String largeIcon,
+            TagVariableInfo[] tvi) {
+        this.tagName       = tagName;
+        this.tagClassName  = tagClassName;
+        this.bodyContent   = bodycontent;
+        this.infoString    = infoString;
+        this.tagLibrary    = taglib;
+        this.tagExtraInfo  = tagExtraInfo;
+        this.attributeInfo = attributeInfo;
+        this.displayName = displayName;
+        this.smallIcon = smallIcon;
+        this.largeIcon = largeIcon;
+        this.tagVariableInfo = tvi;
+
+        if (tagExtraInfo != null)
+            tagExtraInfo.setTagInfo(this);
+    }
+                         
+    /**
+     * Constructor for TagInfo from data in the JSP 2.0 format for TLD.
+     * This class is to be instantiated only from the TagLibrary code
+     * under request from some JSP code that is parsing a
+     * TLD (Tag Library Descriptor).
+     *
+     * Note that, since TagLibibraryInfo reflects both TLD information
+     * and taglib directive information, a TagInfo instance is
+     * dependent on a taglib directive.  This is probably a
+     * design error, which may be fixed in the future.
+     *
+     * @param tagName The name of this tag
+     * @param tagClassName The name of the tag handler class
+     * @param bodycontent Information on the body content of these tags
+     * @param infoString The (optional) string information for this tag
+     * @param taglib The instance of the tag library that contains us.
+     * @param tagExtraInfo The instance providing extra Tag info.  May be null
+     * @param attributeInfo An array of AttributeInfo data from descriptor.
+     * May be null;
+     * @param displayName A short name to be displayed by tools
+     * @param smallIcon Path to a small icon to be displayed by tools
+     * @param largeIcon Path to a large icon to be displayed by tools
+     * @param tvi An array of a TagVariableInfo (or null)
+     * @param dynamicAttributes True if supports dynamic attributes
+     *
+     * @since 2.0
+     */
+    public TagInfo(String tagName,
+            String tagClassName,
+            String bodycontent,
+            String infoString,
+            TagLibraryInfo taglib,
+            TagExtraInfo tagExtraInfo,
+            TagAttributeInfo[] attributeInfo,
+            String displayName,
+            String smallIcon,
+            String largeIcon,
+            TagVariableInfo[] tvi,
+            boolean dynamicAttributes) {
+        this.tagName       = tagName;
+        this.tagClassName  = tagClassName;
+        this.bodyContent   = bodycontent;
+        this.infoString    = infoString;
+        this.tagLibrary    = taglib;
+        this.tagExtraInfo  = tagExtraInfo;
+        this.attributeInfo = attributeInfo;
+        this.displayName = displayName;
+        this.smallIcon = smallIcon;
+        this.largeIcon = largeIcon;
+        this.tagVariableInfo = tvi;
+        this.dynamicAttributes = dynamicAttributes;
+
+        if (tagExtraInfo != null)
+            tagExtraInfo.setTagInfo(this);
+    }
+
+    /**
+     * The name of the Tag.
+     *
+     * @return The (short) name of the tag.
+     */
+
+    public String getTagName() {
+        return tagName;
+    }
+
+    /**
+     * Attribute information (in the TLD) on this tag.
+     * The return is an array describing the attributes of this tag, as
+     * indicated in the TLD.
+     *
+     * @return The array of TagAttributeInfo for this tag, or a
+     *         zero-length array if the tag has no attributes.
+     */
+
+   public TagAttributeInfo[] getAttributes() {
+       return attributeInfo;
+   }
+
+    /**
+     * Information on the scripting objects created by this tag at runtime.
+     * This is a convenience method on the associated TagExtraInfo class.
+     *
+     * @param data TagData describing this action.
+     * @return if a TagExtraInfo object is associated with this TagInfo, the
+     *     result of getTagExtraInfo().getVariableInfo( data ), otherwise
+     *     null.
+     */
+   public VariableInfo[] getVariableInfo(TagData data) {
+       VariableInfo[] result = null;
+       TagExtraInfo tei = getTagExtraInfo();
+       if (tei != null) {
+           result = tei.getVariableInfo( data );
+       }
+       return result;
+   }
+
+    /**
+     * Translation-time validation of the attributes. 
+     * This is a convenience method on the associated TagExtraInfo class.
+     *
+     * @param data The translation-time TagData instance.
+     * @return Whether the data is valid.
+     */
+    public boolean isValid(TagData data) {
+        TagExtraInfo tei = getTagExtraInfo();
+        if (tei == null) {
+            return true;
+        }
+        return tei.isValid(data);
+    }
+
+    /**
+     * Translation-time validation of the attributes.
+     * This is a convenience method on the associated TagExtraInfo class.
+     *
+     * @param data The translation-time TagData instance.
+     * @return A null object, or zero length array if no errors, an
+     *     array of ValidationMessages otherwise.
+     * @since 2.0
+     */
+    public ValidationMessage[] validate( TagData data ) {
+        TagExtraInfo tei = getTagExtraInfo();
+        if( tei == null ) {
+            return null;
+        }
+        return tei.validate( data );
+    }
+
+    /**
+     * Set the instance for extra tag information.
+     * 
+     * @param tei the TagExtraInfo instance
+     */
+    public void setTagExtraInfo(TagExtraInfo tei) {
+        tagExtraInfo = tei;
+    }
+
+
+    /**
+     * The instance (if any) for extra tag information.
+     * 
+     * @return The TagExtraInfo instance, if any.
+     */
+    public TagExtraInfo getTagExtraInfo() {
+        return tagExtraInfo;
+    }
+
+
+    /**
+     * Name of the class that provides the handler for this tag.
+     *
+     * @return The name of the tag handler class.
+     */
+    
+    public String getTagClassName() {
+        return tagClassName;
+    }
+
+
+    /**
+     * The bodycontent information for this tag.
+     * If the bodycontent is not defined for this
+     * tag, the default of JSP will be returned.
+     *
+     * @return the body content string.
+     */
+
+    public String getBodyContent() {
+        return bodyContent;
+    }
+
+
+    /**
+     * The information string for the tag.
+     *
+     * @return the info string, or null if 
+     *         not defined
+     */
+
+    public String getInfoString() {
+        return infoString;
+    }
+
+
+    /**
+     * Set the TagLibraryInfo property.
+     *
+     * Note that a TagLibraryInfo element is dependent
+     * not just on the TLD information but also on the
+     * specific taglib instance used.  This means that
+     * a fair amount of work needs to be done to construct
+     * and initialize TagLib objects.
+     *
+     * If used carefully, this setter can be used to avoid having to
+     * create new TagInfo elements for each taglib directive.
+     *
+     * @param tl the TagLibraryInfo to assign
+     */
+
+    public void setTagLibrary(TagLibraryInfo tl) {
+        tagLibrary = tl;
+    }
+
+    /**
+     * The instance of TabLibraryInfo we belong to.
+     *
+     * @return the tag library instance we belong to
+     */
+
+    public TagLibraryInfo getTagLibrary() {
+        return tagLibrary;
+    }
+
+
+    // ============== JSP 2.0 TLD Information ========
+
+
+    /**
+     * Get the displayName.
+     *
+     * @return A short name to be displayed by tools,
+     *         or null if not defined
+     */
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    /**
+     * Get the path to the small icon.
+     *
+     * @return Path to a small icon to be displayed by tools,
+     *         or null if not defined
+     */
+
+    public String getSmallIcon() {
+        return smallIcon;
+    }
+
+    /**
+     * Get the path to the large icon.
+     *
+     * @return Path to a large icon to be displayed by tools,
+     *         or null if not defined
+     */
+
+    public String getLargeIcon() {
+        return largeIcon;
+    }
+
+    /**
+     * Get TagVariableInfo objects associated with this TagInfo.
+     *
+     * @return Array of TagVariableInfo objects corresponding to
+     *         variables declared by this tag, or a zero length
+     *         array if no variables have been declared
+     */
+
+    public TagVariableInfo[] getTagVariableInfos() {
+        return tagVariableInfo;
+    }
+
+
+    // ============== JSP 2.0 TLD Information ========
+
+    /**
+     * Get dynamicAttributes associated with this TagInfo.
+     *
+     * @return True if tag handler supports dynamic attributes
+     * @since 2.0
+     */
+    public boolean hasDynamicAttributes() {
+        return dynamicAttributes;
+    }
+
+    /*
+     * private fields for 1.1 info
+     */
+    private String             tagName; // the name of the tag
+    private String             tagClassName;
+    private String             bodyContent;
+    private String             infoString;
+    private TagLibraryInfo     tagLibrary;
+    private TagExtraInfo       tagExtraInfo; // instance of TagExtraInfo
+    private TagAttributeInfo[] attributeInfo;
+
+    /*
+     * private fields for 1.2 info
+     */
+    private String             displayName;
+    private String             smallIcon;
+    private String             largeIcon;
+    private TagVariableInfo[]  tagVariableInfo;
+
+    /*
+     * Additional private fields for 2.0 info
+     */
+    private boolean dynamicAttributes;
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagLibraryInfo.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagLibraryInfo.java
new file mode 100644
index 0000000..ac9720d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagLibraryInfo.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.jsp.tagext;
+
+
+/**
+ * Translation-time information associated with a taglib directive, and its
+ * underlying TLD file. Most of the information is directly from the TLD, except
+ * for the prefix and the uri values used in the taglib directive
+ */
+public abstract class TagLibraryInfo {
+
+    /**
+     * Constructor. This will invoke the constructors for TagInfo, and
+     * TagAttributeInfo after parsing the TLD file.
+     * 
+     * @param prefix
+     *            the prefix actually used by the taglib directive
+     * @param uri
+     *            the URI actually used by the taglib directive
+     */
+    protected TagLibraryInfo(String prefix, String uri) {
+        this.prefix = prefix;
+        this.uri = uri;
+    }
+
+    // ==== methods accessing taglib information =======
+
+    /**
+     * The value of the uri attribute from the taglib directive for this
+     * library.
+     * 
+     * @return the value of the uri attribute
+     */
+    public String getURI() {
+        return uri;
+    }
+
+    /**
+     * The prefix assigned to this taglib from the taglib directive
+     * 
+     * @return the prefix assigned to this taglib from the taglib directive
+     */
+    public String getPrefixString() {
+        return prefix;
+    }
+
+    // ==== methods using the TLD data =======
+
+    /**
+     * The preferred short name (prefix) as indicated in the TLD. This may be
+     * used by authoring tools as the preferred prefix to use when creating an
+     * taglib directive for this library.
+     * 
+     * @return the preferred short name for the library
+     */
+    public String getShortName() {
+        return shortname;
+    }
+
+    /**
+     * The "reliable" URN indicated in the TLD (the uri element). This may be
+     * used by authoring tools as a global identifier to use when creating a
+     * taglib directive for this library.
+     * 
+     * @return a reliable URN to a TLD like this
+     */
+    public String getReliableURN() {
+        return urn;
+    }
+
+    /**
+     * Information (documentation) for this TLD.
+     * 
+     * @return the info string for this tag lib
+     */
+    public String getInfoString() {
+        return info;
+    }
+
+    /**
+     * A string describing the required version of the JSP container.
+     * 
+     * @return the (minimal) required version of the JSP container.
+     * @see javax.servlet.jsp.JspEngineInfo
+     */
+    public String getRequiredVersion() {
+        return jspversion;
+    }
+
+    /**
+     * An array describing the tags that are defined in this tag library.
+     * 
+     * @return the TagInfo objects corresponding to the tags defined by this tag
+     *         library, or a zero length array if this tag library defines no
+     *         tags
+     */
+    public TagInfo[] getTags() {
+        return tags;
+    }
+
+    /**
+     * An array describing the tag files that are defined in this tag library.
+     * 
+     * @return the TagFileInfo objects corresponding to the tag files defined by
+     *         this tag library, or a zero length array if this tag library
+     *         defines no tags files
+     * @since 2.0
+     */
+    public TagFileInfo[] getTagFiles() {
+        return tagFiles;
+    }
+
+    /**
+     * Get the TagInfo for a given tag name, looking through all the tags in
+     * this tag library.
+     * 
+     * @param shortname
+     *            The short name (no prefix) of the tag
+     * @return the TagInfo for the tag with the specified short name, or null if
+     *         no such tag is found
+     */
+    public TagInfo getTag(String shortname) {
+        TagInfo tags[] = getTags();
+
+        if (tags == null || tags.length == 0 || shortname == null) {
+            return null;
+        }
+
+        for (int i = 0; i < tags.length; i++) {
+            if (shortname.equals(tags[i].getTagName())) {
+                return tags[i];
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get the TagFileInfo for a given tag name, looking through all the tag
+     * files in this tag library.
+     * 
+     * @param shortname
+     *            The short name (no prefix) of the tag
+     * @return the TagFileInfo for the specified Tag file, or null if no Tag
+     *         file is found
+     * @since 2.0
+     */
+    public TagFileInfo getTagFile(String shortname) {
+        TagFileInfo tagFiles[] = getTagFiles();
+
+        if (tagFiles == null || tagFiles.length == 0) {
+            return null;
+        }
+
+        for (int i = 0; i < tagFiles.length; i++) {
+            if (tagFiles[i].getName().equals(shortname)) {
+                return tagFiles[i];
+            }
+        }
+        return null;
+    }
+
+    /**
+     * An array describing the functions that are defined in this tag library.
+     * 
+     * @return the functions defined in this tag library, or a zero length array
+     *         if the tag library defines no functions.
+     * @since 2.0
+     */
+    public FunctionInfo[] getFunctions() {
+        return functions;
+    }
+
+    /**
+     * Get the FunctionInfo for a given function name, looking through all the
+     * functions in this tag library.
+     * 
+     * @param name
+     *            The name (no prefix) of the function
+     * @return the FunctionInfo for the function with the given name, or null if
+     *         no such function exists
+     * @since 2.0
+     */
+    public FunctionInfo getFunction(String name) {
+
+        if (functions == null || functions.length == 0) {
+            System.err.println("No functions");
+            return null;
+        }
+
+        for (int i = 0; i < functions.length; i++) {
+            if (functions[i].getName().equals(name)) {
+                return functions[i];
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns an array of TagLibraryInfo objects representing the entire set of
+     * tag libraries (including this TagLibraryInfo) imported by taglib
+     * directives in the translation unit that references this TagLibraryInfo.
+     * If a tag library is imported more than once and bound to different
+     * prefixes, only the TagLibraryInfo bound to the first prefix must be
+     * included in the returned array.
+     * 
+     * @return Array of TagLibraryInfo objects representing the entire set of
+     *         tag libraries (including this TagLibraryInfo) imported by taglib
+     *         directives in the translation unit that references this
+     *         TagLibraryInfo.
+     * @since 2.1
+     */
+    public abstract javax.servlet.jsp.tagext.TagLibraryInfo[] getTagLibraryInfos();
+
+    // Protected fields
+
+    /**
+     * The prefix assigned to this taglib from the taglib directive.
+     */
+    protected String prefix;
+
+    /**
+     * The value of the uri attribute from the taglib directive for this
+     * library.
+     */
+    protected String uri;
+
+    /**
+     * An array describing the tags that are defined in this tag library.
+     */
+    protected TagInfo[] tags;
+
+    /**
+     * An array describing the tag files that are defined in this tag library.
+     * 
+     * @since 2.0
+     */
+    protected TagFileInfo[] tagFiles;
+
+    /**
+     * An array describing the functions that are defined in this tag library.
+     * 
+     * @since 2.0
+     */
+    protected FunctionInfo[] functions;
+
+    // Tag Library Data
+
+    /**
+     * The version of the tag library.
+     */
+    protected String tlibversion; // required
+
+    /**
+     * The version of the JSP specification this tag library is written to.
+     */
+    protected String jspversion; // required
+
+    /**
+     * The preferred short name (prefix) as indicated in the TLD.
+     */
+    protected String shortname; // required
+
+    /**
+     * The "reliable" URN indicated in the TLD.
+     */
+    protected String urn; // required
+
+    /**
+     * Information (documentation) for this TLD.
+     */
+    protected String info; // optional
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagLibraryValidator.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagLibraryValidator.java
new file mode 100644
index 0000000..632c89b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagLibraryValidator.java
@@ -0,0 +1,144 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES 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.tagext;
+
+import java.util.Map;
+
+/**
+ * Translation-time validator class for a JSP page. 
+ * A validator operates on the XML view associated with the JSP page.
+ *
+ * <p>
+ * The TLD file associates a TagLibraryValidator class and some init
+ * arguments with a tag library.
+ *
+ * <p>
+ * The JSP container is responsible for locating an appropriate
+ * instance of the appropriate subclass by
+ *
+ * <ul>
+ * <li> new a fresh instance, or reuse an available one
+ * <li> invoke the setInitParams(Map) method on the instance
+ * </ul>
+ *
+ * once initialized, the validate(String, String, PageData) method will
+ * be invoked, where the first two arguments are the prefix
+ * and uri for this tag library in the XML View.  The prefix is intended
+ * to make it easier to produce an error message.  However, it is not
+ * always accurate.  In the case where a single URI is mapped to more 
+ * than one prefix in the XML view, the prefix of the first URI is provided.
+ * Therefore, to provide high quality error messages in cases where the 
+ * tag elements themselves are checked, the prefix parameter should be 
+ * ignored and the actual prefix of the element should be used instead.  
+ * TagLibraryValidators should always use the uri to identify elements 
+ * as beloning to the tag library, not the prefix.
+ *
+ * <p>
+ * A TagLibraryValidator instance
+ * may create auxiliary objects internally to perform
+ * the validation (e.g. an XSchema validator) and may reuse it for all
+ * the pages in a given translation run.
+ *
+ * <p>
+ * The JSP container is not guaranteed to serialize invocations of
+ * validate() method, and TagLibraryValidators should perform any
+ * synchronization they may require.
+ *
+ * <p>
+ * As of JSP 2.0, a JSP container must provide a jsp:id attribute to
+ * provide higher quality validation errors.
+ * The container will track the JSP pages
+ * as passed to the container, and will assign to each element
+ * a unique "id", which is passed as the value of the jsp:id
+ * attribute.  Each XML element in the XML view available will
+ * be extended with this attribute.  The TagLibraryValidator
+ * can then use the attribute in one or more ValidationMessage
+ * objects.  The container then, in turn, can use these
+ * values to provide more precise information on the location
+ * of an error.
+ *
+ * <p>
+ * The actual prefix of the <code>id</code> attribute may or may not be 
+ * <code>jsp</code> but it will always map to the namespace
+ * <code>http://java.sun.com/JSP/Page</code>.  A TagLibraryValidator
+ * implementation must rely on the uri, not the prefix, of the <code>id</code>
+ * attribute.
+ */
+
+public abstract class TagLibraryValidator {
+
+    /**
+     * Sole constructor. (For invocation by subclass constructors, 
+     * typically implicit.)
+     */
+    public TagLibraryValidator() {
+        // NOOP by default
+    }
+    
+    /**
+     * Set the init data in the TLD for this validator.
+     * Parameter names are keys, and parameter values are the values.
+     *
+     * @param map A Map describing the init parameters
+     */
+    public void setInitParameters(Map<String, Object> map) {
+        initParameters = map;
+    }
+
+
+    /**
+     * Get the init parameters data as an immutable Map.
+     * Parameter names are keys, and parameter values are the values.
+     *
+     * @return The init parameters as an immutable map.
+     */
+    public Map<String, Object> getInitParameters() {
+        return initParameters;
+    }
+
+    /**
+     * Validate a JSP page.
+     * This will get invoked once per unique tag library URI in the
+     * XML view.  This method will return null if the page is valid; otherwise
+     * the method should return an array of ValidationMessage objects.
+     * An array of length zero is also interpreted as no errors.
+     *
+     * @param prefix the first prefix with which the tag library is 
+     *     associated, in the XML view.  Note that some tags may use 
+     *     a different prefix if the namespace is redefined.
+     * @param uri the tag library's unique identifier
+     * @param page the JspData page object
+     * @return A null object, or zero length array if no errors, an array
+     * of ValidationMessages otherwise.
+     */
+    public ValidationMessage[] validate(String prefix, String uri, 
+        PageData page) {
+        return null;
+    }
+
+    /**
+     * Release any data kept by this instance for validation purposes.
+     */
+    public void release() {
+        initParameters = null;
+    }
+
+    // Private data
+    private Map<String, Object> initParameters;
+
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagSupport.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagSupport.java
new file mode 100644
index 0000000..4355e71
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagSupport.java
@@ -0,0 +1,291 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES 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.tagext;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.PageContext;
+
+/**
+ * A base class for defining new tag handlers implementing Tag.
+ *
+ * <p> The TagSupport class is a utility class intended to be used as
+ * the base class for new tag handlers.  The TagSupport class
+ * implements the Tag and IterationTag interfaces and adds additional
+ * convenience methods including getter methods for the properties in
+ * Tag.  TagSupport has one static method that is included to
+ * facilitate coordination among cooperating tags.
+ *
+ * <p> Many tag handlers will extend TagSupport and only redefine a
+ * few methods. 
+ */
+public class TagSupport implements IterationTag, Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Find the instance of a given class type that is closest to a given
+     * instance.
+     * This method uses the getParent method from the Tag
+     * interface.
+     * This method is used for coordination among cooperating tags.
+     *
+     * <p>
+     * The current version of the specification only provides one formal
+     * way of indicating the observable type of a tag handler: its
+     * tag handler implementation class, described in the tag-class
+     * subelement of the tag element.  This is extended in an
+     * informal manner by allowing the tag library author to
+     * indicate in the description subelement an observable type.
+     * The type should be a subtype of the tag handler implementation
+     * class or void.
+     * This additional constraint can be exploited by a
+     * specialized container that knows about that specific tag library,
+     * as in the case of the JSP standard tag library.
+     *
+     * <p>
+     * When a tag library author provides information on the
+     * observable type of a tag handler, client programmatic code
+     * should adhere to that constraint.  Specifically, the Class
+     * passed to findAncestorWithClass should be a subtype of the
+     * observable type.
+     * 
+     *
+     * @param from The instance from where to start looking.
+     * @param klass The subclass of Tag or interface to be matched
+     * @return the nearest ancestor that implements the interface
+     * or is an instance of the class specified
+     */
+    public static final Tag findAncestorWithClass(Tag from,
+            // TCK signature test fails with generics
+            @SuppressWarnings("rawtypes")
+            Class klass) {
+        boolean isInterface = false;
+
+        if (from == null ||
+            klass == null ||
+            (!Tag.class.isAssignableFrom(klass) &&
+             !(isInterface = klass.isInterface()))) {
+            return null;
+        }
+
+        for (;;) {
+            Tag tag = from.getParent();
+
+            if (tag == null) {
+                return null;
+            }
+
+            if ((isInterface && klass.isInstance(tag)) ||
+                    ((Class<?>)klass).isAssignableFrom(tag.getClass())) {
+                return tag;
+            }
+            from = tag;
+        }
+    }
+
+    /**
+     * Default constructor, all subclasses are required to define only
+     * a public constructor with the same signature, and to call the
+     * superclass constructor.
+     *
+     * This constructor is called by the code generated by the JSP
+     * translator.
+     */
+    public TagSupport() {
+        // NOOP by default
+    }
+
+    /**
+     * Default processing of the start tag, returning SKIP_BODY.
+     *
+     * @return SKIP_BODY
+     * @throws JspException if an error occurs while processing this tag
+     *
+     * @see Tag#doStartTag()
+     */
+    @Override
+    public int doStartTag() throws JspException {
+        return SKIP_BODY;
+    }
+
+    /**
+     * Default processing of the end tag returning EVAL_PAGE.
+     *
+     * @return EVAL_PAGE
+     * @throws JspException if an error occurs while processing this tag
+     *
+     * @see Tag#doEndTag()
+     */
+    @Override
+    public int doEndTag() throws JspException {
+        return EVAL_PAGE;
+    }
+
+
+    /**
+     * Default processing for a body.
+     *
+     * @return SKIP_BODY
+     * @throws JspException if an error occurs while processing this tag
+     *
+     * @see IterationTag#doAfterBody()
+     */
+    @Override
+    public int doAfterBody() throws JspException {
+        return SKIP_BODY;
+    }
+
+    // Actions related to body evaluation
+
+
+    /**
+     * Release state.
+     *
+     * @see Tag#release()
+     */
+    @Override
+    public void release() {
+        parent = null;
+        id = null;
+        if( values != null ) {
+            values.clear();
+        }
+        values = null;
+    }
+
+    /**
+     * Set the nesting tag of this tag.
+     *
+     * @param t The parent Tag.
+     * @see Tag#setParent(Tag)
+     */
+    @Override
+    public void setParent(Tag t) {
+        parent = t;
+    }
+
+    /**
+     * The Tag instance most closely enclosing this tag instance.
+     * @see Tag#getParent()
+     *
+     * @return the parent tag instance or null
+     */
+    @Override
+    public Tag getParent() {
+        return parent;
+    }
+
+    /**
+     * Set the id attribute for this tag.
+     *
+     * @param id The String for the id.
+     */
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    /**
+     * The value of the id attribute of this tag; or null.
+     *
+     * @return the value of the id attribute, or null
+     */
+    public String getId() {
+        return id;
+    }
+
+    /**
+     * Set the page context.
+     *
+     * @param pageContext The PageContext.
+     * @see Tag#setPageContext
+     */
+    @Override
+    public void setPageContext(PageContext pageContext) {
+        this.pageContext = pageContext;
+    }
+
+    /**
+     * Associate a value with a String key.
+     *
+     * @param k The key String.
+     * @param o The value to associate.
+     */
+    public void setValue(String k, Object o) {
+        if (values == null) {
+            values = new Hashtable<String, Object>();
+        }
+        values.put(k, o);
+    }
+
+    /**
+     * Get a the value associated with a key.
+     *
+     * @param k The string key.
+     * @return The value associated with the key, or null.
+     */
+    public Object getValue(String k) {
+        if (values == null) {
+            return null;
+        }
+        return values.get(k);
+    }
+
+    /**
+     * Remove a value associated with a key.
+     *
+     * @param k The string key.
+     */
+    public void removeValue(String k) {
+        if (values != null) {
+            values.remove(k);
+        }
+    }
+
+    /**
+     * Enumerate the keys for the values kept by this tag handler.
+     *
+     * @return An enumeration of all the keys for the values set,
+     *     or null or an empty Enumeration if no values have been set.
+     */
+    public Enumeration<String> getValues() {
+        if (values == null) {
+            return null;
+        }
+        return values.keys();
+    }
+
+    // private fields
+
+    private   Tag         parent;
+    private   Hashtable<String, Object>   values;
+    /**
+     * The value of the id attribute of this tag; or null.
+     */
+    protected String      id;
+
+    // protected fields
+
+    /**
+     * The PageContext.
+     */
+    protected transient PageContext pageContext;
+}
+
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagVariableInfo.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagVariableInfo.java
new file mode 100644
index 0000000..87a7540
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TagVariableInfo.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 javax.servlet.jsp.tagext;
+
+/**
+ * Variable information for a tag in a Tag Library; This class is instantiated
+ * from the Tag Library Descriptor file (TLD) and is available only at
+ * translation time. This object should be immutable. This information is only
+ * available in JSP 1.2 format TLDs or above.
+ */
+public class TagVariableInfo {
+
+    /**
+     * Constructor for TagVariableInfo.
+     * 
+     * @param nameGiven
+     *            value of &lt;name-given&gt;
+     * @param nameFromAttribute
+     *            value of &lt;name-from-attribute&gt;
+     * @param className
+     *            value of &lt;variable-class&gt;
+     * @param declare
+     *            value of &lt;declare&gt;
+     * @param scope
+     *            value of &lt;scope&gt;
+     */
+    public TagVariableInfo(String nameGiven, String nameFromAttribute,
+            String className, boolean declare, int scope) {
+        this.nameGiven = nameGiven;
+        this.nameFromAttribute = nameFromAttribute;
+        this.className = className;
+        this.declare = declare;
+        this.scope = scope;
+    }
+
+    /**
+     * The body of the &lt;name-given&gt; element.
+     * 
+     * @return The variable name as a constant
+     */
+    public String getNameGiven() {
+        return nameGiven;
+    }
+
+    /**
+     * The body of the &lt;name-from-attribute&gt; element. This is the name of
+     * an attribute whose (translation-time) value will give the name of the
+     * variable. One of &lt;name-given&gt; or &lt;name-from-attribute&gt; is
+     * required.
+     * 
+     * @return The attribute whose value defines the variable name
+     */
+    public String getNameFromAttribute() {
+        return nameFromAttribute;
+    }
+
+    /**
+     * The body of the &lt;variable-class&gt; element.
+     * 
+     * @return The name of the class of the variable or 'java.lang.String' if
+     *         not defined in the TLD.
+     */
+    public String getClassName() {
+        return className;
+    }
+
+    /**
+     * The body of the &lt;declare&gt; element.
+     * 
+     * @return Whether the variable is to be declared or not. If not defined in
+     *         the TLD, 'true' will be returned.
+     */
+    public boolean getDeclare() {
+        return declare;
+    }
+
+    /**
+     * The body of the &lt;scope&gt; element.
+     * 
+     * @return The scope to give the variable. NESTED scope will be returned if
+     *         not defined in the TLD.
+     */
+    public int getScope() {
+        return scope;
+    }
+
+    /*
+     * private fields
+     */
+    private final String nameGiven; // <name-given>
+    private final String nameFromAttribute; // <name-from-attribute>
+    private final String className; // <class>
+    private final boolean declare; // <declare>
+    private final int scope; // <scope>
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TryCatchFinally.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TryCatchFinally.java
new file mode 100644
index 0000000..64621f0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/TryCatchFinally.java
@@ -0,0 +1,99 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES 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.tagext;
+
+
+
+/**
+ * The auxiliary interface of a Tag, IterationTag or BodyTag tag
+ * handler that wants additional hooks for managing resources.
+ *
+ * <p>This interface provides two new methods: doCatch(Throwable)
+ * and doFinally().  The prototypical invocation is as follows:
+ *
+ * <pre>
+ * h = get a Tag();  // get a tag handler, perhaps from pool
+ *
+ * h.setPageContext(pc);  // initialize as desired
+ * h.setParent(null);
+ * h.setFoo("foo");
+ * 
+ * // tag invocation protocol; see Tag.java
+ * try {
+ *   doStartTag()...
+ *   ....
+ *   doEndTag()...
+ * } catch (Throwable t) {
+ *   // react to exceptional condition
+ *   h.doCatch(t);
+ * } finally {
+ *   // restore data invariants and release per-invocation resources
+ *   h.doFinally();
+ * }
+ * 
+ * ... other invocations perhaps with some new setters
+ * ...
+ * h.release();  // release long-term resources
+ * </pre>
+ */
+
+public interface TryCatchFinally {
+
+    /**
+     * Invoked if a Throwable occurs while evaluating the BODY
+     * inside a tag or in any of the following methods:
+     * Tag.doStartTag(), Tag.doEndTag(),
+     * IterationTag.doAfterBody() and BodyTag.doInitBody().
+     *
+     * <p>This method is not invoked if the Throwable occurs during
+     * one of the setter methods.
+     *
+     * <p>This method may throw an exception (the same or a new one)
+     * that will be propagated further up the nest chain.  If an exception
+     * is thrown, doFinally() will be invoked.
+     *
+     * <p>This method is intended to be used to respond to an exceptional
+     * condition.
+     *
+     * @param t The throwable exception navigating through this tag.
+     * @throws Throwable if the exception is to be rethrown further up 
+     *     the nest chain.
+     */
+ 
+    void doCatch(Throwable t) throws Throwable;
+
+    /**
+     * Invoked in all cases after doEndTag() for any class implementing
+     * Tag, IterationTag or BodyTag.  This method is invoked even if
+     * an exception has occurred in the BODY of the tag,
+     * or in any of the following methods:
+     * Tag.doStartTag(), Tag.doEndTag(),
+     * IterationTag.doAfterBody() and BodyTag.doInitBody().
+     *
+     * <p>This method is not invoked if the Throwable occurs during
+     * one of the setter methods.
+     *
+     * <p>This method should not throw an Exception.
+     *
+     * <p>This method is intended to maintain per-invocation data
+     * integrity and resource management actions.
+     */
+
+    void doFinally();
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/ValidationMessage.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/ValidationMessage.java
new file mode 100644
index 0000000..9859074
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/ValidationMessage.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.tagext;
+
+/**
+ * A validation message from either TagLibraryValidator or TagExtraInfo.
+ * <p>
+ * As of JSP 2.0, a JSP container must support a jsp:id attribute to provide
+ * higher quality validation errors. The container will track the JSP pages as
+ * passed to the container, and will assign to each element a unique "id", which
+ * is passed as the value of the jsp:id attribute. Each XML element in the XML
+ * view available will be extended with this attribute. The TagLibraryValidator
+ * can then use the attribute in one or more ValidationMessage objects. The
+ * container then, in turn, can use these values to provide more precise
+ * information on the location of an error.
+ * <p>
+ * The actual prefix of the <code>id</code> attribute may or may not be
+ * <code>jsp</code> but it will always map to the namespace
+ * <code>http://java.sun.com/JSP/Page</code>. A TagLibraryValidator
+ * implementation must rely on the uri, not the prefix, of the <code>id</code>
+ * attribute.
+ */
+public class ValidationMessage {
+
+    /**
+     * Create a ValidationMessage. The message String should be non-null. The
+     * value of id may be null, if the message is not specific to any XML
+     * element, or if no jsp:id attributes were passed on. If non-null, the
+     * value of id must be the value of a jsp:id attribute for the PageData
+     * passed into the validate() method.
+     * 
+     * @param id
+     *            Either null, or the value of a jsp:id attribute.
+     * @param message
+     *            A localized validation message.
+     */
+    public ValidationMessage(String id, String message) {
+        this.id = id;
+        this.message = message;
+    }
+
+    /**
+     * Get the jsp:id. Null means that there is no information available.
+     * 
+     * @return The jsp:id information.
+     */
+    public String getId() {
+        return id;
+    }
+
+    /**
+     * Get the localized validation message.
+     * 
+     * @return A validation message
+     */
+    public String getMessage() {
+        return message;
+    }
+
+    // Private data
+    private final String id;
+    private final String message;
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/VariableInfo.java b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/VariableInfo.java
new file mode 100644
index 0000000..d35303d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/VariableInfo.java
@@ -0,0 +1,257 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.tagext;
+
+/**
+ * Information on the scripting variables that are created/modified by a tag (at
+ * run-time). This information is provided by TagExtraInfo classes and it is
+ * used by the translation phase of JSP.
+ * <p>
+ * Scripting variables generated by a custom action have an associated scope of
+ * either AT_BEGIN, NESTED, or AT_END.
+ * <p>
+ * The class name (VariableInfo.getClassName) in the returned objects is used to
+ * determine the types of the scripting variables. Note that because scripting
+ * variables are assigned their values from scoped attributes which cannot be of
+ * primitive types, &quot;boxed&quot; types such as
+ * <code>java.lang.Integer</code> must be used instead of primitives.
+ * <p>
+ * The class name may be a Fully Qualified Class Name, or a short class name.
+ * <p>
+ * If a Fully Qualified Class Name is provided, it should refer to a class that
+ * should be in the CLASSPATH for the Web Application (see Servlet 2.4
+ * specification - essentially it is WEB-INF/lib and WEB-INF/classes). Failure
+ * to be so will lead to a translation-time error.
+ * <p>
+ * If a short class name is given in the VariableInfo objects, then the class
+ * name must be that of a public class in the context of the import directives
+ * of the page where the custom action appears. The class must also be in the
+ * CLASSPATH for the Web Application (see Servlet 2.4 specification -
+ * essentially it is WEB-INF/lib and WEB-INF/classes). Failure to be so will
+ * lead to a translation-time error.
+ * <p>
+ * <B>Usage Comments</B>
+ * <p>
+ * Frequently a fully qualified class name will refer to a class that is known
+ * to the tag library and thus, delivered in the same JAR file as the tag
+ * handlers. In most other remaining cases it will refer to a class that is in
+ * the platform on which the JSP processor is built (like J2EE). Using fully
+ * qualified class names in this manner makes the usage relatively resistant to
+ * configuration errors.
+ * <p>
+ * A short name is usually generated by the tag library based on some attributes
+ * passed through from the custom action user (the author), and it is thus less
+ * robust: for instance a missing import directive in the referring JSP page
+ * will lead to an invalid short name class and a translation error.
+ * <p>
+ * <B>Synchronization Protocol</B>
+ * <p>
+ * The result of the invocation on getVariableInfo is an array of VariableInfo
+ * objects. Each such object describes a scripting variable by providing its
+ * name, its type, whether the variable is new or not, and what its scope is.
+ * Scope is best described through a picture:
+ * <p>
+ * <IMG src="doc-files/VariableInfo-1.gif"
+ * alt="NESTED, AT_BEGIN and AT_END Variable Scopes"/>
+ * <p>
+ * The JSP 2.0 specification defines the interpretation of 3 values:
+ * <ul>
+ * <li>NESTED, if the scripting variable is available between the start tag and
+ * the end tag of the action that defines it.
+ * <li>AT_BEGIN, if the scripting variable is available from the start tag of
+ * the action that defines it until the end of the scope.
+ * <li>AT_END, if the scripting variable is available after the end tag of the
+ * action that defines it until the end of the scope.
+ * </ul>
+ * The scope value for a variable implies what methods may affect its value and
+ * thus where synchronization is needed as illustrated by the table below.
+ * <b>Note:</b> the synchronization of the variable(s) will occur <em>after</em>
+ * the respective method has been called. <blockquote>
+ * <table cellpadding="2" cellspacing="2" border="0" width="55%" * bgcolor="#999999" summary="Variable Synchronization Points">
+ * <tbody>
+ * <tr align="center">
+ * <td valign="top" colspan="6" bgcolor="#999999"><u><b>Variable Synchronization
+ * Points</b></u><br>
+ * </td>
+ * </tr>
+ * <tr>
+ * <th valign="top" bgcolor="#c0c0c0">&nbsp;</th>
+ * <th valign="top" bgcolor="#c0c0c0" align="center">doStartTag()</th>
+ * <th valign="top" bgcolor="#c0c0c0" align="center">doInitBody()</th>
+ * <th valign="top" bgcolor="#c0c0c0" align="center">doAfterBody()</th>
+ * <th valign="top" bgcolor="#c0c0c0" align="center">doEndTag()</th>
+ * <th valign="top" bgcolor="#c0c0c0" align="center">doTag()</th>
+ * </tr>
+ * <tr>
+ * <td valign="top" bgcolor="#c0c0c0"><b>Tag<br>
+ * </b></td>
+ * <td valign="top" align="center" bgcolor="#ffffff">AT_BEGIN, NESTED<br>
+ * </td>
+ * <td valign="top" align="center" bgcolor="#ffffff"><br>
+ * </td>
+ * <td valign="top" align="center" bgcolor="#ffffff"><br>
+ * </td>
+ * <td valign="top" align="center" bgcolor="#ffffff">AT_BEGIN, AT_END<br>
+ * </td>
+ * <td valign="top" align="center" bgcolor="#ffffff"><br>
+ * </td>
+ * </tr>
+ * <tr>
+ * <td valign="top" bgcolor="#c0c0c0"><b>IterationTag<br>
+ * </b></td>
+ * <td valign="top" align="center" bgcolor="#ffffff">AT_BEGIN, NESTED<br>
+ * </td>
+ * <td valign="top" align="center" bgcolor="#ffffff"><br>
+ * </td>
+ * <td valign="top" align="center" bgcolor="#ffffff">AT_BEGIN, NESTED<br>
+ * </td>
+ * <td valign="top" align="center" bgcolor="#ffffff">AT_BEGIN, AT_END<br>
+ * </td>
+ * <td valign="top" align="center" bgcolor="#ffffff"><br>
+ * </td>
+ * </tr>
+ * <tr>
+ * <td valign="top" bgcolor="#c0c0c0"><b>BodyTag<br>
+ * </b></td>
+ * <td valign="top" align="center" bgcolor="#ffffff">AT_BEGIN,
+ * NESTED<sup>1</sup><br>
+ * </td>
+ * <td valign="top" align="center" bgcolor="#ffffff">AT_BEGIN,
+ * NESTED<sup>1</sup><br>
+ * </td>
+ * <td valign="top" align="center" bgcolor="#ffffff">AT_BEGIN, NESTED<br>
+ * </td>
+ * <td valign="top" align="center" bgcolor="#ffffff">AT_BEGIN, AT_END<br>
+ * </td>
+ * <td valign="top" align="center" bgcolor="#ffffff"><br>
+ * </td>
+ * </tr>
+ * <tr>
+ * <td valign="top" bgcolor="#c0c0c0"><b>SimpleTag<br>
+ * </b></td>
+ * <td valign="top" align="center" bgcolor="#ffffff"><br>
+ * </td>
+ * <td valign="top" align="center" bgcolor="#ffffff"><br>
+ * </td>
+ * <td valign="top" align="center" bgcolor="#ffffff"><br>
+ * </td>
+ * <td valign="top" align="center" bgcolor="#ffffff"><br>
+ * </td>
+ * <td valign="top" align="center" bgcolor="#ffffff">AT_BEGIN, AT_END<br>
+ * </td>
+ * </tr>
+ * </tbody>
+ * </table>
+ * <sup>1</sup> Called after <code>doStartTag()</code> if
+ * <code>EVAL_BODY_INCLUDE</code> is returned, or after
+ * <code>doInitBody()</code> otherwise. </blockquote>
+ * <p>
+ * <B>Variable Information in the TLD</B>
+ * <p>
+ * Scripting variable information can also be encoded directly for most cases
+ * into the Tag Library Descriptor using the &lt;variable&gt; subelement of the
+ * &lt;tag&gt; element. See the JSP specification.
+ */
+public class VariableInfo {
+
+    /**
+     * Scope information that scripting variable is visible only within the
+     * start/end tags.
+     */
+    public static final int NESTED = 0;
+
+    /**
+     * Scope information that scripting variable is visible after start tag.
+     */
+    public static final int AT_BEGIN = 1;
+
+    /**
+     * Scope information that scripting variable is visible after end tag.
+     */
+    public static final int AT_END = 2;
+
+    /**
+     * Constructor These objects can be created (at translation time) by the
+     * TagExtraInfo instances.
+     * 
+     * @param varName
+     *            The name of the scripting variable
+     * @param className
+     *            The type of this variable
+     * @param declare
+     *            If true, it is a new variable (in some languages this will
+     *            require a declaration)
+     * @param scope
+     *            Indication on the lexical scope of the variable
+     */
+    public VariableInfo(String varName, String className, boolean declare,
+            int scope) {
+        this.varName = varName;
+        this.className = className;
+        this.declare = declare;
+        this.scope = scope;
+    }
+
+    // Accessor methods
+
+    /**
+     * Returns the name of the scripting variable.
+     * 
+     * @return the name of the scripting variable
+     */
+    public String getVarName() {
+        return varName;
+    }
+
+    /**
+     * Returns the type of this variable.
+     * 
+     * @return the type of this variable
+     */
+    public String getClassName() {
+        return className;
+    }
+
+    /**
+     * Returns whether this is a new variable. If so, in some languages this
+     * will require a declaration.
+     * 
+     * @return whether this is a new variable.
+     */
+    public boolean getDeclare() {
+        return declare;
+    }
+
+    /**
+     * Returns the lexical scope of the variable.
+     * 
+     * @return the lexical scope of the variable, either AT_BEGIN, AT_END, or
+     *         NESTED.
+     * @see #AT_BEGIN
+     * @see #AT_END
+     * @see #NESTED
+     */
+    public int getScope() {
+        return scope;
+    }
+
+    // == private data
+    private final String varName;
+    private final String className;
+    private final boolean declare;
+    private final int scope;
+}
diff --git a/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/package.html b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/package.html
new file mode 100644
index 0000000..62c5e89
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/javax/servlet/jsp/tagext/package.html
@@ -0,0 +1,47 @@
+<!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 definition of JavaServer Pages Tag Libraries.
+
+<p>
+The JavaServer Pages(tm) (JSP) 2.0 specification provides a portable
+mechanism for the description of tag libraries.
+<p>
+A JSP tag library contains
+<ul>
+<li>A Tag Library Descriptor</li>
+<li>A number of Tag Files or Tag handler classes defining 
+    request-time behavior</li>
+<li>Additional classes and resources used at runtime</li>
+<li>Possibly some additional classes to provide extra translation 
+    information</li>
+</ul>
+<p>
+The JSP 2.0 specification and the reference implementation both contain
+simple and moderately complex examples of actions defined using this
+mechanism.  These are available at JSP's web site, at
+<a href="http://java.sun.com/products/jsp">http://java.sun.com/products/jsp</a>.
+Some readers may want to consult those to get a quick feel for how
+the mechanisms work together.
+
+</body>
+</html>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/AccessLog.java b/bundles/org.apache.tomcat/src/org/apache/catalina/AccessLog.java
new file mode 100644
index 0000000..9e9a418
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/AccessLog.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;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+
+
+/**
+ * Intended for use by a {@link Valve} to indicate that the {@link Valve}
+ * provides access logging. It is used by the Tomcat internals to identify a
+ * Valve that logs access requests so requests that are rejected
+ * earlier in the processing chain can still be added to the access log.
+ * Implementations of this interface should be robust against the provided
+ * {@link Request} and {@link Response} objects being null, having null
+ * attributes or any other 'oddness' that may result from attempting to log
+ * a request that was almost certainly rejected because it was mal-formed.
+ */
+public interface AccessLog {
+
+    /**
+     * Name of request attribute used to override the remote address recorded by
+     * the AccessLog.
+     */
+    public static final String REMOTE_ADDR_ATTRIBUTE =
+        "org.apache.catalina.AccessLog.RemoteAddr";
+
+    /**
+     * Name of request attribute used to override remote host name recorded by
+     * the AccessLog.
+     */
+    public static final String REMOTE_HOST_ATTRIBUTE =
+        "org.apache.catalina.AccessLog.RemoteHost";
+
+    /**
+     * Name of request attribute used to override the protocol recorded by the
+     * AccessLog.
+     */
+    public static final String PROTOCOL_ATTRIBUTE =
+        "org.apache.catalina.AccessLog.Protocol";
+
+    /**
+     * Name of request attribute used to override the server port recorded by
+     * the AccessLog.
+     */
+    public static final String SERVER_PORT_ATTRIBUTE =
+        "org.apache.catalina.AccessLog.ServerPort";
+    
+
+    /**
+     * Add the request/response to the access log using the specified processing
+     * time.
+     * 
+     * @param request   Request (associated with the response) to log
+     * @param response  Response (associated with the request) to log
+     * @param time      Time taken to process the request/response in
+     *                  milliseconds (use 0 if not known) 
+     */
+    public void log(Request request, Response response, long time);
+    
+    /**
+     * Should this valve set request attributes for IP address, Hostname,
+     * protocol and port used for the request? This are typically used in
+     * conjunction with the {@link org.apache.catalina.valves.AccessLogValve}
+     * which will otherwise log the original values.
+     * Default is <code>true</code>.
+     * 
+     * The attributes set are:
+     * <ul>
+     * <li>org.apache.catalina.RemoteAddr</li>
+     * <li>org.apache.catalina.RemoteHost</li>
+     * <li>org.apache.catalina.Protocol</li>
+     * <li>org.apache.catalina.ServerPost</li>
+     * </ul>
+     * 
+     * @param requestAttributesEnabled  <code>true</code> causes the attributes
+     *                                  to be set, <code>false</code> disables
+     *                                  the setting of the attributes. 
+     */
+    public void setRequestAttributesEnabled(boolean requestAttributesEnabled);
+    
+    /**
+     * @see #setRequestAttributesEnabled(boolean)
+     * @return <code>true</code> if the attributes will be logged, otherwise
+     *         <code>false</code>
+     */
+    public boolean getRequestAttributesEnabled();
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/Authenticator.java b/bundles/org.apache.tomcat/src/org/apache/catalina/Authenticator.java
new file mode 100644
index 0000000..fb52cee
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/Authenticator.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.catalina;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.deploy.LoginConfig;
+
+
+/**
+ * An <b>Authenticator</b> is a component (usually a Valve or Container) that
+ * provides some sort of authentication service.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: Authenticator.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public interface Authenticator {
+    
+    /**
+     * Authenticate the user making this request, based on the specified
+     * login configuration.  Return <code>true</code> if any specified
+     * constraint has been satisfied, or <code>false</code> if we have
+     * created a response challenge already.
+     *
+     * @param request Request we are processing
+     * @param response Response we are populating
+     * @param config    Login configuration describing how authentication
+     *              should be performed
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public boolean authenticate(Request request, HttpServletResponse response,
+            LoginConfig config) throws IOException;
+    
+    public void login(String userName, String password, Request request)
+            throws ServletException;
+
+    public void logout(Request request) throws ServletException;
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/CatalinaFactory.java b/bundles/org.apache.tomcat/src/org/apache/catalina/CatalinaFactory.java
new file mode 100644
index 0000000..412c1b4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/CatalinaFactory.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.catalina;
+
+import org.apache.catalina.core.StandardPipeline;
+
+/**
+ * Factory class used whenever a default implementation of a component is
+ * required. It provides both class names (for the digester) and objects for
+ * other components.  The current implementation is as simple as possible. If
+ * there is demand it can be extended to support alternative factories and/or
+ * alternative defaults.
+ * 
+ * TODO: Create the other standard components via this factory
+ */
+public class CatalinaFactory {
+    
+    private static CatalinaFactory factory = new CatalinaFactory();
+    
+    public static CatalinaFactory getFactory() {
+        return factory;
+    }
+    
+    private CatalinaFactory() {
+        // Hide the default constructor
+    }
+    
+    public String getDefaultPipelineClassName() {
+        return StandardPipeline.class.getName();
+    }
+
+    public Pipeline createPipeline(Container container) {
+        Pipeline pipeline = new StandardPipeline();
+        pipeline.setContainer(container);
+        return pipeline;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/Cluster.java b/bundles/org.apache.tomcat/src/org/apache/catalina/Cluster.java
new file mode 100644
index 0000000..0b66977
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/Cluster.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.catalina;
+
+/**
+ * A <b>Cluster</b> works as a Cluster client/server for the local host
+ * Different Cluster implementations can be used to support different
+ * ways to communicate within the Cluster. A Cluster implementation is
+ * responsible for setting up a way to communicate within the Cluster
+ * and also supply "ClientApplications" with <code>ClusterSender</code>
+ * used when sending information in the Cluster and
+ * <code>ClusterInfo</code> used for receiving information in the Cluster.
+ *
+ * @author Bip Thelin
+ * @author Remy Maucherat
+ * @author Filip Hanik
+ * @version $Id: Cluster.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ */
+
+public interface Cluster {
+
+    // ------------------------------------------------------------- Properties
+
+    /**
+     * Return descriptive information about this Cluster implementation and
+     * the corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    public String getInfo();
+
+    /**
+     * Return the name of the cluster that this Server is currently
+     * configured to operate within.
+     *
+     * @return The name of the cluster associated with this server
+     */
+    public String getClusterName();
+
+    /**
+     * Set the name of the cluster to join, if no cluster with
+     * this name is present create one.
+     *
+     * @param clusterName The clustername to join
+     */
+    public void setClusterName(String clusterName);
+
+    /**
+     * Set the Container associated with our Cluster
+     *
+     * @param container The Container to use
+     */
+    public void setContainer(Container container);
+
+    /**
+     * Get the Container associated with our Cluster
+     *
+     * @return The Container associated with our Cluster
+     */
+    public Container getContainer();
+
+    /**
+     * Set the protocol parameters.
+     *
+     * @param protocol The protocol used by the cluster
+     * @deprecated
+     */
+    @Deprecated
+    public void setProtocol(String protocol);
+
+    /**
+     * Get the protocol used by the cluster.
+     *
+     * @return The protocol
+     * @deprecated
+     */
+    @Deprecated
+    public String getProtocol();
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Create a new manager which will use this cluster to replicate its
+     * sessions.
+     *
+     * @param name Name (key) of the application with which the manager is
+     * associated
+     */
+    public Manager createManager(String name);
+    
+    /**
+     * Register a manager with the cluster. If the cluster is not responsible 
+     * for creating a manager, then the container will at least notify the 
+     * cluster that this manager is participating in the cluster.
+     * @param manager Manager
+     */
+    public void registerManager(Manager manager);
+    
+    /**
+     * Removes a manager from the cluster
+     * @param manager Manager
+     */
+    public void removeManager(Manager manager);
+
+    // --------------------------------------------------------- Cluster Wide Deployments
+    
+    
+    /**
+     * 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.
+     */
+    public void backgroundProcess();
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/Contained.java b/bundles/org.apache.tomcat/src/org/apache/catalina/Contained.java
new file mode 100644
index 0000000..de62e13
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/Contained.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;
+
+
+/**
+ * <p>Decoupling interface which specifies that an implementing class is
+ * associated with at most one <strong>Container</strong> instance.</p>
+ *
+ * @author Craig R. McClanahan
+ * @author Peter Donald
+ * @version $Id: Contained.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public interface Contained {
+
+
+    //-------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the <code>Container</code> with which this instance is associated
+     * (if any); otherwise return <code>null</code>.
+     */
+    public Container getContainer();
+
+
+    /**
+     * Set the <code>Container</code> with which this instance is associated.
+     *
+     * @param container The Container instance with which this instance is to
+     *  be associated, or <code>null</code> to disassociate this instance
+     *  from any Container
+     */
+    public void setContainer(Container container);
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/Container.java b/bundles/org.apache.tomcat/src/org/apache/catalina/Container.java
new file mode 100644
index 0000000..16cb7d1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/Container.java
@@ -0,0 +1,479 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+
+import java.beans.PropertyChangeListener;
+import java.io.IOException;
+
+import javax.management.ObjectName;
+import javax.naming.directory.DirContext;
+import javax.servlet.ServletException;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.juli.logging.Log;
+
+
+/**
+ * A <b>Container</b> is an object that can execute requests received from
+ * a client, and return responses based on those requests.  A Container may
+ * optionally support a pipeline of Valves that process the request in an
+ * order configured at runtime, by implementing the <b>Pipeline</b> interface
+ * as well.
+ * <p>
+ * Containers will exist at several conceptual levels within Catalina.  The
+ * following examples represent common cases:
+ * <ul>
+ * <li><b>Engine</b> - Representation of the entire Catalina servlet engine,
+ *     most likely containing one or more subcontainers that are either Host
+ *     or Context implementations, or other custom groups.
+ * <li><b>Host</b> - Representation of a virtual host containing a number
+ *     of Contexts.
+ * <li><b>Context</b> - Representation of a single ServletContext, which will
+ *     typically contain one or more Wrappers for the supported servlets.
+ * <li><b>Wrapper</b> - Representation of an individual servlet definition
+ *     (which may support multiple servlet instances if the servlet itself
+ *     implements SingleThreadModel).
+ * </ul>
+ * A given deployment of Catalina need not include Containers at all of the
+ * levels described above.  For example, an administration application
+ * embedded within a network device (such as a router) might only contain
+ * a single Context and a few Wrappers, or even a single Wrapper if the
+ * application is relatively small.  Therefore, Container implementations
+ * need to be designed so that they will operate correctly in the absence
+ * of parent Containers in a given deployment.
+ * <p>
+ * A Container may also be associated with a number of support components
+ * that provide functionality which might be shared (by attaching it to a
+ * parent Container) or individually customized.  The following support
+ * components are currently recognized:
+ * <ul>
+ * <li><b>Loader</b> - Class loader to use for integrating new Java classes
+ *     for this Container into the JVM in which Catalina is running.
+ * <li><b>Logger</b> - Implementation of the <code>log()</code> method
+ *     signatures of the <code>ServletContext</code> interface.
+ * <li><b>Manager</b> - Manager for the pool of Sessions associated with
+ *     this Container.
+ * <li><b>Realm</b> - Read-only interface to a security domain, for
+ *     authenticating user identities and their corresponding roles.
+ * <li><b>Resources</b> - JNDI directory context enabling access to static
+ *     resources, enabling custom linkages to existing server components when
+ *     Catalina is embedded in a larger server.
+ * </ul>
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @version $Id: Container.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public interface Container extends Lifecycle {
+
+
+    // ----------------------------------------------------- Manifest Constants
+
+
+    /**
+     * The ContainerEvent event type sent when a child container is added
+     * by <code>addChild()</code>.
+     */
+    public static final String ADD_CHILD_EVENT = "addChild";
+
+
+    /**
+     * The ContainerEvent event type sent when a Mapper is added
+     * by <code>addMapper()</code>.
+     */
+    public static final String ADD_MAPPER_EVENT = "addMapper";
+
+
+    /**
+     * The ContainerEvent event type sent when a valve is added
+     * by <code>addValve()</code>, if this Container supports pipelines.
+     */
+    public static final String ADD_VALVE_EVENT = "addValve";
+
+
+    /**
+     * The ContainerEvent event type sent when a child container is removed
+     * by <code>removeChild()</code>.
+     */
+    public static final String REMOVE_CHILD_EVENT = "removeChild";
+
+
+    /**
+     * The ContainerEvent event type sent when a Mapper is removed
+     * by <code>removeMapper()</code>.
+     */
+    public static final String REMOVE_MAPPER_EVENT = "removeMapper";
+
+
+    /**
+     * The ContainerEvent event type sent when a valve is removed
+     * by <code>removeValve()</code>, if this Container supports pipelines.
+     */
+    public static final String REMOVE_VALVE_EVENT = "removeValve";
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return descriptive information about this Container implementation and
+     * the corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    public String getInfo();
+
+
+    /**
+     * Return the Loader with which this Container is associated.  If there is
+     * no associated Loader, return the Loader associated with our parent
+     * Container (if any); otherwise, return <code>null</code>.
+     */
+    public Loader getLoader();
+
+
+    /**
+     * Set the Loader with which this Container is associated.
+     *
+     * @param loader The newly associated loader
+     */
+    public void setLoader(Loader loader);
+
+
+    /**
+     * Return the Logger with which this Container is associated.  If there is
+     * no associated Logger, return the Logger associated with our parent
+     * Container (if any); otherwise return <code>null</code>.
+     */
+    public Log getLogger();
+
+
+    /**
+     * Return the Manager with which this Container is associated.  If there is
+     * no associated Manager, return the Manager associated with our parent
+     * Container (if any); otherwise return <code>null</code>.
+     */
+    public Manager getManager();
+
+
+    /**
+     * Set the Manager with which this Container is associated.
+     *
+     * @param manager The newly associated Manager
+     */
+    public void setManager(Manager manager);
+
+
+    /**
+     * Return an object which may be utilized for mapping to this component.
+     */
+    public Object getMappingObject();
+
+    
+    /**
+     * Return the JMX name associated with this container.
+     */
+    public ObjectName getObjectName();    
+
+    /**
+     * Return the Pipeline object that manages the Valves associated with
+     * this Container.
+     */
+    public Pipeline getPipeline();
+
+
+    /**
+     * Return the Cluster with which this Container is associated.  If there is
+     * no associated Cluster, return the Cluster associated with our parent
+     * Container (if any); otherwise return <code>null</code>.
+     */
+    public Cluster getCluster();
+
+
+    /**
+     * Set the Cluster with which this Container is associated.
+     *
+     * @param cluster the Cluster with which this Container is associated.
+     */
+    public void setCluster(Cluster cluster);
+
+
+    /**
+     * Get the delay between the invocation of the backgroundProcess method on
+     * this container and its children. Child containers will not be invoked
+     * if their delay value is not negative (which would mean they are using 
+     * their own thread). Setting this to a positive value will cause 
+     * a thread to be spawn. After waiting the specified amount of time, 
+     * the thread will invoke the executePeriodic method on this container 
+     * and all its children.
+     */
+    public int getBackgroundProcessorDelay();
+
+
+    /**
+     * Set the delay between the invocation of the execute method on this
+     * container and its children.
+     * 
+     * @param delay The delay in seconds between the invocation of 
+     *              backgroundProcess methods
+     */
+    public void setBackgroundProcessorDelay(int delay);
+
+
+    /**
+     * Return a name string (suitable for use by humans) that describes this
+     * Container.  Within the set of child containers belonging to a particular
+     * parent, Container names must be unique.
+     */
+    public String getName();
+
+
+    /**
+     * Set a name string (suitable for use by humans) that describes this
+     * Container.  Within the set of child containers belonging to a particular
+     * parent, Container names must be unique.
+     *
+     * @param name New name of this container
+     *
+     * @exception IllegalStateException if this Container has already been
+     *  added to the children of a parent Container (after which the name
+     *  may not be changed)
+     */
+    public void setName(String name);
+
+
+    /**
+     * Return the Container for which this Container is a child, if there is
+     * one.  If there is no defined parent, return <code>null</code>.
+     */
+    public Container getParent();
+
+
+    /**
+     * Set the parent Container to which this Container is being added as a
+     * child.  This Container may refuse to become attached to the specified
+     * Container by throwing an exception.
+     *
+     * @param container Container to which this Container is being added
+     *  as a child
+     *
+     * @exception IllegalArgumentException if this Container refuses to become
+     *  attached to the specified Container
+     */
+    public void setParent(Container container);
+
+
+    /**
+     * Return the parent class loader for this component. If not set, return
+     * {@link #getParent()} {@link #getParentClassLoader()}. If no parent has
+     * been set, return the system class loader.
+     */
+    public ClassLoader getParentClassLoader();
+
+
+    /**
+     * Set the parent class loader for this component. For {@link Context}s
+     * this call is meaningful only <strong>before</strong> a Loader has
+     * been configured, and the specified value (if non-null) should be
+     * passed as an argument to the class loader constructor.
+     *
+     * @param parent The new parent class loader
+     */
+    public void setParentClassLoader(ClassLoader parent);
+
+
+    /**
+     * Return the Realm with which this Container is associated.  If there is
+     * no associated Realm, return the Realm associated with our parent
+     * Container (if any); otherwise return <code>null</code>.
+     */
+    public Realm getRealm();
+
+
+    /**
+     * Set the Realm with which this Container is associated.
+     *
+     * @param realm The newly associated Realm
+     */
+    public void setRealm(Realm realm);
+
+
+    /**
+     * Return the Resources with which this Container is associated.  If there
+     * is no associated Resources object, return the Resources associated with
+     * our parent Container (if any); otherwise return <code>null</code>.
+     */
+    public DirContext getResources();
+
+
+    /**
+     * Set the Resources object with which this Container is associated.
+     *
+     * @param resources The newly associated Resources
+     */
+    public void setResources(DirContext resources);
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * 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.
+     */
+    public void backgroundProcess();
+
+
+    /**
+     * Add a new child Container to those associated with this Container,
+     * if supported.  Prior to adding this Container to the set of children,
+     * the child's <code>setParent()</code> method must be called, with this
+     * Container as an argument.  This method may thrown an
+     * <code>IllegalArgumentException</code> if this Container chooses not
+     * to be attached to the specified Container, in which case it is not added
+     *
+     * @param child New child Container to be added
+     *
+     * @exception IllegalArgumentException if this exception is thrown by
+     *  the <code>setParent()</code> method of the child Container
+     * @exception IllegalArgumentException if the new child does not have
+     *  a name unique from that of existing children of this Container
+     * @exception IllegalStateException if this Container does not support
+     *  child Containers
+     */
+    public void addChild(Container child);
+
+
+    /**
+     * Add a container event listener to this component.
+     *
+     * @param listener The listener to add
+     */
+    public void addContainerListener(ContainerListener listener);
+
+
+    /**
+     * Add a property change listener to this component.
+     *
+     * @param listener The listener to add
+     */
+    public void addPropertyChangeListener(PropertyChangeListener listener);
+
+
+    /**
+     * Return the child Container, associated with this Container, with
+     * the specified name (if any); otherwise, return <code>null</code>
+     *
+     * @param name Name of the child Container to be retrieved
+     */
+    public Container findChild(String name);
+
+
+    /**
+     * Return the set of children Containers associated with this Container.
+     * If this Container has no children, a zero-length array is returned.
+     */
+    public Container[] findChildren();
+
+
+    /**
+     * Return the set of container listeners associated with this Container.
+     * If this Container has no registered container listeners, a zero-length
+     * array is returned.
+     */
+    public ContainerListener[] findContainerListeners();
+
+
+    /**
+     * Process the specified Request, and generate the corresponding Response,
+     * according to the design of this particular Container.
+     *
+     * @param request Request to be processed
+     * @param response Response to be produced
+     *
+     * @exception IOException if an input/output error occurred while
+     *  processing
+     * @exception ServletException if a ServletException was thrown
+     *  while processing this request
+     */
+    public void invoke(Request request, Response response)
+        throws IOException, ServletException;
+
+
+    /**
+     * Remove an existing child Container from association with this parent
+     * Container.
+     *
+     * @param child Existing child Container to be removed
+     */
+    public void removeChild(Container child);
+
+
+    /**
+     * Remove a container event listener from this component.
+     *
+     * @param listener The listener to remove
+     */
+    public void removeContainerListener(ContainerListener listener);
+
+
+    /**
+     * Remove a property change listener from this component.
+     *
+     * @param listener The listener to remove
+     */
+    public void removePropertyChangeListener(PropertyChangeListener listener);
+
+
+    /**
+     * Notify all container event listeners that a particular event has
+     * occurred for this Container.  The default implementation performs
+     * this notification synchronously using the calling thread.
+     *
+     * @param type Event type
+     * @param data Event data
+     */
+    public void fireContainerEvent(String type, Object data);
+    
+    
+    /**
+     * Log a request/response that was destined for this container but has been
+     * handled earlier in the processing chain so that the request/response
+     * still appears in the correct access logs.
+     * @param request       Request (associated with the response) to log
+     * @param response      Response (associated with the request) to log
+     * @param time          Time taken to process the request/response in
+     *                      milliseconds (use 0 if not known) 
+     * @param   useDefault  Flag that indicates that the request/response should
+     *                      be logged in the engine's default access log
+     */
+    public void logAccess(Request request, Response response, long time,
+            boolean useDefault);
+    
+    
+    /**
+     * Identify the AccessLog to use to log a request/response that was destined
+     * for this container but was handled earlier in the processing chain so
+     * that the request/response still appears in the correct access logs.
+     */
+    public AccessLog getAccessLog();
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ContainerEvent.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ContainerEvent.java
new file mode 100644
index 0000000..b8a2b19
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ContainerEvent.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;
+
+
+import java.util.EventObject;
+
+
+/**
+ * General event for notifying listeners of significant changes on a Container.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: ContainerEvent.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public final class ContainerEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * The event data associated with this event.
+     */
+    private Object data = null;
+
+
+    /**
+     * The event type this instance represents.
+     */
+    private String type = null;
+
+
+    /**
+     * Construct a new ContainerEvent with the specified parameters.
+     *
+     * @param container Container on which this event occurred
+     * @param type Event type
+     * @param data Event data
+     */
+    public ContainerEvent(Container container, String type, Object data) {
+
+        super(container);
+        this.type = type;
+        this.data = data;
+
+    }
+
+
+    /**
+     * Return the event data of this event.
+     */
+    public Object getData() {
+
+        return (this.data);
+
+    }
+
+
+    /**
+     * Return the Container on which this event occurred.
+     */
+    public Container getContainer() {
+
+        return (Container) getSource();
+
+    }
+
+
+    /**
+     * Return the event type of this event.
+     */
+    public String getType() {
+
+        return (this.type);
+
+    }
+
+
+    /**
+     * Return a string representation of this event.
+     */
+    @Override
+    public String toString() {
+
+        return ("ContainerEvent['" + getContainer() + "','" +
+                getType() + "','" + getData() + "']");
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ContainerListener.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ContainerListener.java
new file mode 100644
index 0000000..0798fb8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ContainerListener.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina;
+
+
+/**
+ * Interface defining a listener for significant Container generated events.
+ * Note that "container start" and "container stop" events are normally
+ * LifecycleEvents, not ContainerEvents.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: ContainerListener.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public interface ContainerListener {
+
+
+    /**
+     * Acknowledge the occurrence of the specified event.
+     *
+     * @param event ContainerEvent that has occurred
+     */
+    public void containerEvent(ContainerEvent event);
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ContainerServlet.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ContainerServlet.java
new file mode 100644
index 0000000..32cb442
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ContainerServlet.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.catalina;
+
+
+/**
+ * A <b>ContainerServlet</b> is a servlet that has access to Catalina
+ * internal functionality, and is loaded from the Catalina class loader
+ * instead of the web application class loader.  The property setter
+ * methods must be called by the container whenever a new instance of
+ * this servlet is put into service.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: ContainerServlet.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public interface ContainerServlet {
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the Wrapper with which this Servlet is associated.
+     */
+    public Wrapper getWrapper();
+
+
+    /**
+     * Set the Wrapper with which this Servlet is associated.
+     *
+     * @param wrapper The new associated Wrapper
+     */
+    public void setWrapper(Wrapper wrapper);
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/Context.java b/bundles/org.apache.tomcat/src/org/apache/catalina/Context.java
new file mode 100644
index 0000000..3eb460e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/Context.java
@@ -0,0 +1,1366 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+
+import java.net.URL;
+import java.util.Set;
+
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.descriptor.JspConfigDescriptor;
+
+import org.apache.catalina.core.ApplicationServletRegistration;
+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.LoginConfig;
+import org.apache.catalina.deploy.NamingResources;
+import org.apache.catalina.deploy.SecurityConstraint;
+import org.apache.catalina.util.CharsetMapper;
+import org.apache.tomcat.JarScanner;
+import org.apache.tomcat.util.http.mapper.Mapper;
+
+
+/**
+ * A <b>Context</b> is a Container that represents a servlet context, and
+ * therefore an individual web application, in the Catalina servlet engine.
+ * It is therefore useful in almost every deployment of Catalina (even if a
+ * Connector attached to a web server (such as Apache) uses the web server's
+ * facilities to identify the appropriate Wrapper to handle this request.
+ * It also provides a convenient mechanism to use Interceptors that see
+ * every request processed by this particular web application.
+ * <p>
+ * The parent Container attached to a Context is generally a Host, but may
+ * be some other implementation, or may be omitted if it is not necessary.
+ * <p>
+ * The child containers attached to a Context are generally implementations
+ * of Wrapper (representing individual servlet definitions).
+ * <p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: Context.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public interface Context extends Container {
+
+
+    // ----------------------------------------------------- Manifest Constants
+
+
+    /**
+     * The LifecycleEvent type sent when a context is reloaded.
+     */
+    public static final String RELOAD_EVENT = "reload";
+
+    /**
+     * Container event for adding a welcome file.
+     */
+    public static final String ADD_WELCOME_FILE_EVENT = "addWelcomeFile";
+    
+    /**
+     * Container event for removing a wrapper.
+     */
+    public static final String REMOVE_WELCOME_FILE_EVENT = "removeWelcomeFile";
+
+    /**
+     * Container event for clearing welcome files.
+     */
+    public static final String  CLEAR_WELCOME_FILES_EVENT = "clearWelcomeFiles";
+
+    // ------------------------------------------------------------- Properties
+
+    /**
+     * Returns <code>true</code> if requests mapped to servlets without
+     * "multipart config" to parse multipart/form-data requests anyway.
+     *
+     * @return <code>true</code> if requests mapped to servlets without
+     *    "multipart config" to parse multipart/form-data requests,
+     *    <code>false</code> otherwise.
+     */
+    public boolean getAllowCasualMultipartParsing();
+
+
+   /**
+     * Set to <code>true</code> to allow requests mapped to servlets that
+     * do not explicitly declare @MultipartConfig or have
+     * &lt;multipart-config&gt; specified in web.xml to parse
+     * multipart/form-data requests.
+     *
+     * @param allowCasualMultipartParsing <code>true</code> to allow such
+     *        casual parsing, <code>false</code> otherwise.
+     */
+    public void setAllowCasualMultipartParsing(boolean allowCasualMultipartParsing);
+
+
+    /**
+     * Return the set of initialized application event listener objects,
+     * in the order they were specified in the web application deployment
+     * descriptor, for this application.
+     *
+     * @exception IllegalStateException if this method is called before
+     *  this application has started, or after it has been stopped
+     */
+    public Object[] getApplicationEventListeners();
+
+
+    /**
+     * Store the set of initialized application event listener objects,
+     * in the order they were specified in the web application deployment
+     * descriptor, for this application.
+     *
+     * @param listeners The set of instantiated listener objects.
+     */
+    public void setApplicationEventListeners(Object listeners[]);
+
+
+    /**
+     * Return the set of initialized application lifecycle listener objects,
+     * in the order they were specified in the web application deployment
+     * descriptor, for this application.
+     *
+     * @exception IllegalStateException if this method is called before
+     *  this application has started, or after it has been stopped
+     */
+    public Object[] getApplicationLifecycleListeners();
+
+
+    /**
+     * Store the set of initialized application lifecycle listener objects,
+     * in the order they were specified in the web application deployment
+     * descriptor, for this application.
+     *
+     * @param listeners The set of instantiated listener objects.
+     */
+    public void setApplicationLifecycleListeners(Object listeners[]);
+
+
+    /**
+     * Return the application available flag for this Context.
+     */
+    public boolean getAvailable();
+
+
+    /**
+     * Return the Locale to character set mapper for this Context.
+     */
+    public CharsetMapper getCharsetMapper();
+
+
+    /**
+     * Set the Locale to character set mapper for this Context.
+     *
+     * @param mapper The new mapper
+     */
+    public void setCharsetMapper(CharsetMapper mapper);
+
+
+    /**
+     * Return the URL of the XML descriptor for this context.
+     */
+    public URL getConfigFile();
+
+
+    /**
+     * Set the URL of the XML descriptor for this context.
+     *
+     * @param configFile The URL of the XML descriptor for this context.
+     */
+    public void setConfigFile(URL configFile);
+
+
+    /**
+     * Return the "correctly configured" flag for this Context.
+     */
+    public boolean getConfigured();
+
+
+    /**
+     * Set the "correctly configured" flag for this Context.  This can be
+     * set to false by startup listeners that detect a fatal configuration
+     * error to avoid the application from being made available.
+     *
+     * @param configured The new correctly configured flag
+     */
+    public void setConfigured(boolean configured);
+
+
+    /**
+     * Return the "use cookies for session ids" flag.
+     */
+    public boolean getCookies();
+
+
+    /**
+     * Set the "use cookies for session ids" flag.
+     *
+     * @param cookies The new flag
+     */
+    public void setCookies(boolean cookies);
+
+    
+    /**
+     * Gets the name to use for session cookies. Overrides any setting that
+     * may be specified by the application.
+     * 
+     * @return  The value of the default session cookie name or null if not
+     *          specified
+     */
+    public String getSessionCookieName();
+    
+    
+    /**
+     * Sets the name to use for session cookies. Overrides any setting that
+     * may be specified by the application.
+     * 
+     * @param sessionCookieName   The name to use
+     */
+    public void setSessionCookieName(String sessionCookieName);
+
+    
+    /**
+     * Gets the value of the use HttpOnly cookies for session cookies flag.
+     * 
+     * @return <code>true</code> if the HttpOnly flag should be set on session
+     *         cookies
+     */
+    public boolean getUseHttpOnly();
+
+
+    /**
+     * Sets the use HttpOnly cookies for session cookies flag.
+     * 
+     * @param useHttpOnly   Set to <code>true</code> to use HttpOnly cookies
+     *                          for session cookies
+     */
+    public void setUseHttpOnly(boolean useHttpOnly);
+    
+    
+    /**
+     * Gets the domain to use for session cookies. Overrides any setting that
+     * may be specified by the application.
+     * 
+     * @return  The value of the default session cookie domain or null if not
+     *          specified
+     */
+    public String getSessionCookieDomain();
+    
+    
+    /**
+     * Sets the domain to use for session cookies. Overrides any setting that
+     * may be specified by the application.
+     * 
+     * @param sessionCookieDomain   The domain to use
+     */
+    public void setSessionCookieDomain(String sessionCookieDomain);
+
+    
+    /**
+     * Gets the path to use for session cookies. Overrides any setting that
+     * may be specified by the application.
+     * 
+     * @return  The value of the default session cookie path or null if not
+     *          specified
+     */
+    public String getSessionCookiePath();
+    
+    
+    /**
+     * Sets the path to use for session cookies. Overrides any setting that
+     * may be specified by the application.
+     * 
+     * @param sessionCookiePath   The path to use
+     */
+    public void setSessionCookiePath(String sessionCookiePath);
+
+    
+    /**
+     * Return the "allow crossing servlet contexts" flag.
+     */
+    public boolean getCrossContext();
+
+    
+    /**
+     * Return the alternate Deployment Descriptor name.
+     */
+    public String getAltDDName();
+    
+    
+    /**
+     * Set an alternate Deployment Descriptor name.
+     */
+    public void setAltDDName(String altDDName) ;
+    
+    
+    /**
+     * Set the "allow crossing servlet contexts" flag.
+     *
+     * @param crossContext The new cross contexts flag
+     */
+    public void setCrossContext(boolean crossContext);
+
+
+    /**
+     * Return the display name of this web application.
+     */
+    public String getDisplayName();
+
+
+    /**
+     * Set the display name of this web application.
+     *
+     * @param displayName The new display name
+     */
+    public void setDisplayName(String displayName);
+
+
+    /**
+     * Return the distributable flag for this web application.
+     */
+    public boolean getDistributable();
+
+
+    /**
+     * Set the distributable flag for this web application.
+     *
+     * @param distributable The new distributable flag
+     */
+    public void setDistributable(boolean distributable);
+
+
+    /**
+     * Return the document root for this Context.  This can be an absolute
+     * pathname, a relative pathname, or a URL.
+     */
+    public String getDocBase();
+
+
+    /**
+     * Set the document root for this Context.  This can be an absolute
+     * pathname, a relative pathname, or a URL.
+     *
+     * @param docBase The new document root
+     */
+    public void setDocBase(String docBase);
+
+
+    /**
+     * Return the URL encoded context path, using UTF-8.
+     */
+    public String getEncodedPath();
+
+
+    /**
+     * Return the boolean on the annotations parsing.
+     */
+    public boolean getIgnoreAnnotations();
+    
+    
+    /**
+     * Set the boolean on the annotations parsing for this web 
+     * application.
+     * 
+     * @param ignoreAnnotations The boolean on the annotations parsing
+     */
+    public void setIgnoreAnnotations(boolean ignoreAnnotations);
+    
+    
+    /**
+     * Return the login configuration descriptor for this web application.
+     */
+    public LoginConfig getLoginConfig();
+
+
+    /**
+     * Set the login configuration descriptor for this web application.
+     *
+     * @param config The new login configuration
+     */
+    public void setLoginConfig(LoginConfig config);
+
+
+    /**
+     * Get the request dispatcher mapper.
+     */
+    public Mapper getMapper();
+
+
+    /**
+     * Return the naming resources associated with this web application.
+     */
+    public NamingResources getNamingResources();
+
+
+    /**
+     * Set the naming resources for this web application.
+     *
+     * @param namingResources The new naming resources
+     */
+    public void setNamingResources(NamingResources namingResources);
+
+
+    /**
+     * Return the context path for this web application.
+     */
+    public String getPath();
+
+
+    /**
+     * Set the context path for this web application.
+     *
+     * @param path The new context path
+     */
+    public void setPath(String path);
+
+
+    /**
+     * Return the public identifier of the deployment descriptor DTD that is
+     * currently being parsed.
+     */
+    public String getPublicId();
+
+
+    /**
+     * Set the public identifier of the deployment descriptor DTD that is
+     * currently being parsed.
+     *
+     * @param publicId The public identifier
+     */
+    public void setPublicId(String publicId);
+
+
+    /**
+     * Return the reloadable flag for this web application.
+     */
+    public boolean getReloadable();
+
+
+    /**
+     * Set the reloadable flag for this web application.
+     *
+     * @param reloadable The new reloadable flag
+     */
+    public void setReloadable(boolean reloadable);
+
+
+    /**
+     * Return the override flag for this web application.
+     */
+    public boolean getOverride();
+
+
+    /**
+     * Set the override flag for this web application.
+     *
+     * @param override The new override flag
+     */
+    public void setOverride(boolean override);
+
+
+    /**
+     * Return the privileged flag for this web application.
+     */
+    public boolean getPrivileged();
+
+
+    /**
+     * Set the privileged flag for this web application.
+     *
+     * @param privileged The new privileged flag
+     */
+    public void setPrivileged(boolean privileged);
+
+
+    /**
+     * Return the servlet context for which this Context is a facade.
+     */
+    public ServletContext getServletContext();
+
+
+    /**
+     * Return the default session timeout (in minutes) for this
+     * web application.
+     */
+    public int getSessionTimeout();
+
+
+    /**
+     * Set the default session timeout (in minutes) for this
+     * web application.
+     *
+     * @param timeout The new default session timeout
+     */
+    public void setSessionTimeout(int timeout);
+
+
+    /**
+     * Returns <code>true</code> if remaining request data will be read
+     * (swallowed) even the request violates a data size constraint.
+     *
+     * @return <code>true</code> if data will be swallowed (default),
+     *    <code>false</code> otherwise.
+     */
+    public boolean getSwallowAbortedUploads();
+
+
+    /**
+     * Set to <code>false</code> to disable request data swallowing
+     * after an upload was aborted due to size constraints.
+     *
+     * @param swallowAbortedUploads <code>false</code> to disable
+     *        swallowing, <code>true</code> otherwise (default).
+     */
+    public void setSwallowAbortedUploads(boolean swallowAbortedUploads);
+
+    /**
+     * Return the value of the swallowOutput flag.
+     */
+    public boolean getSwallowOutput();
+
+
+    /**
+     * Set the value of the swallowOutput flag. If set to true, the system.out
+     * and system.err will be redirected to the logger during a servlet
+     * execution.
+     *
+     * @param swallowOutput The new value
+     */
+    public void setSwallowOutput(boolean swallowOutput);
+
+
+    /**
+     * Return the Java class name of the Wrapper implementation used
+     * for servlets registered in this Context.
+     */
+    public String getWrapperClass();
+
+
+    /**
+     * Set the Java class name of the Wrapper implementation used
+     * for servlets registered in this Context.
+     *
+     * @param wrapperClass The new wrapper class
+     */
+    public void setWrapperClass(String wrapperClass);
+
+
+    /**
+     * Get the server.xml <context> attribute's xmlNamespaceAware.
+     * @return true if namespace awareness is enabled.
+     *
+     */
+    public boolean getXmlNamespaceAware();
+
+
+    /**
+     * Get the server.xml <context> attribute's xmlValidation.
+     * @return true if validation is enabled.
+     *
+     */
+    public boolean getXmlValidation();
+
+
+    /**
+     * Set the validation feature of the XML parser used when
+     * parsing xml instances.
+     * @param xmlValidation true to enable xml instance validation
+     */
+    public void setXmlValidation(boolean xmlValidation);
+
+
+   /**
+     * Set the namespace aware feature of the XML parser used when
+     * parsing xml instances.
+     * @param xmlNamespaceAware true to enable namespace awareness
+     */
+    public void setXmlNamespaceAware(boolean xmlNamespaceAware);
+    /**
+     * Get the server.xml <context> attribute's xmlValidation.
+     * @return true if validation is enabled.
+     */
+     
+
+    /**
+     * Set the validation feature of the XML parser used when
+     * parsing tlds files. 
+     * @param tldValidation true to enable xml instance validation
+     */
+    public void setTldValidation(boolean tldValidation);
+
+
+    /**
+     * Get the server.xml <context> attribute's webXmlValidation.
+     * @return true if validation is enabled.
+     *
+     */
+    public boolean getTldValidation();
+
+
+    /**
+     * Get the server.xml &lt;host&gt; attribute's xmlNamespaceAware.
+     * @return true if namespace awareness is enabled.
+     */
+    public boolean getTldNamespaceAware();
+
+
+    /**
+     * Set the namespace aware feature of the XML parser used when
+     * parsing xml instances.
+     * @param tldNamespaceAware true to enable namespace awareness
+     */
+    public void setTldNamespaceAware(boolean tldNamespaceAware);
+
+    /**
+     * Get the Jar Scanner to be used to scan for JAR resources for this
+     * context.
+     * @return  The Jar Scanner configured for this context.
+     */
+    public JarScanner getJarScanner();
+
+    /**
+     * Set the Jar Scanner to be used to scan for JAR resources for this
+     * context.
+     * @param jarScanner    The Jar Scanner to be used for this context.
+     */
+    public void setJarScanner(JarScanner jarScanner);
+
+    /**
+     * Obtain the {@link Authenticator} that is used by this context or
+     * <code>null</code> if none is used.
+     */
+    public Authenticator getAuthenticator();
+    
+    /**
+     * Set whether or not the effective web.xml for this context should be
+     * logged on context start.
+     */
+    public void setLogEffectiveWebXml(boolean logEffectiveWebXml);
+    
+    /**
+     * Should the effective web.xml for this context be logged on context start?
+     */
+    public boolean getLogEffectiveWebXml();
+    
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add a new Listener class name to the set of Listeners
+     * configured for this application.
+     *
+     * @param listener Java class name of a listener class
+     */
+    public void addApplicationListener(String listener);
+
+
+    /**
+     * Add a new application parameter for this application.
+     *
+     * @param parameter The new application parameter
+     */
+    public void addApplicationParameter(ApplicationParameter parameter);
+
+
+    /**
+     * Add a security constraint to the set for this web application.
+     */
+    public void addConstraint(SecurityConstraint constraint);
+
+
+    /**
+     * Add an error page for the specified error or Java exception.
+     *
+     * @param errorPage The error page definition to be added
+     */
+    public void addErrorPage(ErrorPage errorPage);
+
+
+    /**
+     * Add a filter definition to this Context.
+     *
+     * @param filterDef The filter definition to be added
+     */
+    public void addFilterDef(FilterDef filterDef);
+
+
+    /**
+     * Add a filter mapping to this Context.
+     *
+     * @param filterMap The filter mapping to be added
+     */
+    public void addFilterMap(FilterMap filterMap);
+
+    /**
+     * Add a filter mapping to this Context before the mappings defined in the
+     * deployment descriptor but after any other mappings added via this method.
+     *
+     * @param filterMap The filter mapping to be added
+     *
+     * @exception IllegalArgumentException if the specified filter name
+     *  does not match an existing filter definition, or the filter mapping
+     *  is malformed
+     */
+    public void addFilterMapBefore(FilterMap filterMap);
+
+    /**
+     * Add the classname of an InstanceListener to be added to each
+     * Wrapper appended to this Context.
+     *
+     * @param listener Java class name of an InstanceListener class
+     */
+    public void addInstanceListener(String listener);
+
+
+    /**
+     * Add a Locale Encoding Mapping (see Sec 5.4 of Servlet spec 2.4)
+     *
+     * @param locale locale to map an encoding for
+     * @param encoding encoding to be used for a give locale
+     */
+    public void addLocaleEncodingMappingParameter(String locale, String encoding);
+
+
+    /**
+     * Add a new MIME mapping, replacing any existing mapping for
+     * the specified extension.
+     *
+     * @param extension Filename extension being mapped
+     * @param mimeType Corresponding MIME type
+     */
+    public void addMimeMapping(String extension, String mimeType);
+
+
+    /**
+     * Add a new context initialization parameter, replacing any existing
+     * value for the specified name.
+     *
+     * @param name Name of the new parameter
+     * @param value Value of the new  parameter
+     */
+    public void addParameter(String name, String value);
+
+
+    /**
+     * Add a security role reference for this web application.
+     *
+     * @param role Security role used in the application
+     * @param link Actual security role to check for
+     */
+    public void addRoleMapping(String role, String link);
+
+
+    /**
+     * Add a new security role for this web application.
+     *
+     * @param role New security role
+     */
+    public void addSecurityRole(String role);
+
+
+    /**
+     * Add a new servlet mapping, replacing any existing mapping for
+     * the specified pattern.
+     *
+     * @param pattern URL pattern to be mapped
+     * @param name Name of the corresponding servlet to execute
+     */
+    public void addServletMapping(String pattern, String name);
+
+
+    /**
+     * Add a new servlet mapping, replacing any existing mapping for
+     * the specified pattern.
+     *
+     * @param pattern URL pattern to be mapped
+     * @param name Name of the corresponding servlet to execute
+     * @param jspWildcard true if name identifies the JspServlet
+     * and pattern contains a wildcard; false otherwise
+     */
+    public void addServletMapping(String pattern, String name,
+            boolean jspWildcard);
+
+
+    /**
+     * Add a resource which will be watched for reloading by the host auto
+     * deployer. Note: this will not be used in embedded mode.
+     * 
+     * @param name Path to the resource, relative to docBase
+     */
+    public void addWatchedResource(String name);
+    
+
+    /**
+     * Add a new welcome file to the set recognized by this Context.
+     *
+     * @param name New welcome file name
+     */
+    public void addWelcomeFile(String name);
+
+
+    /**
+     * Add the classname of a LifecycleListener to be added to each
+     * Wrapper appended to this Context.
+     *
+     * @param listener Java class name of a LifecycleListener class
+     */
+    public void addWrapperLifecycle(String listener);
+
+
+    /**
+     * Add the classname of a ContainerListener to be added to each
+     * Wrapper appended to this Context.
+     *
+     * @param listener Java class name of a ContainerListener class
+     */
+    public void addWrapperListener(String listener);
+
+
+    /**
+     * Factory method to create and return a new Wrapper instance, of
+     * the Java implementation class appropriate for this Context
+     * implementation.  The constructor of the instantiated Wrapper
+     * will have been called, but no properties will have been set.
+     */
+    public Wrapper createWrapper();
+
+
+    /**
+     * Return the set of application listener class names configured
+     * for this application.
+     */
+    public String[] findApplicationListeners();
+
+
+    /**
+     * Return the set of application parameters for this application.
+     */
+    public ApplicationParameter[] findApplicationParameters();
+
+
+    /**
+     * Return the set of security constraints for this web application.
+     * If there are none, a zero-length array is returned.
+     */
+    public SecurityConstraint[] findConstraints();
+
+
+    /**
+     * 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 ErrorPage findErrorPage(int errorCode);
+
+
+    /**
+     * 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 ErrorPage findErrorPage(String exceptionType);
+
+
+
+    /**
+     * Return the set of defined error pages for all specified error codes
+     * and exception types.
+     */
+    public ErrorPage[] findErrorPages();
+
+
+    /**
+     * Return the filter definition for the specified filter name, if any;
+     * otherwise return <code>null</code>.
+     *
+     * @param filterName Filter name to look up
+     */
+    public FilterDef findFilterDef(String filterName);
+
+
+    /**
+     * Return the set of defined filters for this Context.
+     */
+    public FilterDef[] findFilterDefs();
+
+
+    /**
+     * Return the set of filter mappings for this Context.
+     */
+    public FilterMap[] findFilterMaps();
+
+
+    /**
+     * Return the set of InstanceListener classes that will be added to
+     * newly created Wrappers automatically.
+     */
+    public String[] findInstanceListeners();
+
+
+    /**
+     * Return the MIME type to which the specified extension is mapped,
+     * if any; otherwise return <code>null</code>.
+     *
+     * @param extension Extension to map to a MIME type
+     */
+    public String findMimeMapping(String extension);
+
+
+    /**
+     * Return the extensions for which MIME mappings are defined.  If there
+     * are none, a zero-length array is returned.
+     */
+    public String[] findMimeMappings();
+
+
+    /**
+     * Return the value for the specified context initialization
+     * parameter name, if any; otherwise return <code>null</code>.
+     *
+     * @param name Name of the parameter to return
+     */
+    public String findParameter(String name);
+
+
+    /**
+     * Return the names of all defined context initialization parameters
+     * for this Context.  If no parameters are defined, a zero-length
+     * array is returned.
+     */
+    public String[] findParameters();
+
+
+    /**
+     * For the given security role (as used by an application), return the
+     * corresponding role name (as defined by the underlying Realm) if there
+     * is one.  Otherwise, return the specified role unchanged.
+     *
+     * @param role Security role to map
+     */
+    public String findRoleMapping(String role);
+
+
+    /**
+     * Return <code>true</code> if the specified security role is defined
+     * for this application; otherwise return <code>false</code>.
+     *
+     * @param role Security role to verify
+     */
+    public boolean findSecurityRole(String role);
+
+
+    /**
+     * Return the security roles defined for this application.  If none
+     * have been defined, a zero-length array is returned.
+     */
+    public String[] findSecurityRoles();
+
+
+    /**
+     * Return the servlet name mapped by the specified pattern (if any);
+     * otherwise return <code>null</code>.
+     *
+     * @param pattern Pattern for which a mapping is requested
+     */
+    public String findServletMapping(String pattern);
+
+
+    /**
+     * Return the patterns of all defined servlet mappings for this
+     * Context.  If no mappings are defined, a zero-length array is returned.
+     */
+    public String[] findServletMappings();
+
+
+    /**
+     * Return the context-relative URI of the error page for the specified
+     * HTTP status code, if any; otherwise return <code>null</code>.
+     *
+     * @param status HTTP status code to look up
+     */
+    public String findStatusPage(int status);
+
+
+    /**
+     * Return the set of HTTP status codes for which error pages have
+     * been specified.  If none are specified, a zero-length array
+     * is returned.
+     */
+    public int[] findStatusPages();
+
+
+    /**
+     * Return the set of watched resources for this Context. If none are 
+     * defined, a zero length array will be returned.
+     */
+    public String[] findWatchedResources();
+    
+
+    /**
+     * Return <code>true</code> if the specified welcome file is defined
+     * for this Context; otherwise return <code>false</code>.
+     *
+     * @param name Welcome file to verify
+     */
+    public boolean findWelcomeFile(String name);
+
+    
+    /**
+     * Return the set of welcome files defined for this Context.  If none are
+     * defined, a zero-length array is returned.
+     */
+    public String[] findWelcomeFiles();
+
+
+    /**
+     * Return the set of LifecycleListener classes that will be added to
+     * newly created Wrappers automatically.
+     */
+    public String[] findWrapperLifecycles();
+
+
+    /**
+     * Return the set of ContainerListener classes that will be added to
+     * newly created Wrappers automatically.
+     */
+    public String[] findWrapperListeners();
+
+
+    /**
+     * Notify all {@link javax.servlet.ServletRequestListener}s that a request
+     * has started.
+     * @return <code>true</code> if the listeners fire successfully, else
+     *         <code>false</code>
+     */
+    public boolean fireRequestInitEvent(ServletRequest request);
+
+    /**
+     * Notify all {@link javax.servlet.ServletRequestListener}s that a request
+     * has ended.
+     * @return <code>true</code> if the listeners fire successfully, else
+     *         <code>false</code>
+     */
+    public boolean fireRequestDestroyEvent(ServletRequest request);
+
+    /**
+     * Reload this web application, if reloading is supported.
+     *
+     * @exception IllegalStateException if the <code>reloadable</code>
+     *  property is set to <code>false</code>.
+     */
+    public void reload();
+
+
+    /**
+     * Remove the specified application listener class from the set of
+     * listeners for this application.
+     *
+     * @param listener Java class name of the listener to be removed
+     */
+    public void removeApplicationListener(String listener);
+
+
+    /**
+     * Remove the application parameter with the specified name from
+     * the set for this application.
+     *
+     * @param name Name of the application parameter to remove
+     */
+    public void removeApplicationParameter(String name);
+
+
+    /**
+     * Remove the specified security constraint from this web application.
+     *
+     * @param constraint Constraint to be removed
+     */
+    public void removeConstraint(SecurityConstraint constraint);
+
+
+    /**
+     * Remove the error page for the specified error code or
+     * Java language exception, if it exists; otherwise, no action is taken.
+     *
+     * @param errorPage The error page definition to be removed
+     */
+    public void removeErrorPage(ErrorPage errorPage);
+
+
+    /**
+     * Remove the specified filter definition from this Context, if it exists;
+     * otherwise, no action is taken.
+     *
+     * @param filterDef Filter definition to be removed
+     */
+    public void removeFilterDef(FilterDef filterDef);
+
+
+    /**
+     * Remove a filter mapping from this Context.
+     *
+     * @param filterMap The filter mapping to be removed
+     */
+    public void removeFilterMap(FilterMap filterMap);
+
+
+    /**
+     * Remove a class name from the set of InstanceListener classes that
+     * will be added to newly created Wrappers.
+     *
+     * @param listener Class name of an InstanceListener class to be removed
+     */
+    public void removeInstanceListener(String listener);
+
+
+    /**
+     * Remove the MIME mapping for the specified extension, if it exists;
+     * otherwise, no action is taken.
+     *
+     * @param extension Extension to remove the mapping for
+     */
+    public void removeMimeMapping(String extension);
+
+
+    /**
+     * Remove the context initialization parameter with the specified
+     * name, if it exists; otherwise, no action is taken.
+     *
+     * @param name Name of the parameter to remove
+     */
+    public void removeParameter(String name);
+
+
+    /**
+     * Remove any security role reference for the specified name
+     *
+     * @param role Security role (as used in the application) to remove
+     */
+    public void removeRoleMapping(String role);
+
+
+    /**
+     * Remove any security role with the specified name.
+     *
+     * @param role Security role to remove
+     */
+    public void removeSecurityRole(String role);
+
+
+    /**
+     * Remove any servlet mapping for the specified pattern, if it exists;
+     * otherwise, no action is taken.
+     *
+     * @param pattern URL pattern of the mapping to remove
+     */
+    public void removeServletMapping(String pattern);
+
+
+    /**
+     * Remove the specified watched resource name from the list associated
+     * with this Context.
+     * 
+     * @param name Name of the watched resource to be removed
+     */
+    public void removeWatchedResource(String name);
+    
+
+    /**
+     * Remove the specified welcome file name from the list recognized
+     * by this Context.
+     *
+     * @param name Name of the welcome file to be removed
+     */
+    public void removeWelcomeFile(String name);
+
+
+    /**
+     * Remove a class name from the set of LifecycleListener classes that
+     * will be added to newly created Wrappers.
+     *
+     * @param listener Class name of a LifecycleListener class to be removed
+     */
+    public void removeWrapperLifecycle(String listener);
+
+
+    /**
+     * Remove a class name from the set of ContainerListener classes that
+     * will be added to newly created Wrappers.
+     *
+     * @param listener Class name of a ContainerListener class to be removed
+     */
+    public void removeWrapperListener(String listener);
+
+
+    /**
+     * Return the real path for a given virtual path, if possible; otherwise
+     * return <code>null</code>.
+     *
+     * @param path The path to the desired resource
+     */
+    public String getRealPath(String path);
+    
+    
+    /**
+     * Return the effective major version of the Servlet spec used by this
+     * context.
+     */
+    public int getEffectiveMajorVersion();
+    
+    
+    /**
+     * Set the effective major version of the Servlet spec used by this
+     * context.
+     */
+    public void setEffectiveMajorVersion(int major);
+    
+    
+    /**
+     * Return the effective minor version of the Servlet spec used by this
+     * context.
+     */
+    public int getEffectiveMinorVersion();
+    
+    
+    /**
+     * Set the effective minor version of the Servlet spec used by this
+     * context.
+     */
+    public void setEffectiveMinorVersion(int minor);
+    
+    
+    /**
+     * Obtain the JSP configuration for this context.
+     */
+    public JspConfigDescriptor getJspConfigDescriptor();
+
+    
+    /**
+     * Add a URL for a JAR that contains static resources in a
+     * META-INF/resources directory that should be included in the static
+     * resources for this context.
+     */
+    public void addResourceJarUrl(URL url);
+    
+    
+    /**
+     * Add a ServletContainerInitializer instance to this web application.
+     * 
+     * @param sci       The instance to add
+     * @param classes   The classes in which the initializer expressed an
+     *                  interest
+     */
+    public void addServletContainerInitializer(
+            ServletContainerInitializer sci, Set<Class<?>> classes);
+    
+    /**
+     * Is this Context paused whilst it is reloaded?
+     */
+    public boolean getPaused();
+
+    
+    /**
+     * Is this context using version 2.2 of the Servlet spec?
+     */
+    boolean isServlet22();
+
+    /**
+     * Notification that servlet security has been dynamically set in a
+     * {@link javax.servlet.ServletRegistration.Dynamic}
+     * @param registration servlet security was modified for
+     * @param servletSecurityElement new security constraints for this servlet
+     * @return urls currently mapped to this registration that are already
+     *         present in web.xml
+     */
+    Set<String> addServletSecurity(ApplicationServletRegistration registration,
+            ServletSecurityElement servletSecurityElement);
+    
+    /**
+     * Sets the (comma separated) list of Servlets that expect a resource to be
+     * present. Used to ensure that welcome files associated with Servlets that
+     * expect a resource to be present are not mapped when there is no resource. 
+     */
+    public void setResourceOnlyServlets(String resourceOnlyServlets);
+    
+    /**
+     * Obtains the list of Servlets that expect a resource to be present.
+     * 
+     * @return  A comma separated list of Servlet names as used in web.xml
+     */
+    public String getResourceOnlyServlets();
+
+    /**
+     * Checks the named Servlet to see if it expects a resource to be present.
+     * 
+     * @param servletName   Name of the Servlet (as per web.xml) to check
+     * @return              <code>true</code> if the Servlet expects a resource,
+     *                      otherwise <code>false</code>
+     */
+    public boolean isResourceOnlyServlet(String servletName);
+    
+    /**
+     * Return the base name to use for WARs, directories or context.xml files
+     * for this context.
+     */
+    public String getBaseName();
+     
+    /**
+     * Set the version of this web application - used to differentiate
+     * different versions of the same web application when using parallel
+     * deployment.
+     */
+    public void setWebappVersion(String webappVersion);
+
+    /**
+     * Set the version of this web application - used to differentiate
+     * different versions of the same web application when using parallel
+     * deployment. If not specified, defaults to the empty string.
+     */
+    public String getWebappVersion();
+    
+    /**
+     * Configure whether or not requests listeners will be fired on forwards for
+     * this Context.
+     */
+    public void setFireRequestListenersOnForwards(boolean enable);
+
+    /**
+     * Determine whether or not requests listeners will be fired on forwards for
+     * this Context.
+     */
+    public boolean getFireRequestListenersOnForwards();
+    
+    /**
+     * Configures if a user presents authentication credentials, whether the
+     * context will process them when the request is for a non-protected
+     * resource.
+     */
+    public void setPreemptiveAuthentication(boolean enable);
+
+    /**
+     * Determines if a user presents authentication credentials, will the
+     * context will process them when the request is for a non-protected
+     * resource.
+     */
+    public boolean getPreemptiveAuthentication();
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/DistributedManager.java b/bundles/org.apache.tomcat/src/org/apache/catalina/DistributedManager.java
new file mode 100644
index 0000000..2d042c6
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/DistributedManager.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;
+
+import java.util.Set;
+
+/**
+ * Interface implemented by session managers that do not keep a complete copy
+ * of all sessions on the local node but do know where every session is. The
+ * BackupManager is an example of such a Manager. Sessions can be primary
+ * (master copy on this node), backup (backup copy on this node) or proxy (only
+ * the session ID on this node). The identity of the primary and backup nodes
+ * are known for all sessions, including proxy sessions.
+ */
+public interface DistributedManager {
+
+    /**
+     * Returns the total session count for primary, backup and proxy.
+     * 
+     * @return  The total session count across the cluster.
+     */
+    public int getActiveSessionsFull();
+
+    /**
+     * Returns the list of all sessions IDS (primary, backup and proxy).
+     * 
+     * @return  The complete set of sessions IDs across the cluster.
+     */
+    public Set<String> getSessionIdsFull();
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/Engine.java b/bundles/org.apache.tomcat/src/org/apache/catalina/Engine.java
new file mode 100644
index 0000000..06fcbc1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/Engine.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.catalina;
+
+/**
+ * An <b>Engine</b> is a Container that represents the entire Catalina servlet
+ * engine.  It is useful in the following types of scenarios:
+ * <ul>
+ * <li>You wish to use Interceptors that see every single request processed
+ *     by the entire engine.
+ * <li>You wish to run Catalina in with a standalone HTTP connector, but still
+ *     want support for multiple virtual hosts.
+ * </ul>
+ * In general, you would not use an Engine when deploying Catalina connected
+ * to a web server (such as Apache), because the Connector will have
+ * utilized the web server's facilities to determine which Context (or
+ * perhaps even which Wrapper) should be utilized to process this request.
+ * <p>
+ * The child containers attached to an Engine are generally implementations
+ * of Host (representing a virtual host) or Context (representing individual
+ * an individual servlet context), depending upon the Engine implementation.
+ * <p>
+ * If used, an Engine is always the top level Container in a Catalina
+ * hierarchy. Therefore, the implementation's <code>setParent()</code> method
+ * should throw <code>IllegalArgumentException</code>.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: Engine.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public interface Engine extends Container {
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the default hostname for this Engine.
+     */
+    public String getDefaultHost();
+
+
+    /**
+     * Set the default hostname for this Engine.
+     *
+     * @param defaultHost The new default host
+     */
+    public void setDefaultHost(String defaultHost);
+
+
+    /**
+     * Retrieve the JvmRouteId for this engine.
+     */
+    public String getJvmRoute();
+
+
+    /**
+     * Set the JvmRouteId for this engine.
+     *
+     * @param jvmRouteId the (new) JVM Route ID. Each Engine within a cluster
+     *        must have a unique JVM Route ID.
+     */
+    public void setJvmRoute(String jvmRouteId);
+
+
+    /**
+     * Return the <code>Service</code> with which we are associated (if any).
+     */
+    public Service getService();
+
+
+    /**
+     * Set the <code>Service</code> with which we are associated (if any).
+     *
+     * @param service The service that owns this Engine
+     */
+    public void setService(Service service);
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/Executor.java b/bundles/org.apache.tomcat/src/org/apache/catalina/Executor.java
new file mode 100644
index 0000000..6ce8755
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/Executor.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.catalina;
+
+import java.util.concurrent.TimeUnit;
+
+
+public interface Executor extends java.util.concurrent.Executor, Lifecycle {
+    public String getName();
+    
+    /**
+     * 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 until it throws a RejectedExecutionException
+     *
+     * @param command the runnable task
+     * @throws org.apache.catalina.util.RejectedExecutionException if this task
+     * cannot be accepted for execution - the queue is full
+     * @throws NullPointerException if command or unit is null
+     */
+    void execute(Runnable command, long timeout, TimeUnit unit);
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/Globals.java b/bundles/org.apache.tomcat/src/org/apache/catalina/Globals.java
new file mode 100644
index 0000000..735f643
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/Globals.java
@@ -0,0 +1,197 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+
+/**
+ * Global constants that are applicable to multiple packages within Catalina.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: Globals.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ */
+
+public final class Globals {
+
+    /**
+     * The servlet context attribute under which we store the alternate
+     * deployment descriptor for this web application 
+     */
+    public static final String ALT_DD_ATTR = 
+        "org.apache.catalina.deploy.alt_dd";
+
+
+    /**
+     * The request attribute under which we store the array of X509Certificate
+     * objects representing the certificate chain presented by our client,
+     * if any.
+     */
+    public static final String CERTIFICATES_ATTR =
+        "javax.servlet.request.X509Certificate";
+
+
+    /**
+     * The request attribute under which we store the name of the cipher suite
+     * being used on an SSL connection (as an object of type
+     * java.lang.String).
+     */
+    public static final String CIPHER_SUITE_ATTR =
+        "javax.servlet.request.cipher_suite";
+
+
+    /**
+     * Request dispatcher state.
+     */
+    public static final String DISPATCHER_TYPE_ATTR = 
+        "org.apache.catalina.core.DISPATCHER_TYPE";
+
+
+    /**
+     * Request dispatcher path.
+     */
+    public static final String DISPATCHER_REQUEST_PATH_ATTR = 
+        "org.apache.catalina.core.DISPATCHER_REQUEST_PATH";
+
+
+    /**
+     * The JNDI directory context which is associated with the context. This
+     * context can be used to manipulate static files.
+     */
+    public static final String RESOURCES_ATTR =
+        "org.apache.catalina.resources";
+
+
+    /**
+     * The servlet context attribute under which we store the class path
+     * for our application class loader (as an object of type String),
+     * delimited with the appropriate path delimiter for this platform.
+     */
+    public static final String CLASS_PATH_ATTR =
+        "org.apache.catalina.jsp_classpath";
+
+
+    /**
+     * The request attribute under which we store the key size being used for
+     * this SSL connection (as an object of type java.lang.Integer).
+     */
+    public static final String KEY_SIZE_ATTR =
+        "javax.servlet.request.key_size";
+
+
+    /**
+     * The request attribute under which we store the session id being used
+     * for this SSL connection (as an object of type java.lang.String).
+     */
+    public static final String SSL_SESSION_ID_ATTR =
+        "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 SSL_SESSION_MGR_ATTR =
+        "javax.servlet.request.ssl_session_mgr";
+
+
+    /**
+     * The servlet context attribute under which the managed bean Registry
+     * will be stored for privileged contexts (if enabled).
+     */
+    public static final String MBEAN_REGISTRY_ATTR =
+        "org.apache.catalina.Registry";
+
+
+    /**
+     * The servlet context attribute under which the MBeanServer will be stored
+     * for privileged contexts (if enabled).
+     */
+    public static final String MBEAN_SERVER_ATTR =
+        "org.apache.catalina.MBeanServer";
+
+
+    /**
+     * The request attribute under which we store the servlet name on a
+     * named dispatcher request.
+     */
+    public static final String NAMED_DISPATCHER_ATTR =
+        "org.apache.catalina.NAMED";
+
+
+    /**
+     * The servlet context attribute under which we store a flag used
+     * to mark this request as having been processed by the SSIServlet.
+     * We do this because of the pathInfo mangling happening when using
+     * the CGIServlet in conjunction with the SSI servlet. (value stored
+     * as an object of type String)
+     */
+     public static final String SSI_FLAG_ATTR =
+         "org.apache.catalina.ssi.SSIServlet";
+
+
+    /**
+     * The subject under which the AccessControlContext is running.
+     */
+    public static final String SUBJECT_ATTR =
+        "javax.security.auth.subject";
+
+    
+    public static final String GSS_CREDENTIAL_ATTR =
+        "org.apache.catalina.realm.GSS_CREDENTIAL";
+
+
+    /**
+     * The master flag which controls strict servlet specification 
+     * compliance.
+     */
+    public static final boolean STRICT_SERVLET_COMPLIANCE =
+        Boolean.valueOf(System.getProperty("org.apache.catalina.STRICT_SERVLET_COMPLIANCE", "false")).booleanValue();
+
+
+    /**
+     * Has security been turned on?
+     */
+    public static final boolean IS_SECURITY_ENABLED =
+        (System.getSecurityManager() != null);
+    
+    /**
+     * 
+     */
+    public static final String ASYNC_SUPPORTED_ATTR = 
+        "org.apache.catalina.ASYNC_SUPPORTED";
+
+    
+    /**
+     * Default domain for MBeans if none can be determined
+     */
+    public static final String DEFAULT_MBEAN_DOMAIN = "Catalina";
+
+
+    /**
+     * 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/catalina/Group.java b/bundles/org.apache.tomcat/src/org/apache/catalina/Group.java
new file mode 100644
index 0000000..5c33c10
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/Group.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;
+
+
+import java.security.Principal;
+import java.util.Iterator;
+
+
+/**
+ * <p>Abstract representation of a group of {@link User}s in a
+ * {@link UserDatabase}.  Each user that is a member of this group
+ * inherits the {@link Role}s assigned to the group.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: Group.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ * @since 4.1
+ */
+
+public interface Group extends Principal {
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the description of this group.
+     */
+    public String getDescription();
+
+
+    /**
+     * Set the description of this group.
+     *
+     * @param description The new description
+     */
+    public void setDescription(String description);
+
+
+    /**
+     * Return the group name of this group, which must be unique
+     * within the scope of a {@link UserDatabase}.
+     */
+    public String getGroupname();
+
+
+    /**
+     * Set the group name of this group, which must be unique
+     * within the scope of a {@link UserDatabase}.
+     *
+     * @param groupname The new group name
+     */
+    public void setGroupname(String groupname);
+
+
+    /**
+     * Return the set of {@link Role}s assigned specifically to this group.
+     */
+    public Iterator<Role> getRoles();
+
+
+    /**
+     * Return the {@link UserDatabase} within which this Group is defined.
+     */
+    public UserDatabase getUserDatabase();
+
+
+    /**
+     * Return the set of {@link User}s that are members of this group.
+     */
+    public Iterator<User> getUsers();
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add a new {@link Role} to those assigned specifically to this group.
+     *
+     * @param role The new role
+     */
+    public void addRole(Role role);
+
+
+    /**
+     * Is this group specifically assigned the specified {@link Role}?
+     *
+     * @param role The role to check
+     */
+    public boolean isInRole(Role role);
+
+
+    /**
+     * Remove a {@link Role} from those assigned to this group.
+     *
+     * @param role The old role
+     */
+    public void removeRole(Role role);
+
+
+    /**
+     * Remove all {@link Role}s from those assigned to this group.
+     */
+    public void removeRoles();
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/Host.java b/bundles/org.apache.tomcat/src/org/apache/catalina/Host.java
new file mode 100644
index 0000000..015e71f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/Host.java
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import java.util.regex.Pattern;
+
+
+/**
+ * A <b>Host</b> is a Container that represents a virtual host in the
+ * Catalina servlet engine.  It is useful in the following types of scenarios:
+ * <ul>
+ * <li>You wish to use Interceptors that see every single request processed
+ *     by this particular virtual host.
+ * <li>You wish to run Catalina in with a standalone HTTP connector, but still
+ *     want support for multiple virtual hosts.
+ * </ul>
+ * In general, you would not use a Host when deploying Catalina connected
+ * to a web server (such as Apache), because the Connector will have
+ * utilized the web server's facilities to determine which Context (or
+ * perhaps even which Wrapper) should be utilized to process this request.
+ * <p>
+ * The parent Container attached to a Host is generally an Engine, but may
+ * be some other implementation, or may be omitted if it is not necessary.
+ * <p>
+ * The child containers attached to a Host are generally implementations
+ * of Context (representing an individual servlet context).
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: Host.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public interface Host extends Container {
+
+
+    // ----------------------------------------------------- Manifest Constants
+
+
+    /**
+     * The ContainerEvent event type sent when a new alias is added
+     * by <code>addAlias()</code>.
+     */
+    public static final String ADD_ALIAS_EVENT = "addAlias";
+
+
+    /**
+     * The ContainerEvent event type sent when an old alias is removed
+     * by <code>removeAlias()</code>.
+     */
+    public static final String REMOVE_ALIAS_EVENT = "removeAlias";
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the XML root for this Host.  This can be an absolute
+     * pathname, a relative pathname, or a URL.
+     * If null, defaults to
+     * ${catalina.base}/conf/&lt;engine name&gt;/&lt;host name&gt; directory
+     */
+    public String getXmlBase();
+    
+    /**
+     * Set the Xml root for this Host.  This can be an absolute
+     * pathname, a relative pathname, or a URL.
+     * If null, defaults to
+     * ${catalina.base}/conf/&lt;engine name&gt;/&lt;host name&gt; directory
+     * @param xmlBase The new XML root
+     */
+    public void setXmlBase(String xmlBase);
+
+        /**
+     * Return the application root for this Host.  This can be an absolute
+     * pathname, a relative pathname, or a URL.
+     */
+    public String getAppBase();
+
+
+    /**
+     * Set the application root for this Host.  This can be an absolute
+     * pathname, a relative pathname, or a URL.
+     *
+     * @param appBase The new application root
+     */
+    public void setAppBase(String appBase);
+
+
+    /**
+     * Return the value of the auto deploy flag.  If true, it indicates that 
+     * this host's child webapps should be discovered and automatically 
+     * deployed dynamically.
+     */
+    public boolean getAutoDeploy();
+
+
+    /**
+     * Set the auto deploy flag value for this host.
+     * 
+     * @param autoDeploy The new auto deploy flag
+     */
+    public void setAutoDeploy(boolean autoDeploy);
+
+
+    /**
+     * Return the Java class name of the context configuration class
+     * for new web applications.
+     */
+    public String getConfigClass();
+
+    
+    /**
+     * Set the Java class name of the context configuration class
+     * for new web applications.
+     *
+     * @param configClass The new context configuration class
+     */
+    public void setConfigClass(String configClass);
+
+        
+    /**
+     * Return the value of the deploy on startup flag.  If true, it indicates 
+     * that this host's child webapps should be discovered and automatically 
+     * deployed.
+     */
+    public boolean getDeployOnStartup();
+
+
+    /**
+     * Set the deploy on startup flag value for this host.
+     * 
+     * @param deployOnStartup The new deploy on startup flag
+     */
+    public void setDeployOnStartup(boolean deployOnStartup);
+
+
+    /**
+     * Return the regular expression that defines the files and directories in
+     * the host's appBase that will be ignored by the automatic deployment
+     * process.
+     */
+    public String getDeployIgnore();
+
+
+    /**
+     * Return the compiled regular expression that defines the files and
+     * directories in the host's appBase that will be ignored by the automatic
+     * deployment process.
+     */
+    public Pattern getDeployIgnorePattern();
+
+
+    /**
+     * Set the regular expression that defines the files and directories in
+     * the host's appBase that will be ignored by the automatic deployment
+     * process.
+     */
+    public void setDeployIgnore(String deployIgnore);
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add an alias name that should be mapped to this same Host.
+     *
+     * @param alias The alias to be added
+     */
+    public void addAlias(String alias);
+
+
+    /**
+     * Return the set of alias names for this Host.  If none are defined,
+     * a zero length array is returned.
+     */
+    public String[] findAliases();
+
+
+    /**
+     * Remove the specified alias name from the aliases for this Host.
+     *
+     * @param alias Alias name to be removed
+     */
+    public void removeAlias(String alias);
+
+    /**
+     * Returns true if the Host will attempt to create directories for appBase and xmlBase
+     * unless they already exist.
+     * @return true if the Host will attempt to create directories
+     */
+    public boolean getCreateDirs();
+    /**
+     * Set to true if the Host should attempt to create directories for xmlBase and appBase upon startup
+     * @param createDirs
+     */
+    public void setCreateDirs(boolean createDirs);
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/InstanceEvent.java b/bundles/org.apache.tomcat/src/org/apache/catalina/InstanceEvent.java
new file mode 100644
index 0000000..ffbb31b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/InstanceEvent.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;
+
+
+import java.util.EventObject;
+
+import javax.servlet.Filter;
+import javax.servlet.Servlet;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+
+/**
+ * General event for notifying listeners of significant events related to
+ * a specific instance of a Servlet, or a specific instance of a Filter,
+ * as opposed to the Wrapper component that manages it.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: InstanceEvent.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public final class InstanceEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+
+    /**
+     * The event indicating that the <code>init()</code> method is about
+     * to be called for this instance.
+     */
+    public static final String BEFORE_INIT_EVENT = "beforeInit";
+
+
+    /**
+     * The event indicating that the <code>init()</code> method has returned.
+     */
+    public static final String AFTER_INIT_EVENT = "afterInit";
+
+
+    /**
+     * The event indicating that the <code>service()</code> method is about
+     * to be called on a servlet.  The <code>servlet</code> property contains
+     * the servlet being called, and the <code>request</code> and
+     * <code>response</code> properties contain the current request and
+     * response being processed.
+     */
+    public static final String BEFORE_SERVICE_EVENT = "beforeService";
+
+
+    /**
+     * The event indicating that the <code>service()</code> method has
+     * returned.  The <code>servlet</code> property contains the servlet
+     * that was called, and the <code>request</code> and
+     * <code>response</code> properties contain the current request and
+     * response being processed.
+     */
+    public static final String AFTER_SERVICE_EVENT = "afterService";
+
+
+    /**
+     * The event indicating that the <code>destroy</code> method is about
+     * to be called for this instance.
+     */
+    public static final String BEFORE_DESTROY_EVENT = "beforeDestroy";
+
+
+    /**
+     * The event indicating that the <code>destroy()</code> method has
+     * returned.
+     */
+    public static final String AFTER_DESTROY_EVENT = "afterDestroy";
+
+
+    /**
+     * The event indicating that the <code>service()</code> method of a
+     * servlet accessed via a request dispatcher is about to be called.
+     * The <code>servlet</code> property contains a reference to the
+     * dispatched-to servlet instance, and the <code>request</code> and
+     * <code>response</code> properties contain the current request and
+     * response being processed.  The <code>wrapper</code> property will
+     * contain a reference to the dispatched-to Wrapper.
+     */
+    public static final String BEFORE_DISPATCH_EVENT = "beforeDispatch";
+
+
+    /**
+     * The event indicating that the <code>service()</code> method of a
+     * servlet accessed via a request dispatcher has returned.  The
+     * <code>servlet</code> property contains a reference to the
+     * dispatched-to servlet instance, and the <code>request</code> and
+     * <code>response</code> properties contain the current request and
+     * response being processed.  The <code>wrapper</code> property will
+     * contain a reference to the dispatched-to Wrapper.
+     */
+    public static final String AFTER_DISPATCH_EVENT = "afterDispatch";
+
+
+    /**
+     * The event indicating that the <code>doFilter()</code> method of a
+     * Filter is about to be called.  The <code>filter</code> property
+     * contains a reference to the relevant filter instance, and the
+     * <code>request</code> and <code>response</code> properties contain
+     * the current request and response being processed.
+     */
+    public static final String BEFORE_FILTER_EVENT = "beforeFilter";
+
+
+    /**
+     * The event indicating that the <code>doFilter()</code> method of a
+     * Filter has returned.  The <code>filter</code> property contains
+     * a reference to the relevant filter instance, and the
+     * <code>request</code> and <code>response</code> properties contain
+     * the current request and response being processed.
+     */
+    public static final String AFTER_FILTER_EVENT = "afterFilter";
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new InstanceEvent with the specified parameters.  This
+     * constructor is used for filter lifecycle events.
+     *
+     * @param wrapper Wrapper managing this servlet instance
+     * @param filter Filter instance for which this event occurred
+     * @param type Event type (required)
+     */
+    public InstanceEvent(Wrapper wrapper, Filter filter, String type) {
+
+      super(wrapper);
+      this.filter = filter;
+      this.servlet = null;
+      this.type = type;
+
+    }
+
+
+    /**
+     * Construct a new InstanceEvent with the specified parameters.  This
+     * constructor is used for filter lifecycle events.
+     *
+     * @param wrapper Wrapper managing this servlet instance
+     * @param filter Filter instance for which this event occurred
+     * @param type Event type (required)
+     * @param exception Exception that occurred
+     */
+    public InstanceEvent(Wrapper wrapper, Filter filter, String type,
+                         Throwable exception) {
+
+      super(wrapper);
+      this.filter = filter;
+      this.servlet = null;
+      this.type = type;
+      this.exception = exception;
+
+    }
+
+
+    /**
+     * Construct a new InstanceEvent with the specified parameters.  This
+     * constructor is used for filter processing events.
+     *
+     * @param wrapper Wrapper managing this servlet instance
+     * @param filter Filter instance for which this event occurred
+     * @param type Event type (required)
+     * @param request Servlet request we are processing
+     * @param response Servlet response we are processing
+     */
+    public InstanceEvent(Wrapper wrapper, Filter filter, String type,
+                         ServletRequest request, ServletResponse response) {
+
+      super(wrapper);
+      this.filter = filter;
+      this.servlet = null;
+      this.type = type;
+      this.request = request;
+      this.response = response;
+
+    }
+
+
+    /**
+     * Construct a new InstanceEvent with the specified parameters.  This
+     * constructor is used for filter processing events.
+     *
+     * @param wrapper Wrapper managing this servlet instance
+     * @param filter Filter instance for which this event occurred
+     * @param type Event type (required)
+     * @param request Servlet request we are processing
+     * @param response Servlet response we are processing
+     * @param exception Exception that occurred
+     */
+    public InstanceEvent(Wrapper wrapper, Filter filter, String type,
+                         ServletRequest request, ServletResponse response,
+                         Throwable exception) {
+
+      super(wrapper);
+      this.filter = filter;
+      this.servlet = null;
+      this.type = type;
+      this.request = request;
+      this.response = response;
+      this.exception = exception;
+
+    }
+
+
+    /**
+     * Construct a new InstanceEvent with the specified parameters.  This
+     * constructor is used for processing servlet lifecycle events.
+     *
+     * @param wrapper Wrapper managing this servlet instance
+     * @param servlet Servlet instance for which this event occurred
+     * @param type Event type (required)
+     */
+    public InstanceEvent(Wrapper wrapper, Servlet servlet, String type) {
+
+      super(wrapper);
+      this.filter = null;
+      this.servlet = servlet;
+      this.type = type;
+
+    }
+
+
+    /**
+     * Construct a new InstanceEvent with the specified parameters.  This
+     * constructor is used for processing servlet lifecycle events.
+     *
+     * @param wrapper Wrapper managing this servlet instance
+     * @param servlet Servlet instance for which this event occurred
+     * @param type Event type (required)
+     * @param exception Exception that occurred
+     */
+    public InstanceEvent(Wrapper wrapper, Servlet servlet, String type,
+                         Throwable exception) {
+
+      super(wrapper);
+      this.filter = null;
+      this.servlet = servlet;
+      this.type = type;
+      this.exception = exception;
+
+    }
+
+
+    /**
+     * Construct a new InstanceEvent with the specified parameters.  This
+     * constructor is used for processing servlet processing events.
+     *
+     * @param wrapper Wrapper managing this servlet instance
+     * @param servlet Servlet instance for which this event occurred
+     * @param type Event type (required)
+     * @param request Servlet request we are processing
+     * @param response Servlet response we are processing
+     */
+    public InstanceEvent(Wrapper wrapper, Servlet servlet, String type,
+                         ServletRequest request, ServletResponse response) {
+
+      super(wrapper);
+      this.filter = null;
+      this.servlet = servlet;
+      this.type = type;
+      this.request = request;
+      this.response = response;
+
+    }
+
+
+    /**
+     * Construct a new InstanceEvent with the specified parameters.  This
+     * constructor is used for processing servlet processing events.
+     *
+     * @param wrapper Wrapper managing this servlet instance
+     * @param servlet Servlet instance for which this event occurred
+     * @param type Event type (required)
+     * @param request Servlet request we are processing
+     * @param response Servlet response we are processing
+     * @param exception Exception that occurred
+     */
+    public InstanceEvent(Wrapper wrapper, Servlet servlet, String type,
+                         ServletRequest request, ServletResponse response,
+                         Throwable exception) {
+
+      super(wrapper);
+      this.filter = null;
+      this.servlet = servlet;
+      this.type = type;
+      this.request = request;
+      this.response = response;
+      this.exception = exception;
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The exception that was thrown during the processing being reported
+     * by this event (AFTER_INIT_EVENT, AFTER_SERVICE_EVENT, 
+     * AFTER_DESTROY_EVENT, AFTER_DISPATCH_EVENT, and AFTER_FILTER_EVENT only).
+     */
+    private Throwable exception = null;
+
+
+    /**
+     * The Filter instance for which this event occurred (BEFORE_FILTER_EVENT
+     * and AFTER_FILTER_EVENT only).
+     */
+    private transient Filter filter = null;
+
+
+    /**
+     * The servlet request being processed (BEFORE_FILTER_EVENT,
+     * AFTER_FILTER_EVENT, BEFORE_SERVICE_EVENT, and AFTER_SERVICE_EVENT).
+     */
+    private transient ServletRequest request = null;
+
+
+    /**
+     * The servlet response being processed (BEFORE_FILTER_EVENT,
+     * AFTER_FILTER_EVENT, BEFORE_SERVICE_EVENT, and AFTER_SERVICE_EVENT).
+     */
+    private transient ServletResponse response = null;
+
+
+    /**
+     * The Servlet instance for which this event occurred (not present on
+     * BEFORE_FILTER_EVENT or AFTER_FILTER_EVENT events).
+     */
+    private transient Servlet servlet = null;
+
+
+    /**
+     * The event type this instance represents.
+     */
+    private String type = null;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the exception that occurred during the processing
+     * that was reported by this event.
+     */
+    public Throwable getException() {
+
+        return (this.exception);
+
+    }
+
+
+    /**
+     * Return the filter instance for which this event occurred.
+     */
+    public Filter getFilter() {
+
+        return (this.filter);
+
+    }
+
+
+    /**
+     * Return the servlet request for which this event occurred.
+     */
+    public ServletRequest getRequest() {
+
+        return (this.request);
+
+    }
+
+
+    /**
+     * Return the servlet response for which this event occurred.
+     */
+    public ServletResponse getResponse() {
+
+        return (this.response);
+
+    }
+
+
+    /**
+     * Return the servlet instance for which this event occurred.
+     */
+    public Servlet getServlet() {
+
+        return (this.servlet);
+
+    }
+
+
+    /**
+     * Return the event type of this event.
+     */
+    public String getType() {
+
+        return (this.type);
+
+    }
+
+
+    /**
+     * Return the Wrapper managing the servlet instance for which this
+     * event occurred.
+     */
+    public Wrapper getWrapper() {
+
+        return (Wrapper) getSource();
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/InstanceListener.java b/bundles/org.apache.tomcat/src/org/apache/catalina/InstanceListener.java
new file mode 100644
index 0000000..bb368b8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/InstanceListener.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina;
+
+
+/**
+ * Interface defining a listener for significant events related to a
+ * specific servlet instance, rather than to the Wrapper component that
+ * is managing that instance.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: InstanceListener.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public interface InstanceListener {
+
+
+    /**
+     * Acknowledge the occurrence of the specified event.
+     *
+     * @param event InstanceEvent that has occurred
+     */
+    public void instanceEvent(InstanceEvent event);
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/Lifecycle.java b/bundles/org.apache.tomcat/src/org/apache/catalina/Lifecycle.java
new file mode 100644
index 0000000..e9ce781
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/Lifecycle.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.catalina;
+
+
+/**
+ * Common interface for component life cycle methods.  Catalina components
+ * may implement this interface (as well as the appropriate interface(s) for
+ * the functionality they support) in order to provide a consistent mechanism
+ * to start and stop the component.
+ * <br>
+ * The valid state transitions for components that support {@link Lifecycle}
+ * are:
+ * <pre>
+ *    init()
+ * NEW ->-- INITIALIZING
+ * |||           |                  --------------------<-----------------------
+ * |||           |auto              |                                          |
+ * |||          \|/    start()     \|/       auto          auto         stop() |
+ * |||      INITIALIZED -->-- STARTING_PREP -->- STARTING -->- STARTED -->---  |
+ * |||                              ^                             |         |  |
+ * |||        start()               |                             |         |  |
+ * ||----------->--------------------                             |         |  |
+ * ||                                                             |         |  |
+ * |---                auto                    auto               |         |  |
+ * |  |          ---------<----- MUST_STOP ---------------------<--         |  |
+ * |  |          |                                                          |  |
+ * |  |          ---------------------------<--------------------------------  ^
+ * |  |          |                                                             |
+ * |  |         \|/            auto                 auto              start()  |
+ * |  |     STOPPING_PREP ------>----- STOPPING ------>----- STOPPED ---->------
+ * |  |                                   ^                  |  |  ^
+ * |  |                  stop()           |                  |  |  |
+ * |  |          --------------------------                  |  |  |
+ * |  |          |                                  auto     |  |  |
+ * |  |          |                  MUST_DESTROY------<-------  |  |
+ * |  |          |                    |                         |  |
+ * |  |          |                    |auto                     |  |
+ * |  |          |    destroy()      \|/              destroy() |  |
+ * |  |       FAILED ---->------ DESTROYING ---<-----------------  |
+ * |  |                           ^     |                          |
+ * |  |        destroy()          |     |auto                      |
+ * |  -----------------------------    \|/                         |
+ * |                                 DESTROYED                     |
+ * |                                                               |
+ * |                            stop()                             |
+ * --->------------------------------>------------------------------
+ *   
+ * Any state can transition to FAILED.
+ * 
+ * Calling start() while a component is in states STARTING_PREP, STARTING or
+ * STARTED has no effect.
+ * 
+ * Calling start() while a component is in state NEW will cause init() to be
+ * called immediately after the start() method is entered.
+ * 
+ * Calling stop() while a component is in states STOPPING_PREP, STOPPING or
+ * STOPPED has no effect.
+ * 
+ * Calling stop() while a component is in state NEW transitions the component
+ * to STOPPED. This is typically encountered when a component fails to start and
+ * does not start all its sub-components. When the component is stopped, it will
+ * try to stop all sub-components - even those it didn't start.
+ * 
+ * MUST_STOP is used to indicate that the {@link #stop()} should be called on
+ * the component as soon as {@link #start()} exits. It is typically used when a
+ * component has failed to start.
+ * 
+ * MUST_DESTROY is used to indicate that the {@link #stop()} should be called on
+ * the component as soon as {@link #stop()} exits. It is typically used when a
+ * component is not designed to be restarted.
+ * 
+ * Attempting any other transition will throw {@link LifecycleException}.
+ * 
+ * </pre>
+ * The {@link LifecycleEvent}s fired during state changes are defined in the
+ * methods that trigger the changed. No {@link LifecycleEvent}s are fired if the
+ * attempted transition is not valid.
+ * 
+ * TODO: Not all components may transition from STOPPED to STARTING_PREP
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: Lifecycle.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ */
+public interface Lifecycle {
+
+
+    // ----------------------------------------------------- Manifest Constants
+
+
+    /**
+     * The LifecycleEvent type for the "component after init" event.
+     */
+    public static final String BEFORE_INIT_EVENT = "before_init";
+
+
+    /**
+     * The LifecycleEvent type for the "component after init" event.
+     */
+    public static final String AFTER_INIT_EVENT = "after_init";
+
+
+    /**
+     * The LifecycleEvent type for the "component start" event.
+     */
+    public static final String START_EVENT = "start";
+
+
+    /**
+     * The LifecycleEvent type for the "component before start" event.
+     */
+    public static final String BEFORE_START_EVENT = "before_start";
+
+
+    /**
+     * The LifecycleEvent type for the "component after start" event.
+     */
+    public static final String AFTER_START_EVENT = "after_start";
+
+
+    /**
+     * The LifecycleEvent type for the "component stop" event.
+     */
+    public static final String STOP_EVENT = "stop";
+
+
+    /**
+     * The LifecycleEvent type for the "component before stop" event.
+     */
+    public static final String BEFORE_STOP_EVENT = "before_stop";
+
+
+    /**
+     * The LifecycleEvent type for the "component after stop" event.
+     */
+    public static final String AFTER_STOP_EVENT = "after_stop";
+
+
+    /**
+     * The LifecycleEvent type for the "component after destroy" event.
+     */
+    public static final String AFTER_DESTROY_EVENT = "after_destroy";
+
+
+    /**
+     * The LifecycleEvent type for the "component before destroy" event.
+     */
+    public static final String BEFORE_DESTROY_EVENT = "before_destroy";
+
+
+    /**
+     * The LifecycleEvent type for the "periodic" event.
+     */
+    public static final String PERIODIC_EVENT = "periodic";
+
+
+    /**
+     * The LifecycleEvent type for the "configure_start" event. Used by those
+     * components that use a separate component to perform configuration and
+     * need to signal when configuration should be performed - usually after
+     * {@link #BEFORE_START_EVENT} and before {@link #START_EVENT}.
+     */
+    public static final String CONFIGURE_START_EVENT = "configure_start";
+
+    
+    /**
+     * The LifecycleEvent type for the "configure_stop" event. Used by those
+     * components that use a separate component to perform configuration and
+     * need to signal when de-configuration should be performed - usually after
+     * {@link #STOP_EVENT} and before {@link #AFTER_STOP_EVENT}.
+     */
+    public static final String CONFIGURE_STOP_EVENT = "configure_stop";
+
+    
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add a LifecycleEvent listener to this component.
+     *
+     * @param listener The listener to add
+     */
+    public void addLifecycleListener(LifecycleListener listener);
+
+
+    /**
+     * Get the life cycle listeners associated with this life cycle. If this 
+     * component has no listeners registered, a zero-length array is returned.
+     */
+    public LifecycleListener[] findLifecycleListeners();
+
+
+    /**
+     * Remove a LifecycleEvent listener from this component.
+     *
+     * @param listener The listener to remove
+     */
+    public void removeLifecycleListener(LifecycleListener listener);
+
+
+    /**
+     * Prepare the component for starting. This method should perform any
+     * initialization required post object creation. The following
+     * {@link LifecycleEvent}s will be fired in the following order:
+     * <ol>
+     *   <li>INIT_EVENT: On the successful completion of component
+     *                   initialization.</li>
+     * </ol>
+     * 
+     * @exception LifecycleException if this component detects a fatal error
+     *  that prevents this component from being used
+     */
+    public void init() throws LifecycleException;
+
+    /**
+     * Prepare for the beginning of active use of the public methods other than
+     * property getters/setters and life cycle methods of this component. This
+     * method should be called before any of the public methods other than
+     * property getters/setters and life cycle methods of this component are
+     * utilized. The following {@link LifecycleEvent}s will be fired in the
+     * following order:
+     * <ol>
+     *   <li>BEFORE_START_EVENT: At the beginning of the method. It is as this
+     *                           point the state transitions to
+     *                           {@link LifecycleState#STARTING_PREP}.</li>
+     *   <li>START_EVENT: During the method once it is safe to call start() for
+     *                    any child components. It is at this point that the
+     *                    state transitions to {@link LifecycleState#STARTING}
+     *                    and that the public methods other than property
+     *                    getters/setters and life cycle methods may be 
+     *                    used.</li>
+     *   <li>AFTER_START_EVENT: At the end of the method, immediately before it
+     *                          returns. It is at this point that the state
+     *                          transitions to {@link LifecycleState#STARTED}.
+     *                          </li>
+     * </ol>
+     *
+     * @exception LifecycleException if this component detects a fatal error
+     *  that prevents this component from being used
+     */
+    public void start() throws LifecycleException;
+
+
+    /**
+     * Gracefully terminate the active use of the public methods other than
+     * property getters/setters and life cycle methods of this component. Once
+     * the STOP_EVENT is fired, the public methods other than property
+     * getters/setters and life cycle methods should not be used. The following
+     * {@link LifecycleEvent}s will be fired in the following order:
+     * <ol>
+     *   <li>BEFORE_STOP_EVENT: At the beginning of the method. It is at this
+     *                          point that the state transitions to
+     *                          {@link LifecycleState#STOPPING_PREP}.</li>
+     *   <li>STOP_EVENT: During the method once it is safe to call stop() for
+     *                   any child components. It is at this point that the
+     *                   state transitions to {@link LifecycleState#STOPPING}
+     *                   and that the public methods other than property
+     *                   getters/setters and life cycle methods may no longer be
+     *                   used.</li>
+     *   <li>AFTER_STOP_EVENT: At the end of the method, immediately before it
+     *                         returns. It is at this point that the state
+     *                         transitions to {@link LifecycleState#STOPPED}.
+     *                         </li>
+     * </ol>
+     * 
+     * Note that if transitioning from {@link LifecycleState#FAILED} then the
+     * three events above will be fired but the component will transition
+     * directly from {@link LifecycleState#FAILED} to
+     * {@link LifecycleState#STOPPING}, bypassing
+     * {@link LifecycleState#STOPPING_PREP}
+     * 
+     * @exception LifecycleException if this component detects a fatal error
+     *  that needs to be reported
+     */
+    public void stop() throws LifecycleException;
+
+    /**
+     * Prepare to discard the object. The following {@link LifecycleEvent}s will
+     * be fired in the following order:
+     * <ol>
+     *   <li>DESTROY_EVENT: On the successful completion of component
+     *                      destruction.</li>
+     * </ol>
+     * 
+     * @exception LifecycleException if this component detects a fatal error
+     *  that prevents this component from being used
+     */
+    public void destroy() throws LifecycleException;
+
+
+    /**
+     * Obtain the current state of the source component.
+     * 
+     * @return The current state of the source component.
+     */
+    public LifecycleState getState();
+    
+    
+    /**
+     * Obtain a textual representation of the current component state. Useful
+     * for JMX.
+     */
+    public String getStateName();
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/LifecycleEvent.java b/bundles/org.apache.tomcat/src/org/apache/catalina/LifecycleEvent.java
new file mode 100644
index 0000000..c7d2371
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/LifecycleEvent.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.catalina;
+
+
+import java.util.EventObject;
+
+
+/**
+ * General event for notifying listeners of significant changes on a component
+ * that implements the Lifecycle interface.  In particular, this will be useful
+ * on Containers, where these events replace the ContextInterceptor concept in
+ * Tomcat 3.x.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: LifecycleEvent.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ */
+
+public final class LifecycleEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+
+    // ----------------------------------------------------------- Constructors
+
+    /**
+     * Construct a new LifecycleEvent with the specified parameters.
+     *
+     * @param lifecycle Component on which this event occurred
+     * @param type Event type (required)
+     * @param data Event data (if any)
+     */
+    public LifecycleEvent(Lifecycle lifecycle, String type, Object data) {
+
+        super(lifecycle);
+        this.type = type;
+        this.data = data;
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The event data associated with this event.
+     */
+    private Object data = null;
+
+
+    /**
+     * The event type this instance represents.
+     */
+    private String type = null;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the event data of this event.
+     */
+    public Object getData() {
+
+        return (this.data);
+
+    }
+
+
+    /**
+     * Return the Lifecycle on which this event occurred.
+     */
+    public Lifecycle getLifecycle() {
+
+        return (Lifecycle) getSource();
+
+    }
+
+
+    /**
+     * Return the event type of this event.
+     */
+    public String getType() {
+
+        return (this.type);
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/LifecycleException.java b/bundles/org.apache.tomcat/src/org/apache/catalina/LifecycleException.java
new file mode 100644
index 0000000..d8c7ae1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/LifecycleException.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;
+
+
+/**
+ * General purpose exception that is thrown to indicate a lifecycle related
+ * problem.  Such exceptions should generally be considered fatal to the
+ * operation of the application containing this component.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: LifecycleException.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ */
+
+public final class LifecycleException extends Exception {
+
+    private static final long serialVersionUID = 1L;
+
+    //------------------------------------------------------------ Constructors
+
+
+    /**
+     * Construct a new LifecycleException with no other information.
+     */
+    public LifecycleException() {
+        super();
+    }
+
+
+    /**
+     * Construct a new LifecycleException for the specified message.
+     *
+     * @param message Message describing this exception
+     */
+    public LifecycleException(String message) {
+        super(message);
+    }
+
+
+    /**
+     * Construct a new LifecycleException for the specified throwable.
+     *
+     * @param throwable Throwable that caused this exception
+     */
+    public LifecycleException(Throwable throwable) {
+        super(throwable);
+    }
+
+
+    /**
+     * Construct a new LifecycleException for the specified message
+     * and throwable.
+     *
+     * @param message Message describing this exception
+     * @param throwable Throwable that caused this exception
+     */
+    public LifecycleException(String message, Throwable throwable) {
+        super(message, throwable);
+    }
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/LifecycleListener.java b/bundles/org.apache.tomcat/src/org/apache/catalina/LifecycleListener.java
new file mode 100644
index 0000000..8621069
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/LifecycleListener.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;
+
+
+/**
+ * Interface defining a listener for significant events (including "component
+ * start" and "component stop" generated by a component that implements the
+ * Lifecycle interface. The listener will be fired after the associated state
+ * change has taken place.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: LifecycleListener.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public interface LifecycleListener {
+
+
+    /**
+     * Acknowledge the occurrence of the specified event.
+     *
+     * @param event LifecycleEvent that has occurred
+     */
+    public void lifecycleEvent(LifecycleEvent event);
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/LifecycleState.java b/bundles/org.apache.tomcat/src/org/apache/catalina/LifecycleState.java
new file mode 100644
index 0000000..46c80fc
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/LifecycleState.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.catalina;
+
+/**
+ * The list of valid states for components that implement {@link Lifecycle}.
+ * See {@link Lifecycle} for the state transition diagram.
+ */
+public enum LifecycleState {
+    NEW(false, null),
+    INITIALIZING(false, Lifecycle.BEFORE_INIT_EVENT),
+    INITIALIZED(false, Lifecycle.AFTER_INIT_EVENT),
+    STARTING_PREP(false, Lifecycle.BEFORE_START_EVENT),
+    STARTING(true, Lifecycle.START_EVENT),
+    STARTED(true, Lifecycle.AFTER_START_EVENT),
+    STOPPING_PREP(true, Lifecycle.BEFORE_STOP_EVENT),
+    STOPPING(false, Lifecycle.STOP_EVENT),
+    STOPPED(false, Lifecycle.AFTER_STOP_EVENT),
+    DESTROYING(false, Lifecycle.BEFORE_DESTROY_EVENT),
+    DESTROYED(false, Lifecycle.AFTER_DESTROY_EVENT),
+    FAILED(false, null),
+    MUST_STOP(true, null),
+    MUST_DESTROY(false, null);
+    
+    private final boolean available;
+    private final String lifecycleEvent;
+    
+    private LifecycleState(boolean available, String lifecycleEvent) {
+        this.available = available;
+        this.lifecycleEvent = lifecycleEvent;
+    }
+    
+    /**
+     * May the public methods other than property getters/setters and lifecycle
+     * methods be called for a component in this state? It returns
+     * <code>true</code> for any component in any of the following states:
+     * <ul>
+     * <li>{@link #STARTING}</li>
+     * <li>{@link #STARTED}</li>
+     * <li>{@link #STOPPING_PREP}</li>
+     * <li>{@link #MUST_STOP}</li>
+     * </ul>
+     */
+    public boolean isAvailable() {
+        return available;
+    }
+    
+    /**
+     * 
+     */
+    public String getLifecycleEvent() {
+        return lifecycleEvent;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/Loader.java b/bundles/org.apache.tomcat/src/org/apache/catalina/Loader.java
new file mode 100644
index 0000000..d83022e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/Loader.java
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+
+import java.beans.PropertyChangeListener;
+
+
+/**
+ * A <b>Loader</b> represents a Java ClassLoader implementation that can
+ * be used by a Container to load class files (within a repository associated
+ * with the Loader) that are designed to be reloaded upon request, as well as
+ * a mechanism to detect whether changes have occurred in the underlying
+ * repository.
+ * <p>
+ * In order for a <code>Loader</code> implementation to successfully operate
+ * with a <code>Context</code> implementation that implements reloading, it
+ * must obey the following constraints:
+ * <ul>
+ * <li>Must implement <code>Lifecycle</code> so that the Context can indicate
+ *     that a new class loader is required.
+ * <li>The <code>start()</code> method must unconditionally create a new
+ *     <code>ClassLoader</code> implementation.
+ * <li>The <code>stop()</code> method must throw away its reference to the
+ *     <code>ClassLoader</code> previously utilized, so that the class loader,
+ *     all classes loaded by it, and all objects of those classes, can be
+ *     garbage collected.
+ * <li>Must allow a call to <code>stop()</code> to be followed by a call to
+ *     <code>start()</code> on the same <code>Loader</code> instance.
+ * <li>Based on a policy chosen by the implementation, must call the
+ *     <code>Context.reload()</code> method on the owning <code>Context</code>
+ *     when a change to one or more of the class files loaded by this class
+ *     loader is detected.
+ * </ul>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: Loader.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public interface Loader {
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * 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.
+     */
+    public void backgroundProcess();
+
+
+    /**
+     * Return the Java class loader to be used by this Container.
+     */
+    public ClassLoader getClassLoader();
+
+
+    /**
+     * Return the Container with which this Loader has been associated.
+     */
+    public Container getContainer();
+
+
+    /**
+     * Set the Container with which this Loader has been associated.
+     *
+     * @param container The associated Container
+     */
+    public void setContainer(Container container);
+
+
+    /**
+     * Return the "follow standard delegation model" flag used to configure
+     * our ClassLoader.
+     */
+    public boolean getDelegate();
+
+
+    /**
+     * Set the "follow standard delegation model" flag used to configure
+     * our ClassLoader.
+     *
+     * @param delegate The new flag
+     */
+    public void setDelegate(boolean delegate);
+
+
+    /**
+     * Return descriptive information about this Loader implementation and
+     * the corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    public String getInfo();
+
+
+    /**
+     * Return the reloadable flag for this Loader.
+     */
+    public boolean getReloadable();
+
+
+    /**
+     * Set the reloadable flag for this Loader.
+     *
+     * @param reloadable The new reloadable flag
+     */
+    public void setReloadable(boolean reloadable);
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add a property change listener to this component.
+     *
+     * @param listener The listener to add
+     */
+    public void addPropertyChangeListener(PropertyChangeListener listener);
+
+
+    /**
+     * Add a new repository to the set of repositories for this class loader.
+     *
+     * @param repository Repository to be added
+     */
+    public void addRepository(String repository);
+
+
+    /**
+     * Return the set of repositories defined for this class loader.
+     * If none are defined, a zero-length array is returned.
+     */
+    public String[] findRepositories();
+
+
+    /**
+     * Has the internal repository associated with this Loader been modified,
+     * such that the loaded classes should be reloaded?
+     */
+    public boolean modified();
+
+
+    /**
+     * Remove a property change listener from this component.
+     *
+     * @param listener The listener to remove
+     */
+    public void removePropertyChangeListener(PropertyChangeListener listener);
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/Manager.java b/bundles/org.apache.tomcat/src/org/apache/catalina/Manager.java
new file mode 100644
index 0000000..bfe87b8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/Manager.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;
+
+
+import java.beans.PropertyChangeListener;
+import java.io.IOException;
+
+
+/**
+ * A <b>Manager</b> manages the pool of Sessions that are associated with a
+ * particular Container.  Different Manager implementations may support
+ * value-added features such as the persistent storage of session data,
+ * as well as migrating sessions for distributable web applications.
+ * <p>
+ * In order for a <code>Manager</code> implementation to successfully operate
+ * with a <code>Context</code> implementation that implements reloading, it
+ * must obey the following constraints:
+ * <ul>
+ * <li>Must implement <code>Lifecycle</code> so that the Context can indicate
+ *     that a restart is required.
+ * <li>Must allow a call to <code>stop()</code> to be followed by a call to
+ *     <code>start()</code> on the same <code>Manager</code> instance.
+ * </ul>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: Manager.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ */
+
+public interface Manager {
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the Container with which this Manager is associated.
+     */
+    public Container getContainer();
+
+
+    /**
+     * Set the Container with which this Manager is associated.
+     *
+     * @param container The newly associated Container
+     */
+    public void setContainer(Container container);
+
+
+    /**
+     * Return the distributable flag for the sessions supported by
+     * this Manager.
+     */
+    public boolean getDistributable();
+
+
+    /**
+     * Set the distributable flag for the sessions supported by this
+     * Manager.  If this flag is set, all user data objects added to
+     * sessions associated with this manager must implement Serializable.
+     *
+     * @param distributable The new distributable flag
+     */
+    public void setDistributable(boolean distributable);
+
+
+    /**
+     * Return descriptive information about this Manager implementation and
+     * the corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    public String getInfo();
+
+
+    /**
+     * Return the default maximum inactive interval (in seconds)
+     * for Sessions created by this Manager.
+     */
+    public int getMaxInactiveInterval();
+
+
+    /**
+     * Set the default maximum inactive interval (in seconds)
+     * for Sessions created by this Manager.
+     *
+     * @param interval The new default value
+     */
+    public void setMaxInactiveInterval(int interval);
+
+
+    /**
+     * Gets the session id length (in bytes) of Sessions created by
+     * this Manager.
+     *
+     * @return The session id length
+     */
+    public int getSessionIdLength();
+
+
+    /**
+     * Sets the session id length (in bytes) for Sessions created by this
+     * Manager.
+     *
+     * @param idLength The session id length
+     */
+    public void setSessionIdLength(int idLength);
+
+
+    /** 
+     * Returns the total number of sessions created by this manager.
+     *
+     * @return Total number of sessions created by this manager.
+     */
+    public long getSessionCounter();
+
+
+    /** 
+     * Sets the total number of sessions created by this manager.
+     *
+     * @param sessionCounter Total number of sessions created by this manager.
+     */
+    public void setSessionCounter(long sessionCounter);
+
+
+    /**
+     * Gets the maximum number of sessions that have been active at the same
+     * time.
+     *
+     * @return Maximum number of sessions that have been active at the same
+     * time
+     */
+    public int getMaxActive();
+
+
+    /**
+     * (Re)sets the maximum number of sessions that have been active at the
+     * same time.
+     *
+     * @param maxActive Maximum number of sessions that have been active at
+     * the same time.
+     */
+    public void setMaxActive(int maxActive);
+
+
+    /** 
+     * Gets the number of currently active sessions.
+     *
+     * @return Number of currently active sessions
+     */
+    public int getActiveSessions();
+
+
+    /**
+     * Gets the number of sessions that have expired.
+     *
+     * @return Number of sessions that have expired
+     */
+    public long getExpiredSessions();
+
+
+    /**
+     * Sets the number of sessions that have expired.
+     *
+     * @param expiredSessions Number of sessions that have expired
+     */
+    public void setExpiredSessions(long expiredSessions);
+
+
+    /**
+     * Gets the number of sessions that were not created because the maximum
+     * number of active sessions was reached.
+     *
+     * @return Number of rejected sessions
+     */
+    public int getRejectedSessions();
+
+
+    /**
+     * Gets the longest time (in seconds) that an expired session had been
+     * alive.
+     *
+     * @return Longest time (in seconds) that an expired session had been
+     * alive.
+     */
+    public int getSessionMaxAliveTime();
+
+
+    /**
+     * Sets the longest time (in seconds) that an expired session had been
+     * alive.
+     *
+     * @param sessionMaxAliveTime Longest time (in seconds) that an expired
+     * session had been alive.
+     */
+    public void setSessionMaxAliveTime(int sessionMaxAliveTime);
+
+
+    /**
+     * Gets the average time (in seconds) that expired sessions had been
+     * alive. This may be based on sample data.
+     * 
+     * @return Average time (in seconds) that expired sessions had been
+     * alive.
+     */
+    public int getSessionAverageAliveTime();
+
+    
+    /**
+     * Gets the current rate of session creation (in session per minute). This
+     * may be based on sample data.
+     * 
+     * @return  The current rate (in sessions per minute) of session creation
+     */
+    public int getSessionCreateRate();
+    
+
+    /**
+     * Gets the current rate of session expiration (in session per minute). This
+     * may be based on sample data
+     * 
+     * @return  The current rate (in sessions per minute) of session expiration
+     */
+    public int getSessionExpireRate();
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add this Session to the set of active Sessions for this Manager.
+     *
+     * @param session Session to be added
+     */
+    public void add(Session session);
+
+
+    /**
+     * Add a property change listener to this component.
+     *
+     * @param listener The listener to add
+     */
+    public void addPropertyChangeListener(PropertyChangeListener listener);
+
+
+    /**
+     * Change the session ID of the current session to a new randomly generated
+     * session ID.
+     * 
+     * @param session   The session to change the session ID for
+     */
+    public void changeSessionId(Session session);
+    
+    
+    /**
+     * Get a session from the recycled ones or create a new empty one.
+     * The PersistentManager manager does not need to create session data
+     * because it reads it from the Store.
+     */                                                                         
+    public Session createEmptySession();
+
+
+    /**
+     * Construct and return a new session object, based on the default
+     * settings specified by this Manager's properties.  The session
+     * id specified will be used as the session id.
+     * If a new session cannot be created for any reason, return 
+     * <code>null</code>.
+     * 
+     * @param sessionId The session id which should be used to create the
+     *  new session; if <code>null</code>, the session
+     *  id will be assigned by this method, and available via the getId()
+     *  method of the returned session.
+     * @exception IllegalStateException if a new session cannot be
+     *  instantiated for any reason
+     */
+    public Session createSession(String sessionId);
+
+
+    /**
+     * Return the active Session, associated with this Manager, with the
+     * specified session id (if any); otherwise return <code>null</code>.
+     *
+     * @param id The session id for the session to be returned
+     *
+     * @exception IllegalStateException if a new session cannot be
+     *  instantiated for any reason
+     * @exception IOException if an input/output error occurs while
+     *  processing this request
+     */
+    public Session findSession(String id) throws IOException;
+
+
+    /**
+     * Return the set of active Sessions associated with this Manager.
+     * If this Manager has no active Sessions, a zero-length array is returned.
+     */
+    public Session[] findSessions();
+
+
+    /**
+     * Load any currently active sessions that were previously unloaded
+     * to the appropriate persistence mechanism, if any.  If persistence is not
+     * supported, this method returns without doing anything.
+     *
+     * @exception ClassNotFoundException if a serialized class cannot be
+     *  found during the reload
+     * @exception IOException if an input/output error occurs
+     */
+    public void load() throws ClassNotFoundException, IOException;
+
+
+    /**
+     * Remove this Session from the active Sessions for this Manager.
+     *
+     * @param session Session to be removed
+     */
+    public void remove(Session session);
+
+
+    /**
+     * Remove this Session from the active Sessions for this Manager.
+     *
+     * @param session   Session to be removed
+     * @param update    Should the expiration statistics be updated
+     */
+    public void remove(Session session, boolean update);
+
+
+    /**
+     * Remove a property change listener from this component.
+     *
+     * @param listener The listener to remove
+     */
+    public void removePropertyChangeListener(PropertyChangeListener listener);
+
+
+    /**
+     * Save any currently active sessions in the appropriate persistence
+     * mechanism, if any.  If persistence is not supported, this method
+     * returns without doing anything.
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public void unload() throws IOException;
+    
+     /**
+      * This method will be invoked by the context/container on a periodic
+      * basis and allows the manager to implement
+      * a method that executes periodic tasks, such as expiring sessions etc.
+      */
+     public void backgroundProcess();
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/Pipeline.java b/bundles/org.apache.tomcat/src/org/apache/catalina/Pipeline.java
new file mode 100644
index 0000000..265659a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/Pipeline.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.catalina;
+
+
+/**
+ * <p>Interface describing a collection of Valves that should be executed
+ * in sequence when the <code>invoke()</code> method is invoked.  It is
+ * required that a Valve somewhere in the pipeline (usually the last one)
+ * must process the request and create the corresponding response, rather
+ * than trying to pass the request on.</p>
+ *
+ * <p>There is generally a single Pipeline instance associated with each
+ * Container.  The container's normal request processing functionality is
+ * generally encapsulated in a container-specific Valve, which should always
+ * be executed at the end of a pipeline.  To facilitate this, the
+ * <code>setBasic()</code> method is provided to set the Valve instance that
+ * will always be executed last.  Other Valves will be executed in the order
+ * that they were added, before the basic Valve is executed.</p>
+ *
+ * @author Craig R. McClanahan
+ * @author Peter Donald
+ * @version $Id: Pipeline.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public interface Pipeline {
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * <p>Return the Valve instance that has been distinguished as the basic
+     * Valve for this Pipeline (if any).
+     */
+    public Valve getBasic();
+
+
+    /**
+     * <p>Set the Valve instance that has been distinguished as the basic
+     * Valve for this Pipeline (if any).  Prior to setting the basic Valve,
+     * the Valve's <code>setContainer()</code> will be called, if it
+     * implements <code>Contained</code>, with the owning Container as an
+     * argument.  The method may throw an <code>IllegalArgumentException</code>
+     * if this Valve chooses not to be associated with this Container, or
+     * <code>IllegalStateException</code> if it is already associated with
+     * a different Container.</p>
+     *
+     * @param valve Valve to be distinguished as the basic Valve
+     */
+    public void setBasic(Valve valve);
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * <p>Add a new Valve to the end of the pipeline associated with this
+     * Container.  Prior to adding the Valve, the Valve's
+     * <code>setContainer()</code> method will be called, if it implements
+     * <code>Contained</code>, with the owning Container as an argument.
+     * The method may throw an
+     * <code>IllegalArgumentException</code> if this Valve chooses not to
+     * be associated with this Container, or <code>IllegalStateException</code>
+     * if it is already associated with a different Container.</p>
+     *
+     * <p>Implementation note: Implementations are expected to trigger the
+     * {@link Container#ADD_VALVE_EVENT} for the associated container if this
+     * call is successful.</p>
+     * 
+     * @param valve Valve to be added
+     *
+     * @exception IllegalArgumentException if this Container refused to
+     *  accept the specified Valve
+     * @exception IllegalArgumentException if the specified Valve refuses to be
+     *  associated with this Container
+     * @exception IllegalStateException if the specified Valve is already
+     *  associated with a different Container
+     */
+    public void addValve(Valve valve);
+
+
+    /**
+     * Return the set of Valves in the pipeline associated with this
+     * Container, including the basic Valve (if any).  If there are no
+     * such Valves, a zero-length array is returned.
+     */
+    public Valve[] getValves();
+
+
+    /**
+     * Remove the specified Valve from the pipeline associated with this
+     * Container, if it is found; otherwise, do nothing.  If the Valve is
+     * found and removed, the Valve's <code>setContainer(null)</code> method
+     * will be called if it implements <code>Contained</code>.
+     *
+     * <p>Implementation note: Implementations are expected to trigger the
+     * {@link Container#REMOVE_VALVE_EVENT} for the associated container if this
+     * call is successful.</p>
+     *
+     * @param valve Valve to be removed
+     */
+    public void removeValve(Valve valve);
+
+
+    /**
+     * <p>Return the Valve instance that has been distinguished as the basic
+     * Valve for this Pipeline (if any).
+     */
+    public Valve getFirst();
+    
+    /**
+     * Returns true if all the valves in this pipeline support async, false otherwise
+     * @return true if all the valves in this pipeline support async, false otherwise
+     */
+    public boolean isAsyncSupported();
+
+
+    /**
+     * Return the Container with which this Pipeline is associated.
+     */
+    public Container getContainer();
+
+
+    /**
+     * Set the Container with which this Pipeline is associated.
+     *
+     * @param container The new associated container
+     */
+    public void setContainer(Container container);
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/Realm.java b/bundles/org.apache.tomcat/src/org/apache/catalina/Realm.java
new file mode 100644
index 0000000..bb29f66
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/Realm.java
@@ -0,0 +1,204 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import java.beans.PropertyChangeListener;
+import java.io.IOException;
+import java.security.Principal;
+import java.security.cert.X509Certificate;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.deploy.SecurityConstraint;
+import org.ietf.jgss.GSSContext;
+/**
+ * A <b>Realm</b> is a read-only facade for an underlying security realm
+ * used to authenticate individual users, and identify the security roles
+ * associated with those users.  Realms can be attached at any Container
+ * level, but will typically only be attached to a Context, or higher level,
+ * Container.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: Realm.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public interface Realm {
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the Container with which this Realm has been associated.
+     */
+    public Container getContainer();
+
+
+    /**
+     * Set the Container with which this Realm has been associated.
+     *
+     * @param container The associated Container
+     */
+    public void setContainer(Container container);
+
+
+    /**
+     * Return descriptive information about this Realm implementation and
+     * the corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    public String getInfo();
+
+
+    // --------------------------------------------------------- Public Methods
+
+    
+    /**
+     * Add a property change listener to this component.
+     *
+     * @param listener The listener to add
+     */
+    public void addPropertyChangeListener(PropertyChangeListener listener);
+
+
+    /**
+     * Return the Principal associated with the specified username and
+     * credentials, if there is one; otherwise return <code>null</code>.
+     *
+     * @param username Username of the Principal to look up
+     * @param credentials Password or other credentials to use in
+     *  authenticating this username
+     */
+    public Principal authenticate(String username, String credentials);
+
+
+    /**
+     * Return the Principal associated with the specified username, which
+     * matches the digest calculated using the given parameters using the
+     * method described in RFC 2069; otherwise return <code>null</code>.
+     *
+     * @param username Username of the Principal to look up
+     * @param digest Digest which has been submitted by the client
+     * @param nonce Unique (or supposedly unique) token which has been used
+     * for this request
+     * @param realm Realm name
+     * @param md5a2 Second MD5 digest used to calculate the digest :
+     * MD5(Method + ":" + uri)
+     */
+    public Principal authenticate(String username, String digest,
+                                  String nonce, String nc, String cnonce,
+                                  String qop, String realm,
+                                  String md5a2);
+
+
+    /**
+     * Return the Principal associated with the specified chain of X509
+     * client certificates.  If there is none, return <code>null</code>.
+     *
+     * @param gssContext The gssContext processed by the {@link Authenticator}.
+     * @param storeCreds Should the realm attempt to store the delegated
+     *                   credentials in the returned Principal?
+     */
+    public Principal authenticate(GSSContext gssContext, boolean storeCreds);
+    
+    
+    /**
+     * Return the Principal associated with the specified {@link GSSContext}.
+     * If there is none, return <code>null</code>.
+     *
+     * @param gssContext Array of client certificates, with the first one in
+     *  the array being the certificate of the client itself.
+     */
+    public Principal authenticate(X509Certificate certs[]);
+    
+    
+    /**
+     * 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.
+     */
+    public void backgroundProcess();
+
+
+    /**
+     * Return the SecurityConstraints configured to guard the request URI for
+     * this request, or <code>null</code> if there is no such constraint.
+     *
+     * @param request Request we are processing
+     */
+    public SecurityConstraint [] findSecurityConstraints(Request request,
+                                                     Context context);
+    
+    
+    /**
+     * Perform access control based on the specified authorization constraint.
+     * Return <code>true</code> if this constraint is satisfied and processing
+     * should continue, or <code>false</code> otherwise.
+     *
+     * @param request Request we are processing
+     * @param response Response we are creating
+     * @param constraint Security constraint we are enforcing
+     * @param context The Context to which client of this class is attached.
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public boolean hasResourcePermission(Request request,
+                                         Response response,
+                                         SecurityConstraint [] constraint,
+                                         Context context)
+        throws IOException;
+    
+    
+    /**
+     * Return <code>true</code> if the specified Principal has the specified
+     * security role, within the context of this Realm; otherwise return
+     * <code>false</code>.
+     *
+     * @param wrapper wrapper context for evaluating role
+     * @param principal Principal for whom the role is to be checked
+     * @param role Security role to be checked
+     */
+    public boolean hasRole(Wrapper wrapper, Principal principal, String role);
+
+        /**
+     * Enforce any user data constraint required by the security constraint
+     * guarding this request URI.  Return <code>true</code> if this constraint
+     * was not violated and processing should continue, or <code>false</code>
+     * if we have created a response already.
+     *
+     * @param request Request we are processing
+     * @param response Response we are creating
+     * @param constraint Security constraint being checked
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public boolean hasUserDataPermission(Request request,
+                                         Response response,
+                                         SecurityConstraint []constraint)
+        throws IOException;
+    
+    /**
+     * Remove a property change listener from this component.
+     *
+     * @param listener The listener to remove
+     */
+    public void removePropertyChangeListener(PropertyChangeListener listener);
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/Role.java b/bundles/org.apache.tomcat/src/org/apache/catalina/Role.java
new file mode 100644
index 0000000..99dd69a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/Role.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;
+
+
+import java.security.Principal;
+
+
+/**
+ * <p>Abstract representation of a security role, suitable for use in
+ * environments like JAAS that want to deal with <code>Principals</code>.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: Role.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ * @since 4.1
+ */
+
+public interface Role extends Principal {
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the description of this role.
+     */
+    public String getDescription();
+
+
+    /**
+     * Set the description of this role.
+     *
+     * @param description The new description
+     */
+    public void setDescription(String description);
+
+
+    /**
+     * Return the role name of this role, which must be unique
+     * within the scope of a {@link UserDatabase}.
+     */
+    public String getRolename();
+
+
+    /**
+     * Set the role name of this role, which must be unique
+     * within the scope of a {@link UserDatabase}.
+     *
+     * @param rolename The new role name
+     */
+    public void setRolename(String rolename);
+
+
+    /**
+     * Return the {@link UserDatabase} within which this Role is defined.
+     */
+    public UserDatabase getUserDatabase();
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/Server.java b/bundles/org.apache.tomcat/src/org/apache/catalina/Server.java
new file mode 100644
index 0000000..6264566
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/Server.java
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import org.apache.catalina.deploy.NamingResources;
+import org.apache.catalina.startup.Catalina;
+
+/**
+ * A <code>Server</code> element represents the entire Catalina
+ * servlet container.  Its attributes represent the characteristics of
+ * the servlet container as a whole.  A <code>Server</code> may contain
+ * one or more <code>Services</code>, and the top level set of naming
+ * resources.
+ * <p>
+ * Normally, an implementation of this interface will also implement
+ * <code>Lifecycle</code>, such that when the <code>start()</code> and
+ * <code>stop()</code> methods are called, all of the defined
+ * <code>Services</code> are also started or stopped.
+ * <p>
+ * In between, the implementation must open a server socket on the port number
+ * specified by the <code>port</code> property.  When a connection is accepted,
+ * the first line is read and compared with the specified shutdown command.
+ * If the command matches, shutdown of the server is initiated.
+ * <p>
+ * <strong>NOTE</strong> - The concrete implementation of this class should
+ * register the (singleton) instance with the <code>ServerFactory</code>
+ * class in its constructor(s).
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: Server.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public interface Server extends Lifecycle {
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return descriptive information about this Server implementation and
+     * the corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    public String getInfo();
+
+
+    /**
+     * Return the global naming resources.
+     */
+    public NamingResources getGlobalNamingResources();
+
+
+    /**
+     * Set the global naming resources.
+     * 
+     * @param globalNamingResources The new global naming resources
+     */
+    public void setGlobalNamingResources
+        (NamingResources globalNamingResources);
+
+
+    /**
+     * Return the global naming resources context.
+     */
+    public javax.naming.Context getGlobalNamingContext();
+
+
+    /**
+     * Return the port number we listen to for shutdown commands.
+     */
+    public int getPort();
+
+
+    /**
+     * Set the port number we listen to for shutdown commands.
+     *
+     * @param port The new port number
+     */
+    public void setPort(int port);
+
+
+    /**
+     * Return the address on which we listen to for shutdown commands.
+     */
+    public String getAddress();
+
+
+    /**
+     * Set the address on which we listen to for shutdown commands.
+     *
+     * @param address The new address
+     */
+    public void setAddress(String address);
+
+
+    /**
+     * Return the shutdown command string we are waiting for.
+     */
+    public String getShutdown();
+
+
+    /**
+     * Set the shutdown command we are waiting for.
+     *
+     * @param shutdown The new shutdown command
+     */
+    public void setShutdown(String shutdown);
+
+    
+    /**
+     * Return the parent class loader for this component. If not set, return
+     * {@link #getCatalina()} {@link Catalina#getParentClassLoader()}. If
+     * catalina has not been set, return the system class loader.
+     */
+    public ClassLoader getParentClassLoader();
+
+
+    /**
+     * Set the parent class loader for this server.
+     *
+     * @param parent The new parent class loader
+     */
+    public void setParentClassLoader(ClassLoader parent);
+
+    
+    /**
+     * Return the outer Catalina startup/shutdown component if present.
+     */
+    public Catalina getCatalina();
+    
+    /**
+     * Set the outer Catalina startup/shutdown component if present.
+     */
+    public void setCatalina(Catalina catalina);
+    
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add a new Service to the set of defined Services.
+     *
+     * @param service The Service to be added
+     */
+    public void addService(Service service);
+
+
+    /**
+     * Wait until a proper shutdown command is received, then return.
+     */
+    public void await();
+
+
+    /**
+     * Return the specified Service (if it exists); otherwise return
+     * <code>null</code>.
+     *
+     * @param name Name of the Service to be returned
+     */
+    public Service findService(String name);
+
+
+    /**
+     * Return the set of Services defined within this Server.
+     */
+    public Service[] findServices();
+
+
+    /**
+     * Remove the specified Service from the set associated from this
+     * Server.
+     *
+     * @param service The Service to be removed
+     */
+    public void removeService(Service service);
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/Service.java b/bundles/org.apache.tomcat/src/org/apache/catalina/Service.java
new file mode 100644
index 0000000..e145b1a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/Service.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import org.apache.catalina.connector.Connector;
+
+/**
+ * A <strong>Service</strong> is a group of one or more
+ * <strong>Connectors</strong> that share a single <strong>Container</strong>
+ * to process their incoming requests.  This arrangement allows, for example,
+ * a non-SSL and SSL connector to share the same population of web apps.
+ * <p>
+ * A given JVM can contain any number of Service instances; however, they are
+ * completely independent of each other and share only the basic JVM facilities
+ * and classes on the system class path.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: Service.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ */
+
+public interface Service extends Lifecycle {
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the <code>Container</code> that handles requests for all
+     * <code>Connectors</code> associated with this Service.
+     */
+    public Container getContainer();
+
+    /**
+     * Set the <code>Container</code> that handles requests for all
+     * <code>Connectors</code> associated with this Service.
+     *
+     * @param container The new Container
+     */
+    public void setContainer(Container container);
+
+    /**
+     * Return descriptive information about this Service implementation and
+     * the corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    public String getInfo();
+
+    /**
+     * Return the name of this Service.
+     */
+    public String getName();
+
+    /**
+     * Set the name of this Service.
+     *
+     * @param name The new service name
+     */
+    public void setName(String name);
+
+    /**
+     * Return the <code>Server</code> with which we are associated (if any).
+     */
+    public Server getServer();
+
+    /**
+     * Set the <code>Server</code> with which we are associated (if any).
+     *
+     * @param server The server that owns this Service
+     */
+    public void setServer(Server server);
+
+    /**
+     * Return the parent class loader for this component. If not set, return
+     * {@link #getServer()} {@link Server#getParentClassLoader()}. If no server
+     * has been set, return the system class loader.
+     */
+    public ClassLoader getParentClassLoader();
+
+    /**
+     * Set the parent class loader for this service.
+     *
+     * @param parent The new parent class loader
+     */
+    public void setParentClassLoader(ClassLoader parent);
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add a new Connector to the set of defined Connectors, and associate it
+     * with this Service's Container.
+     *
+     * @param connector The Connector to be added
+     */
+    public void addConnector(Connector connector);
+
+    /**
+     * Find and return the set of Connectors associated with this Service.
+     */
+    public Connector[] findConnectors();
+
+    /**
+     * Remove the specified Connector from the set associated from this
+     * Service.  The removed Connector will also be disassociated from our
+     * Container.
+     *
+     * @param connector The Connector to be removed
+     */
+    public void removeConnector(Connector connector);
+
+    /**
+     * Adds a named executor to the service
+     * @param ex Executor
+     */
+    public void addExecutor(Executor ex);
+
+    /**
+     * Retrieves all executors
+     * @return Executor[]
+     */
+    public Executor[] findExecutors();
+
+    /**
+     * Retrieves executor by name, null if not found
+     * @param name String
+     * @return Executor
+     */
+    public Executor getExecutor(String name);
+    
+    /**
+     * Removes an executor from the service
+     * @param ex Executor
+     */
+    public void removeExecutor(Executor ex);
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/Session.java b/bundles/org.apache.tomcat/src/org/apache/catalina/Session.java
new file mode 100644
index 0000000..44f007b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/Session.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 org.apache.catalina;
+
+
+import java.security.Principal;
+import java.util.Iterator;
+
+import javax.servlet.http.HttpSession;
+
+
+/**
+ * A <b>Session</b> is the Catalina-internal facade for an
+ * <code>HttpSession</code> that is used to maintain state information
+ * between requests for a particular user of a web application.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: Session.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public interface Session {
+
+
+    // ----------------------------------------------------- Manifest Constants
+
+
+    /**
+     * The SessionEvent event type when a session is created.
+     */
+    public static final String SESSION_CREATED_EVENT = "createSession";
+
+
+    /**
+     * The SessionEvent event type when a session is destroyed.
+     */
+    public static final String SESSION_DESTROYED_EVENT = "destroySession";
+
+
+    /**
+     * The SessionEvent event type when a session is activated.
+     */
+    public static final String SESSION_ACTIVATED_EVENT = "activateSession";
+
+
+    /**
+     * The SessionEvent event type when a session is passivated.
+     */
+    public static final String SESSION_PASSIVATED_EVENT = "passivateSession";
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the authentication type used to authenticate our cached
+     * Principal, if any.
+     */
+    public String getAuthType();
+
+
+    /**
+     * Set the authentication type used to authenticate our cached
+     * Principal, if any.
+     *
+     * @param authType The new cached authentication type
+     */
+    public void setAuthType(String authType);
+
+
+    /**
+     * Return the creation time for this session.
+     */
+    public long getCreationTime();
+
+
+    /**
+     * Return the creation time for this session, bypassing the session validity
+     * checks.
+     */
+    public long getCreationTimeInternal();
+
+
+    /**
+     * Set the creation time for this session.  This method is called by the
+     * Manager when an existing Session instance is reused.
+     *
+     * @param time The new creation time
+     */
+    public void setCreationTime(long time);
+
+
+    /**
+     * Return the session identifier for this session.
+     */
+    public String getId();
+
+
+    /**
+     * Return the session identifier for this session.
+     */
+    public String getIdInternal();
+
+
+    /**
+     * Set the session identifier for this session.
+     *
+     * @param id The new session identifier
+     */
+    public void setId(String id);
+
+
+    /**
+     * Return descriptive information about this Session implementation and
+     * the corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    public String getInfo();
+
+
+    /**
+     * Return the last time the client sent a request associated with this
+     * session, as the number of milliseconds since midnight, January 1, 1970
+     * GMT.  Actions that your application takes, such as getting or setting
+     * a value associated with the session, do not affect the access time.
+     * This one gets updated whenever a request starts.
+     */
+    public long getThisAccessedTime();
+
+    /**
+     * Return the last client access time without invalidation check
+     * @see #getThisAccessedTime()
+     */
+    public long getThisAccessedTimeInternal();
+
+    /**
+     * Return the last time the client sent a request associated with this
+     * session, as the number of milliseconds since midnight, January 1, 1970
+     * GMT.  Actions that your application takes, such as getting or setting
+     * a value associated with the session, do not affect the access time.
+     * This one gets updated whenever a request finishes.
+     */
+    public long getLastAccessedTime();
+
+    /**
+     * Return the last client access time without invalidation check
+     * @see #getLastAccessedTime()
+     */
+    public long getLastAccessedTimeInternal();
+
+    /**
+     * Return the Manager within which this Session is valid.
+     */
+    public Manager getManager();
+
+
+    /**
+     * Set the Manager within which this Session is valid.
+     *
+     * @param manager The new Manager
+     */
+    public void setManager(Manager manager);
+
+
+    /**
+     * Return the maximum time interval, in seconds, between client requests
+     * before the servlet container will invalidate the session.  A negative
+     * time indicates that the session should never time out.
+     */
+    public int getMaxInactiveInterval();
+
+
+    /**
+     * Set the maximum time interval, in seconds, between client requests
+     * before the servlet container will invalidate the session.  A negative
+     * time indicates that the session should never time out.
+     *
+     * @param interval The new maximum interval
+     */
+    public void setMaxInactiveInterval(int interval);
+
+
+    /**
+     * Set the <code>isNew</code> flag for this session.
+     *
+     * @param isNew The new value for the <code>isNew</code> flag
+     */
+    public void setNew(boolean isNew);
+
+
+    /**
+     * Return the authenticated Principal that is associated with this Session.
+     * This provides an <code>Authenticator</code> with a means to cache a
+     * previously authenticated Principal, and avoid potentially expensive
+     * <code>Realm.authenticate()</code> calls on every request.  If there
+     * is no current associated Principal, return <code>null</code>.
+     */
+    public Principal getPrincipal();
+
+
+    /**
+     * Set the authenticated Principal that is associated with this Session.
+     * This provides an <code>Authenticator</code> with a means to cache a
+     * previously authenticated Principal, and avoid potentially expensive
+     * <code>Realm.authenticate()</code> calls on every request.
+     *
+     * @param principal The new Principal, or <code>null</code> if none
+     */
+    public void setPrincipal(Principal principal);
+
+
+    /**
+     * Return the <code>HttpSession</code> for which this object
+     * is the facade.
+     */
+    public HttpSession getSession();
+
+
+    /**
+     * Set the <code>isValid</code> flag for this session.
+     *
+     * @param isValid The new value for the <code>isValid</code> flag
+     */
+    public void setValid(boolean isValid);
+
+
+    /**
+     * Return the <code>isValid</code> flag for this session.
+     */
+    public boolean isValid();
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Update the accessed time information for this session.  This method
+     * should be called by the context when a request comes in for a particular
+     * session, even if the application does not reference it.
+     */
+    public void access();
+
+
+    /**
+     * Add a session event listener to this component.
+     */
+    public void addSessionListener(SessionListener listener);
+
+
+    /**
+     * End access to the session.
+     */
+    public void endAccess();
+
+
+    /**
+     * Perform the internal processing required to invalidate this session,
+     * without triggering an exception if the session has already expired.
+     */
+    public void expire();
+
+
+    /**
+     * Return the object bound with the specified name to the internal notes
+     * for this session, or <code>null</code> if no such binding exists.
+     *
+     * @param name Name of the note to be returned
+     */
+    public Object getNote(String name);
+
+
+    /**
+     * Return an Iterator containing the String names of all notes bindings
+     * that exist for this session.
+     */
+    public Iterator<String> getNoteNames();
+
+
+    /**
+     * Release all object references, and initialize instance variables, in
+     * preparation for reuse of this object.
+     */
+    public void recycle();
+
+
+    /**
+     * Remove any object bound to the specified name in the internal notes
+     * for this session.
+     *
+     * @param name Name of the note to be removed
+     */
+    public void removeNote(String name);
+
+
+    /**
+     * Remove a session event listener from this component.
+     */
+    public void removeSessionListener(SessionListener listener);
+
+
+    /**
+     * Bind an object to a specified name in the internal notes associated
+     * with this session, replacing any existing binding for this name.
+     *
+     * @param name Name to which the object should be bound
+     * @param value Object to be bound to the specified name
+     */
+    public void setNote(String name, Object value);
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/SessionEvent.java b/bundles/org.apache.tomcat/src/org/apache/catalina/SessionEvent.java
new file mode 100644
index 0000000..daffa09
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/SessionEvent.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.catalina;
+
+
+import java.util.EventObject;
+
+
+/**
+ * General event for notifying listeners of significant changes on a Session.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: SessionEvent.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public final class SessionEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+
+    /**
+     * The event data associated with this event.
+     */
+    private Object data = null;
+
+
+    /**
+     * The Session on which this event occurred.
+     */
+    private Session session = null;
+
+
+    /**
+     * The event type this instance represents.
+     */
+    private String type = null;
+
+
+    /**
+     * Construct a new SessionEvent with the specified parameters.
+     *
+     * @param session Session on which this event occurred
+     * @param type Event type
+     * @param data Event data
+     */
+    public SessionEvent(Session session, String type, Object data) {
+
+        super(session);
+        this.session = session;
+        this.type = type;
+        this.data = data;
+
+    }
+
+
+    /**
+     * Return the event data of this event.
+     */
+    public Object getData() {
+
+        return (this.data);
+
+    }
+
+
+    /**
+     * Return the Session on which this event occurred.
+     */
+    public Session getSession() {
+
+        return (this.session);
+
+    }
+
+
+    /**
+     * Return the event type of this event.
+     */
+    public String getType() {
+
+        return (this.type);
+
+    }
+
+
+    /**
+     * Return a string representation of this event.
+     */
+    @Override
+    public String toString() {
+
+        return ("SessionEvent['" + getSession() + "','" +
+                getType() + "']");
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/SessionListener.java b/bundles/org.apache.tomcat/src/org/apache/catalina/SessionListener.java
new file mode 100644
index 0000000..1deb11f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/SessionListener.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina;
+
+import java.util.EventListener;
+
+
+/**
+ * Interface defining a listener for significant Session generated events.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: SessionListener.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ */
+
+public interface SessionListener extends EventListener {
+
+
+    /**
+     * Acknowledge the occurrence of the specified event.
+     *
+     * @param event SessionEvent that has occurred
+     */
+    public void sessionEvent(SessionEvent event);
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/Store.java b/bundles/org.apache.tomcat/src/org/apache/catalina/Store.java
new file mode 100644
index 0000000..70cebc6
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/Store.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+
+import java.beans.PropertyChangeListener;
+import java.io.IOException;
+
+
+/**
+ * A <b>Store</b> is the abstraction of a Catalina component that provides
+ * persistent storage and loading of Sessions and their associated user data.
+ * Implementations are free to save and load the Sessions to any media they
+ * wish, but it is assumed that saved Sessions are persistent across
+ * server or context restarts.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: Store.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public interface Store {
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return descriptive information about this Store implementation and
+     * the corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    public String getInfo();
+
+
+    /**
+     * Return the Manager instance associated with this Store.
+     */
+    public Manager getManager();
+
+
+    /**
+     * Set the Manager associated with this Store.
+     *
+     * @param manager The Manager which will use this Store.
+     */
+    public void setManager(Manager manager);
+
+
+    /**
+     * Return the number of Sessions present in this Store.
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public int getSize() throws IOException;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add a property change listener to this component.
+     *
+     * @param listener The listener to add
+     */
+    public void addPropertyChangeListener(PropertyChangeListener listener);
+
+
+    /**
+     * Return an array containing the session identifiers of all Sessions
+     * currently saved in this Store.  If there are no such Sessions, a
+     * zero-length array is returned.
+     *
+     * @exception IOException if an input/output error occurred
+     */
+    public String[] keys() throws IOException;
+
+
+    /**
+     * Load and return the Session associated with the specified session
+     * identifier from this Store, without removing it.  If there is no
+     * such stored Session, return <code>null</code>.
+     *
+     * @param id Session identifier of the session to load
+     *
+     * @exception ClassNotFoundException if a deserialization error occurs
+     * @exception IOException if an input/output error occurs
+     */
+    public Session load(String id)
+        throws ClassNotFoundException, IOException;
+
+
+    /**
+     * Remove the Session with the specified session identifier from
+     * this Store, if present.  If no such Session is present, this method
+     * takes no action.
+     *
+     * @param id Session identifier of the Session to be removed
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public void remove(String id) throws IOException;
+
+
+    /**
+     * Remove all Sessions from this Store.
+     */
+    public void clear() throws IOException;
+
+
+    /**
+     * Remove a property change listener from this component.
+     *
+     * @param listener The listener to remove
+     */
+    public void removePropertyChangeListener(PropertyChangeListener listener);
+
+
+    /**
+     * Save the specified Session into this Store.  Any previously saved
+     * information for the associated session identifier is replaced.
+     *
+     * @param session Session to be saved
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public void save(Session session) throws IOException;
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/User.java b/bundles/org.apache.tomcat/src/org/apache/catalina/User.java
new file mode 100644
index 0000000..0a0b353
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/User.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.catalina;
+
+
+import java.security.Principal;
+import java.util.Iterator;
+
+
+/**
+ * <p>Abstract representation of a user in a {@link UserDatabase}.  Each user
+ * is optionally associated with a set of {@link Group}s through which he or
+ * she inherits additional security roles, and is optionally assigned a set
+ * of specific {@link Role}s.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: User.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ * @since 4.1
+ */
+
+public interface User extends Principal {
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the full name of this user.
+     */
+    public String getFullName();
+
+
+    /**
+     * Set the full name of this user.
+     *
+     * @param fullName The new full name
+     */
+    public void setFullName(String fullName);
+
+
+    /**
+     * Return the set of {@link Group}s to which this user belongs.
+     */
+    public Iterator<Group> getGroups();
+
+
+    /**
+     * Return the logon password of this user, optionally prefixed with the
+     * identifier of an encoding scheme surrounded by curly braces, such as
+     * <code>{md5}xxxxx</code>.
+     */
+    public String getPassword();
+
+
+    /**
+     * Set the logon password of this user, optionally prefixed with the
+     * identifier of an encoding scheme surrounded by curly braces, such as
+     * <code>{md5}xxxxx</code>.
+     *
+     * @param password The new logon password
+     */
+    public void setPassword(String password);
+
+
+    /**
+     * Return the set of {@link Role}s assigned specifically to this user.
+     */
+    public Iterator<Role> getRoles();
+
+
+    /**
+     * Return the {@link UserDatabase} within which this User is defined.
+     */
+    public UserDatabase getUserDatabase();
+
+
+    /**
+     * Return the logon username of this user, which must be unique
+     * within the scope of a {@link UserDatabase}.
+     */
+    public String getUsername();
+
+
+    /**
+     * Set the logon username of this user, which must be unique within
+     * the scope of a {@link UserDatabase}.
+     *
+     * @param username The new logon username
+     */
+    public void setUsername(String username);
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add a new {@link Group} to those this user belongs to.
+     *
+     * @param group The new group
+     */
+    public void addGroup(Group group);
+
+
+    /**
+     * Add a {@link Role} to those assigned specifically to this user.
+     *
+     * @param role The new role
+     */
+    public void addRole(Role role);
+
+
+    /**
+     * Is this user in the specified {@link Group}?
+     *
+     * @param group The group to check
+     */
+    public boolean isInGroup(Group group);
+
+
+    /**
+     * Is this user specifically assigned the specified {@link Role}?  This
+     * method does <strong>NOT</strong> check for roles inherited based on
+     * {@link Group} membership.
+     *
+     * @param role The role to check
+     */
+    public boolean isInRole(Role role);
+
+
+    /**
+     * Remove a {@link Group} from those this user belongs to.
+     *
+     * @param group The old group
+     */
+    public void removeGroup(Group group);
+
+
+    /**
+     * Remove all {@link Group}s from those this user belongs to.
+     */
+    public void removeGroups();
+
+
+    /**
+     * Remove a {@link Role} from those assigned to this user.
+     *
+     * @param role The old role
+     */
+    public void removeRole(Role role);
+
+
+    /**
+     * Remove all {@link Role}s from those assigned to this user.
+     */
+    public void removeRoles();
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/UserDatabase.java b/bundles/org.apache.tomcat/src/org/apache/catalina/UserDatabase.java
new file mode 100644
index 0000000..39dfe1c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/UserDatabase.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.catalina;
+
+
+import java.util.Iterator;
+
+
+/**
+ * <p>Abstract representation of a database of {@link User}s and
+ * {@link Group}s that can be maintained by an application,
+ * along with definitions of corresponding {@link Role}s, and
+ * referenced by a {@link Realm} for authentication and access control.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: UserDatabase.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ * @since 4.1
+ */
+
+public interface UserDatabase {
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the set of {@link Group}s defined in this user database.
+     */
+    public Iterator<Group> getGroups();
+
+
+    /**
+     * Return the unique global identifier of this user database.
+     */
+    public String getId();
+
+
+    /**
+     * Return the set of {@link Role}s defined in this user database.
+     */
+    public Iterator<Role> getRoles();
+
+
+    /**
+     * Return the set of {@link User}s defined in this user database.
+     */
+    public Iterator<User> getUsers();
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Finalize access to this user database.
+     *
+     * @exception Exception if any exception is thrown during closing
+     */
+    public void close() throws Exception;
+
+
+    /**
+     * Create and return a new {@link Group} defined in this user database.
+     *
+     * @param groupname The group name of the new group (must be unique)
+     * @param description The description of this group
+     */
+    public Group createGroup(String groupname, String description);
+
+
+    /**
+     * Create and return a new {@link Role} defined in this user database.
+     *
+     * @param rolename The role name of the new role (must be unique)
+     * @param description The description of this role
+     */
+    public Role createRole(String rolename, String description);
+
+
+    /**
+     * Create and return a new {@link User} defined in this user database.
+     *
+     * @param username The logon username of the new user (must be unique)
+     * @param password The logon password of the new user
+     * @param fullName The full name of the new user
+     */
+    public User createUser(String username, String password,
+                           String fullName);
+
+
+    /**
+     * Return the {@link Group} with the specified group name, if any;
+     * otherwise return <code>null</code>.
+     *
+     * @param groupname Name of the group to return
+     */
+    public Group findGroup(String groupname);
+
+
+    /**
+     * Return the {@link Role} with the specified role name, if any;
+     * otherwise return <code>null</code>.
+     *
+     * @param rolename Name of the role to return
+     */
+    public Role findRole(String rolename);
+
+
+    /**
+     * Return the {@link User} with the specified user name, if any;
+     * otherwise return <code>null</code>.
+     *
+     * @param username Name of the user to return
+     */
+    public User findUser(String username);
+
+
+    /**
+     * Initialize access to this user database.
+     *
+     * @exception Exception if any exception is thrown during opening
+     */
+    public void open() throws Exception;
+
+
+    /**
+     * Remove the specified {@link Group} from this user database.
+     *
+     * @param group The group to be removed
+     */
+    public void removeGroup(Group group);
+
+
+    /**
+     * Remove the specified {@link Role} from this user database.
+     *
+     * @param role The role to be removed
+     */
+    public void removeRole(Role role);
+
+
+    /**
+     * Remove the specified {@link User} from this user database.
+     *
+     * @param user The user to be removed
+     */
+    public void removeUser(User user);
+
+
+    /**
+     * Save any updated information to the persistent storage location for
+     * this user database.
+     *
+     * @exception Exception if any exception is thrown during saving
+     */
+    public void save() throws Exception;
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/Valve.java b/bundles/org.apache.tomcat/src/org/apache/catalina/Valve.java
new file mode 100644
index 0000000..49a5572
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/Valve.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.catalina;
+
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+
+import org.apache.catalina.comet.CometEvent;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+
+
+/**
+ * <p>A <b>Valve</b> is a request processing component associated with a
+ * particular Container.  A series of Valves are generally associated with
+ * each other into a Pipeline.  The detailed contract for a Valve is included
+ * in the description of the <code>invoke()</code> method below.</p>
+ *
+ * <b>HISTORICAL NOTE</b>:  The "Valve" name was assigned to this concept
+ * because a valve is what you use in a real world pipeline to control and/or
+ * modify flows through it.
+ *
+ * @author Craig R. McClanahan
+ * @author Gunnar Rjnning
+ * @author Peter Donald
+ * @version $Id: Valve.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public interface Valve {
+
+
+    //-------------------------------------------------------------- Properties
+
+
+    /**
+     * Return descriptive information about this Valve implementation.
+     */
+    public String getInfo();
+
+
+    /**
+     * Return the next Valve in the pipeline containing this Valve, if any.
+     */
+    public Valve getNext();
+
+
+    /**
+     * Set the next Valve in the pipeline containing this Valve.
+     *
+     * @param valve The new next valve, or <code>null</code> if none
+     */
+    public void setNext(Valve valve);
+
+
+    //---------------------------------------------------------- Public Methods
+
+
+    /**
+     * 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.
+     */
+    public void backgroundProcess();
+
+
+    /**
+     * <p>Perform request processing as required by this Valve.</p>
+     *
+     * <p>An individual Valve <b>MAY</b> perform the following actions, in
+     * the specified order:</p>
+     * <ul>
+     * <li>Examine and/or modify the properties of the specified Request and
+     *     Response.
+     * <li>Examine the properties of the specified Request, completely generate
+     *     the corresponding Response, and return control to the caller.
+     * <li>Examine the properties of the specified Request and Response, wrap
+     *     either or both of these objects to supplement their functionality,
+     *     and pass them on.
+     * <li>If the corresponding Response was not generated (and control was not
+     *     returned, call the next Valve in the pipeline (if there is one) by
+     *     executing <code>getNext().invoke()</code>.
+     * <li>Examine, but not modify, the properties of the resulting Response
+     *     (which was created by a subsequently invoked Valve or Container).
+     * </ul>
+     *
+     * <p>A Valve <b>MUST NOT</b> do any of the following things:</p>
+     * <ul>
+     * <li>Change request properties that have already been used to direct
+     *     the flow of processing control for this request (for instance,
+     *     trying to change the virtual host to which a Request should be
+     *     sent from a pipeline attached to a Host or Context in the
+     *     standard implementation).
+     * <li>Create a completed Response <strong>AND</strong> pass this
+     *     Request and Response on to the next Valve in the pipeline.
+     * <li>Consume bytes from the input stream associated with the Request,
+     *     unless it is completely generating the response, or wrapping the
+     *     request before passing it on.
+     * <li>Modify the HTTP headers included with the Response after the
+     *     <code>getNext().invoke()</code> method has returned.
+     * <li>Perform any actions on the output stream associated with the
+     *     specified Response after the <code>getNext().invoke()</code> method has
+     *     returned.
+     * </ul>
+     *
+     * @param request The servlet request to be processed
+     * @param response The servlet response to be created
+     *
+     * @exception IOException if an input/output error occurs, or is thrown
+     *  by a subsequently invoked Valve, Filter, or Servlet
+     * @exception ServletException if a servlet error occurs, or is thrown
+     *  by a subsequently invoked Valve, Filter, or Servlet
+     */
+    public void invoke(Request request, Response response)
+        throws IOException, ServletException;
+
+    
+    /**
+     * Process a Comet event.
+     *
+     * @param request The servlet request to be processed
+     * @param response The servlet response to be created
+     *
+     * @exception IOException if an input/output error occurs, or is thrown
+     *  by a subsequently invoked Valve, Filter, or Servlet
+     * @exception ServletException if a servlet error occurs, or is thrown
+     *  by a subsequently invoked Valve, Filter, or Servlet
+     */
+    public void event(Request request, Response response, CometEvent event)
+        throws IOException, ServletException;
+
+    
+    public boolean isAsyncSupported();
+    
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/Wrapper.java b/bundles/org.apache.tomcat/src/org/apache/catalina/Wrapper.java
new file mode 100644
index 0000000..79a776c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/Wrapper.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;
+
+
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.UnavailableException;
+
+
+/**
+ * A <b>Wrapper</b> is a Container that represents an individual servlet
+ * definition from the deployment descriptor of the web application.  It
+ * provides a convenient mechanism to use Interceptors that see every single
+ * request to the servlet represented by this definition.
+ * <p>
+ * Implementations of Wrapper are responsible for managing the servlet life
+ * cycle for their underlying servlet class, including calling init() and
+ * destroy() at appropriate times, as well as respecting the existence of
+ * the SingleThreadModel declaration on the servlet class itself.
+ * <p>
+ * The parent Container attached to a Wrapper will generally be an
+ * implementation of Context, representing the servlet context (and
+ * therefore the web application) within which this servlet executes.
+ * <p>
+ * Child Containers are not allowed on Wrapper implementations, so the
+ * <code>addChild()</code> method should throw an
+ * <code>IllegalArgumentException</code>.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: Wrapper.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ */
+
+public interface Wrapper extends Container {
+
+    /**
+     * Container event for adding a wrapper.
+     */
+    public static final String ADD_MAPPING_EVENT = "addMapping";
+    
+    /**
+     * Container event for removing a wrapper.
+     */
+    public static final String REMOVE_MAPPING_EVENT = "removeMapping";
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the available date/time for this servlet, in milliseconds since
+     * the epoch.  If this date/time is in the future, any request for this
+     * servlet will return an SC_SERVICE_UNAVAILABLE error.  If it is zero,
+     * the servlet is currently available.  A value equal to Long.MAX_VALUE
+     * is considered to mean that unavailability is permanent.
+     */
+    public long getAvailable();
+
+
+    /**
+     * Set the available date/time for this servlet, in milliseconds since the
+     * epoch.  If this date/time is in the future, any request for this servlet
+     * will return an SC_SERVICE_UNAVAILABLE error.  A value equal to
+     * Long.MAX_VALUE is considered to mean that unavailability is permanent.
+     *
+     * @param available The new available date/time
+     */
+    public void setAvailable(long available);
+
+
+    /**
+     * Return the load-on-startup order value (negative value means
+     * load on first call).
+     */
+    public int getLoadOnStartup();
+
+
+    /**
+     * Set the load-on-startup order value (negative value means
+     * load on first call).
+     *
+     * @param value New load-on-startup value
+     */
+    public void setLoadOnStartup(int value);
+
+
+    /**
+     * Return the run-as identity for this servlet.
+     */
+    public String getRunAs();
+
+
+    /**
+     * Set the run-as identity for this servlet.
+     *
+     * @param runAs New run-as identity value
+     */
+    public void setRunAs(String runAs);
+
+
+    /**
+     * Return the fully qualified servlet class name for this servlet.
+     */
+    public String getServletClass();
+
+
+    /**
+     * Set the fully qualified servlet class name for this servlet.
+     *
+     * @param servletClass Servlet class name
+     */
+    public void setServletClass(String servletClass);
+
+
+    /**
+     * Gets the names of the methods supported by the underlying servlet.
+     *
+     * This is the same set of methods included in the Allow response header
+     * in response to an OPTIONS request method processed by the underlying
+     * servlet.
+     *
+     * @return Array of names of the methods supported by the underlying
+     * servlet
+     */
+    public String[] getServletMethods() throws ServletException;
+
+
+    /**
+     * Is this servlet currently unavailable?
+     */
+    public boolean isUnavailable();
+
+    
+    /**
+     * Return the associated servlet instance.
+     */
+    public Servlet getServlet();
+    
+
+    /**
+     * Set the associated servlet instance
+     */
+    public void setServlet(Servlet servlet);
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add a new servlet initialization parameter for this servlet.
+     *
+     * @param name Name of this initialization parameter to add
+     * @param value Value of this initialization parameter to add
+     */
+    public void addInitParameter(String name, String value);
+
+
+    /**
+     * Add a new listener interested in InstanceEvents.
+     *
+     * @param listener The new listener
+     */
+    public void addInstanceListener(InstanceListener listener);
+
+
+    /**
+     * Add a mapping associated with the Wrapper.
+     * 
+     * @param mapping The new wrapper mapping
+     */
+    public void addMapping(String mapping);
+
+
+    /**
+     * Add a new security role reference record to the set of records for
+     * this servlet.
+     *
+     * @param name Role name used within this servlet
+     * @param link Role name used within the web application
+     */
+    public void addSecurityReference(String name, String link);
+
+
+    /**
+     * Allocate an initialized instance of this Servlet that is ready to have
+     * its <code>service()</code> method called.  If the servlet class does
+     * not implement <code>SingleThreadModel</code>, the (only) initialized
+     * instance may be returned immediately.  If the servlet class implements
+     * <code>SingleThreadModel</code>, the Wrapper implementation must ensure
+     * that this instance is not allocated again until it is deallocated by a
+     * call to <code>deallocate()</code>.
+     *
+     * @exception ServletException if the servlet init() method threw
+     *  an exception
+     * @exception ServletException if a loading error occurs
+     */
+    public Servlet allocate() throws ServletException;
+
+
+    /**
+     * Return this previously allocated servlet to the pool of available
+     * instances.  If this servlet class does not implement SingleThreadModel,
+     * no action is actually required.
+     *
+     * @param servlet The servlet to be returned
+     *
+     * @exception ServletException if a deallocation error occurs
+     */
+    public void deallocate(Servlet servlet) throws ServletException;
+
+
+    /**
+     * Return the value for the specified initialization parameter name,
+     * if any; otherwise return <code>null</code>.
+     *
+     * @param name Name of the requested initialization parameter
+     */
+    public String findInitParameter(String name);
+
+
+    /**
+     * Return the names of all defined initialization parameters for this
+     * servlet.
+     */
+    public String[] findInitParameters();
+
+
+    /**
+     * Return the mappings associated with this wrapper.
+     */
+    public String[] findMappings();
+
+
+    /**
+     * Return the security role link for the specified security role
+     * reference name, if any; otherwise return <code>null</code>.
+     *
+     * @param name Security role reference used within this servlet
+     */
+    public String findSecurityReference(String name);
+
+
+    /**
+     * Return the set of security role reference names associated with
+     * this servlet, if any; otherwise return a zero-length array.
+     */
+    public String[] findSecurityReferences();
+
+
+    /**
+     * Increment the error count value used when monitoring.
+     */
+    public void incrementErrorCount();
+
+
+    /**
+     * Load and initialize an instance of this servlet, if there is not already
+     * at least one initialized instance.  This can be used, for example, to
+     * load servlets that are marked in the deployment descriptor to be loaded
+     * at server startup time.
+     *
+     * @exception ServletException if the servlet init() method threw
+     *  an exception
+     * @exception ServletException if some other loading problem occurs
+     */
+    public void load() throws ServletException;
+
+
+    /**
+     * Remove the specified initialization parameter from this servlet.
+     *
+     * @param name Name of the initialization parameter to remove
+     */
+    public void removeInitParameter(String name);
+
+
+    /**
+     * Remove a listener no longer interested in InstanceEvents.
+     *
+     * @param listener The listener to remove
+     */
+    public void removeInstanceListener(InstanceListener listener);
+
+
+    /**
+     * Remove a mapping associated with the wrapper.
+     *
+     * @param mapping The pattern to remove
+     */
+    public void removeMapping(String mapping);
+
+
+    /**
+     * Remove any security role reference for the specified role name.
+     *
+     * @param name Security role used within this servlet to be removed
+     */
+    public void removeSecurityReference(String name);
+
+
+    /**
+     * Process an UnavailableException, marking this servlet as unavailable
+     * for the specified amount of time.
+     *
+     * @param unavailable The exception that occurred, or <code>null</code>
+     *  to mark this servlet as permanently unavailable
+     */
+    public void unavailable(UnavailableException unavailable);
+
+
+    /**
+     * Unload all initialized instances of this servlet, after calling the
+     * <code>destroy()</code> method for each instance.  This can be used,
+     * for example, prior to shutting down the entire servlet engine, or
+     * prior to reloading all of the classes from the Loader associated with
+     * our Loader's repository.
+     *
+     * @exception ServletException if an unload error occurs
+     */
+    public void unload() throws ServletException;
+
+
+    /**
+     * Get the multi-part configuration for the associated servlet. If no
+     * multi-part configuration has been defined, then <code>null</code> will be
+     * returned.
+     */
+    public MultipartConfigElement getMultipartConfigElement();
+    
+    
+    /**
+     * Set the multi-part configuration for the associated servlet. To clear the
+     * multi-part configuration specify <code>null</code> as the new value.
+     */
+    public void setMultipartConfigElement(
+            MultipartConfigElement multipartConfig);
+    
+    /**
+     * Does the associated Servlet support async processing? Defaults to
+     * <code>true</code>
+     */
+    public boolean isAsyncSupported();
+    
+    /**
+     * Set the async support for the associated servlet.
+     */
+    public void setAsyncSupported(boolean asyncSupport);
+    
+    /**
+     * Is the associated Servlet enabled? Defaults to <code>true</code>.
+     */
+    public boolean isEnabled();
+    
+    /**
+     * Sets the enabled attribute for the associated servlet.
+     */
+    public void setEnabled(boolean enabled);
+
+    /**
+     * Set the flag that indicates
+     * {@link javax.servlet.annotation.ServletSecurity} annotations must be
+     * scanned when the Servlet is first used.
+     * 
+     * @param b The new value of the flag
+     */
+    public void setServletSecurityAnnotationScanRequired(boolean b);
+    
+    /**
+     * Scan for (if necessary) and process (if found) the
+     * {@link javax.servlet.annotation.ServletSecurity} annotations for the
+     * Servlet associated with this wrapper.
+     */
+    public void servletSecurityAnnotationScan() throws ServletException;
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/AuthenticatorBase.java b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/AuthenticatorBase.java
new file mode 100644
index 0000000..4ec1708
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/AuthenticatorBase.java
@@ -0,0 +1,860 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.authenticator;
+
+
+import java.io.IOException;
+import java.security.Principal;
+import java.security.cert.X509Certificate;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.Authenticator;
+import org.apache.catalina.Container;
+import org.apache.catalina.Context;
+import org.apache.catalina.Globals;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.Manager;
+import org.apache.catalina.Realm;
+import org.apache.catalina.Session;
+import org.apache.catalina.Valve;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.deploy.LoginConfig;
+import org.apache.catalina.deploy.SecurityConstraint;
+import org.apache.catalina.util.DateTool;
+import org.apache.catalina.util.SessionIdGenerator;
+import org.apache.catalina.valves.ValveBase;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Basic implementation of the <b>Valve</b> interface that enforces the
+ * <code>&lt;security-constraint&gt;</code> elements in the web application
+ * deployment descriptor.  This functionality is implemented as a Valve
+ * so that it can be omitted in environments that do not require these
+ * features.  Individual implementations of each supported authentication
+ * method can subclass this base class as required.
+ * <p>
+ * <b>USAGE CONSTRAINT</b>:  When this class is utilized, the Context to
+ * which it is attached (or a parent Container in a hierarchy) must have an
+ * associated Realm that can be used for authenticating users and enumerating
+ * the roles to which they have been assigned.
+ * <p>
+ * <b>USAGE CONSTRAINT</b>:  This Valve is only useful when processing HTTP
+ * requests.  Requests of any other type will simply be passed through.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: AuthenticatorBase.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ */
+
+
+public abstract class AuthenticatorBase extends ValveBase
+        implements Authenticator {
+
+    private static final Log log = LogFactory.getLog(AuthenticatorBase.class);
+
+
+    //------------------------------------------------------ Constructor
+    public AuthenticatorBase() {
+        super(true);
+    }
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Authentication header
+     */
+    protected static final String AUTH_HEADER_NAME = "WWW-Authenticate";
+
+    /**
+     * Default authentication realm name.
+     */
+    protected static final String REALM_NAME = "Authentication required";
+
+    /**
+     * Should a session always be used once a user is authenticated? This may
+     * offer some performance benefits since the session can then be used to
+     * cache the authenticated Principal, hence removing the need to
+     * authenticate the user via the Realm on every request. This may be of help
+     * for combinations such as BASIC authentication used with the JNDIRealm or
+     * DataSourceRealms. However there will also be the performance cost of
+     * creating and GC'ing the session. By default, a session will not be
+     * created. 
+     */
+    protected boolean alwaysUseSession = false;
+
+
+    /**
+     * Should we cache authenticated Principals if the request is part of
+     * an HTTP session?
+     */
+    protected boolean cache = true;
+
+
+    /**
+     * Should the session ID, if any, be changed upon a successful
+     * authentication to prevent a session fixation attack?
+     */
+    protected boolean changeSessionIdOnAuthentication = true;
+    
+    /**
+     * The Context to which this Valve is attached.
+     */
+    protected Context context = null;
+
+
+    /**
+     * Descriptive information about this implementation.
+     */
+    protected static final String info =
+        "org.apache.catalina.authenticator.AuthenticatorBase/1.0";
+
+    /**
+     * Flag to determine if we disable proxy caching, or leave the issue
+     * up to the webapp developer.
+     */
+    protected boolean disableProxyCaching = true;
+
+    /**
+     * Flag to determine if we disable proxy caching with headers incompatible
+     * with IE 
+     */
+    protected boolean securePagesWithPragma = true;
+    
+    /**
+     * The Java class name of the secure random number generator class to be
+     * used when generating SSO session identifiers. The random number generator
+     * class must be self-seeding and have a zero-argument constructor. If not
+     * specified, an instance of {@link java.security.SecureRandom} will be
+     * generated.
+     */
+    protected String secureRandomClass = null;
+
+    /**
+     * The name of the algorithm to use to create instances of
+     * {@link java.security.SecureRandom} which are used to generate SSO session
+     * IDs. If no algorithm is specified, SHA1PRNG is used. To use the platform
+     * default (which may be SHA1PRNG), specify the empty string. If an invalid
+     * algorithm and/or provider is specified the SecureRandom instances will be
+     * created using the defaults. If that fails, the SecureRandom instances
+     * will be created using platform defaults.
+     */
+    protected String secureRandomAlgorithm = "SHA1PRNG";
+
+    /**
+     * The name of the provider to use to create instances of
+     * {@link java.security.SecureRandom} which are used to generate session SSO
+     * IDs. If no algorithm is specified the of SHA1PRNG default is used. If an
+     * invalid algorithm and/or provider is specified the SecureRandom instances
+     * will be created using the defaults. If that fails, the SecureRandom
+     * instances will be created using platform defaults.
+     */
+    protected String secureRandomProvider = null;
+
+    protected SessionIdGenerator sessionIdGenerator = null;
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
+     * The SingleSignOn implementation in our request processing chain,
+     * if there is one.
+     */
+    protected SingleSignOn sso = null;
+
+
+    /**
+     * "Expires" header always set to Date(1), so generate once only
+     */
+    private static final String DATE_ONE =
+        (new SimpleDateFormat(DateTool.HTTP_RESPONSE_DATE_HEADER,
+                              Locale.US)).format(new Date(1));
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the cache authenticated Principals flag.
+     */
+    public boolean getCache() {
+
+        return (this.cache);
+
+    }
+
+
+    /**
+     * Set the cache authenticated Principals flag.
+     *
+     * @param cache The new cache flag
+     */
+    public void setCache(boolean cache) {
+
+        this.cache = cache;
+
+    }
+
+
+    /**
+     * Return the Container to which this Valve is attached.
+     */
+    @Override
+    public Container getContainer() {
+
+        return (this.context);
+
+    }
+
+
+    /**
+     * Set the Container to which this Valve is attached.
+     *
+     * @param container The container to which we are attached
+     */
+    @Override
+    public void setContainer(Container container) {
+
+        if (container != null && !(container instanceof Context))
+            throw new IllegalArgumentException
+                (sm.getString("authenticator.notContext"));
+
+        super.setContainer(container);
+        this.context = (Context) container;
+
+    }
+
+
+    /**
+     * Return descriptive information about this Valve implementation.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    /**
+     * Return the flag that states if we add headers to disable caching by
+     * proxies.
+     */
+    public boolean getDisableProxyCaching() {
+        return disableProxyCaching;
+    }
+
+    /**
+     * Set the value of the flag that states if we add headers to disable
+     * caching by proxies.
+     * @param nocache <code>true</code> if we add headers to disable proxy 
+     *              caching, <code>false</code> if we leave the headers alone.
+     */
+    public void setDisableProxyCaching(boolean nocache) {
+        disableProxyCaching = nocache;
+    }
+    
+    /**
+     * Return the flag that states, if proxy caching is disabled, what headers
+     * we add to disable the caching.
+     */
+    public boolean getSecurePagesWithPragma() {
+        return securePagesWithPragma;
+    }
+
+    /**
+     * Set the value of the flag that states what headers we add to disable
+     * proxy caching.
+     * @param securePagesWithPragma <code>true</code> if we add headers which 
+     * are incompatible with downloading office documents in IE under SSL but
+     * which fix a caching problem in Mozilla.
+     */
+    public void setSecurePagesWithPragma(boolean securePagesWithPragma) {
+        this.securePagesWithPragma = securePagesWithPragma;
+    }    
+
+    /**
+     * Return the flag that states if we should change the session ID of an
+     * existing session upon successful authentication.
+     * 
+     * @return <code>true</code> to change session ID upon successful
+     *         authentication, <code>false</code> to do not perform the change.
+     */
+    public boolean getChangeSessionIdOnAuthentication() {
+        return changeSessionIdOnAuthentication;
+    }
+
+    /**
+     * Set the value of the flag that states if we should change the session ID
+     * of an existing session upon successful authentication.
+     * 
+     * @param changeSessionIdOnAuthentication
+     *            <code>true</code> to change session ID upon successful
+     *            authentication, <code>false</code> to do not perform the
+     *            change.
+     */
+    public void setChangeSessionIdOnAuthentication(
+            boolean changeSessionIdOnAuthentication) {
+        this.changeSessionIdOnAuthentication = changeSessionIdOnAuthentication;
+    }
+
+    /**
+     * Return the secure random number generator class name.
+     */
+    public String getSecureRandomClass() {
+
+        return (this.secureRandomClass);
+
+    }
+
+
+    /**
+     * Set the secure random number generator class name.
+     *
+     * @param secureRandomClass The new secure random number generator class
+     *                          name
+     */
+    public void setSecureRandomClass(String secureRandomClass) {
+        this.secureRandomClass = secureRandomClass;
+    }
+
+
+    /**
+     * Return the secure random number generator algorithm name.
+     */
+    public String getSecureRandomAlgorithm() {
+        return secureRandomAlgorithm;
+    }
+
+
+    /**
+     * Set the secure random number generator algorithm name.
+     *
+     * @param secureRandomAlgorithm The new secure random number generator
+     *                              algorithm name
+     */
+    public void setSecureRandomAlgorithm(String secureRandomAlgorithm) {
+        this.secureRandomAlgorithm = secureRandomAlgorithm;
+    }
+
+
+    /**
+     * Return the secure random number generator provider name.
+     */
+    public String getSecureRandomProvider() {
+        return secureRandomProvider;
+    }
+
+
+    /**
+     * Set the secure random number generator provider name.
+     *
+     * @param secureRandomProvider The new secure random number generator
+     *                             provider name
+     */
+    public void setSecureRandomProvider(String secureRandomProvider) {
+        this.secureRandomProvider = secureRandomProvider;
+    }
+
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Enforce the security restrictions in the web application deployment
+     * descriptor of our associated Context.
+     *
+     * @param request Request to be processed
+     * @param response Response to be processed
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if thrown by a processing element
+     */
+    @Override
+    public void invoke(Request request, Response response)
+        throws IOException, ServletException {
+
+        if (log.isDebugEnabled())
+            log.debug("Security checking request " +
+                request.getMethod() + " " + request.getRequestURI());
+        LoginConfig config = this.context.getLoginConfig();
+
+        // Have we got a cached authenticated Principal to record?
+        if (cache) {
+            Principal principal = request.getUserPrincipal();
+            if (principal == null) {
+                Session session = request.getSessionInternal(false);
+                if (session != null) {
+                    principal = session.getPrincipal();
+                    if (principal != null) {
+                        if (log.isDebugEnabled())
+                            log.debug("We have cached auth type " +
+                                session.getAuthType() +
+                                " for principal " +
+                                session.getPrincipal());
+                        request.setAuthType(session.getAuthType());
+                        request.setUserPrincipal(principal);
+                    }
+                }
+            }
+        }
+
+        // Special handling for form-based logins to deal with the case
+        // where the login form (and therefore the "j_security_check" URI
+        // to which it submits) might be outside the secured area
+        String contextPath = this.context.getPath();
+        String requestURI = request.getDecodedRequestURI();
+        if (requestURI.startsWith(contextPath) &&
+            requestURI.endsWith(Constants.FORM_ACTION)) {
+            if (!authenticate(request, response, config)) {
+                if (log.isDebugEnabled())
+                    log.debug(" Failed authenticate() test ??" + requestURI );
+                return;
+            }
+        }
+
+        // The Servlet may specify security constraints through annotations.
+        // Ensure that they have been processed before constraints are checked
+        Wrapper wrapper = (Wrapper) request.getMappingData().wrapper;
+        if (wrapper != null) {
+            wrapper.servletSecurityAnnotationScan();
+        }
+
+        Realm realm = this.context.getRealm();
+        // Is this request URI subject to a security constraint?
+        SecurityConstraint [] constraints
+            = realm.findSecurityConstraints(request, this.context);
+       
+        if (constraints == null && !context.getPreemptiveAuthentication()) {
+            if (log.isDebugEnabled())
+                log.debug(" Not subject to any constraint");
+            getNext().invoke(request, response);
+            return;
+        }
+
+        // Make sure that constrained resources are not cached by web proxies
+        // or browsers as caching can provide a security hole
+        if (constraints != null && disableProxyCaching && 
+            // FIXME: Disabled for Mozilla FORM support over SSL 
+            // (improper caching issue)
+            //!request.isSecure() &&
+            !"POST".equalsIgnoreCase(request.getMethod())) {
+            if (securePagesWithPragma) {
+                // FIXME: These cause problems with downloading office docs
+                // from IE under SSL and may not be needed for newer Mozilla
+                // clients.
+                response.setHeader("Pragma", "No-cache");
+                response.setHeader("Cache-Control", "no-cache");
+            } else {
+                response.setHeader("Cache-Control", "private");
+            }
+            response.setHeader("Expires", DATE_ONE);
+        }
+
+        int i;
+        if (constraints != null) {
+            // Enforce any user data constraint for this security constraint
+            if (log.isDebugEnabled()) {
+                log.debug(" Calling hasUserDataPermission()");
+            }
+            if (!realm.hasUserDataPermission(request, response,
+                                             constraints)) {
+                if (log.isDebugEnabled()) {
+                    log.debug(" Failed hasUserDataPermission() test");
+                }
+                /*
+                 * ASSERT: Authenticator already set the appropriate
+                 * HTTP status code, so we do not have to do anything special
+                 */
+                return;
+            }
+        }
+
+        // Since authenticate modifies the response on failure,
+        // we have to check for allow-from-all first.
+        boolean authRequired;
+        if (constraints == null) {
+            authRequired = false;
+        } else {
+            authRequired = true;
+            for(i=0; i < constraints.length && authRequired; i++) {
+                if(!constraints[i].getAuthConstraint()) {
+                    authRequired = false;
+                } else if(!constraints[i].getAllRoles()) {
+                    String [] roles = constraints[i].findAuthRoles();
+                    if(roles == null || roles.length == 0) {
+                        authRequired = false;
+                    }
+                }
+            }
+        }
+
+        if (!authRequired) {
+            authRequired =
+                request.getCoyoteRequest().getMimeHeaders().getValue(
+                        "authorization") != null;
+        }
+
+        if (!authRequired) {
+            X509Certificate[] certs = (X509Certificate[]) request.getAttribute(
+                    Globals.CERTIFICATES_ATTR);
+            authRequired = certs != null && certs.length > 0;
+        }
+
+        if(authRequired) {  
+            if (log.isDebugEnabled()) {
+                log.debug(" Calling authenticate()");
+            }
+            if (!authenticate(request, response, config)) {
+                if (log.isDebugEnabled()) {
+                    log.debug(" Failed authenticate() test");
+                }
+                /*
+                 * ASSERT: Authenticator already set the appropriate
+                 * HTTP status code, so we do not have to do anything
+                 * special
+                 */
+                return;
+            } 
+            
+        }
+    
+        if (constraints != null) {
+            if (log.isDebugEnabled()) {
+                log.debug(" Calling accessControl()");
+            }
+            if (!realm.hasResourcePermission(request, response,
+                                             constraints,
+                                             this.context)) {
+                if (log.isDebugEnabled()) {
+                    log.debug(" Failed accessControl() test");
+                }
+                /*
+                 * ASSERT: AccessControl method has already set the
+                 * appropriate HTTP status code, so we do not have to do
+                 * anything special
+                 */
+                return;
+            }
+        }
+    
+        // Any and all specified constraints have been satisfied
+        if (log.isDebugEnabled()) {
+            log.debug(" Successfully passed all security constraints");
+        }
+        getNext().invoke(request, response);
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Associate the specified single sign on identifier with the
+     * specified Session.
+     *
+     * @param ssoId Single sign on identifier
+     * @param session Session to be associated
+     */
+    protected void associate(String ssoId, Session session) {
+
+        if (sso == null)
+            return;
+        sso.associate(ssoId, session);
+
+    }
+
+
+    /**
+     * Authenticate the user making this request, based on the specified
+     * login configuration.  Return <code>true</code> if any specified
+     * constraint has been satisfied, or <code>false</code> if we have
+     * created a response challenge already.
+     *
+     * @param request Request we are processing
+     * @param response Response we are populating
+     * @param config    Login configuration describing how authentication
+     *              should be performed
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public abstract boolean authenticate(Request request,
+                                            HttpServletResponse response,
+                                            LoginConfig config)
+        throws IOException;
+
+
+    /**
+     * Attempts reauthentication to the <code>Realm</code> using
+     * the credentials included in argument <code>entry</code>.
+     *
+     * @param ssoId identifier of SingleSignOn session with which the
+     *              caller is associated
+     * @param request   the request that needs to be authenticated
+     */
+    protected boolean reauthenticateFromSSO(String ssoId, Request request) {
+
+        if (sso == null || ssoId == null)
+            return false;
+
+        boolean reauthenticated = false;
+
+        Container parent = getContainer();
+        if (parent != null) {
+            Realm realm = parent.getRealm();
+            if (realm != null) {
+                reauthenticated = sso.reauthenticate(ssoId, realm, request);
+            }
+        }
+
+        if (reauthenticated) {
+            associate(ssoId, request.getSessionInternal(true));
+
+            if (log.isDebugEnabled()) {
+                log.debug(" Reauthenticated cached principal '" +
+                          request.getUserPrincipal().getName() +
+                          "' with auth type '" +  request.getAuthType() + "'");
+            }
+        }
+
+        return reauthenticated;
+    }
+
+
+    /**
+     * Register an authenticated Principal and authentication type in our
+     * request, in the current session (if there is one), and with our
+     * SingleSignOn valve, if there is one.  Set the appropriate cookie
+     * to be returned.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are generating
+     * @param principal The authenticated Principal to be registered
+     * @param authType The authentication type to be registered
+     * @param username Username used to authenticate (if any)
+     * @param password Password used to authenticate (if any)
+     */
+    public void register(Request request, HttpServletResponse response,
+                            Principal principal, String authType,
+                            String username, String password) {
+
+        if (log.isDebugEnabled()) {
+            String name = (principal == null) ? "none" : principal.getName(); 
+            log.debug("Authenticated '" + name + "' with type '" + authType +
+                    "'");
+        }
+
+        // Cache the authentication information in our request
+        request.setAuthType(authType);
+        request.setUserPrincipal(principal);
+
+        Session session = request.getSessionInternal(false);
+        
+        if (session != null) {
+            if (changeSessionIdOnAuthentication) {
+                Manager manager = request.getContext().getManager();
+                manager.changeSessionId(session);
+                request.changeSessionId(session.getId());
+            }
+        } else if (alwaysUseSession) {
+            session = request.getSessionInternal(true);
+        }
+
+        // Cache the authentication information in our session, if any
+        if (cache) {
+            if (session != null) {
+                session.setAuthType(authType);
+                session.setPrincipal(principal);
+                if (username != null)
+                    session.setNote(Constants.SESS_USERNAME_NOTE, username);
+                else
+                    session.removeNote(Constants.SESS_USERNAME_NOTE);
+                if (password != null)
+                    session.setNote(Constants.SESS_PASSWORD_NOTE, password);
+                else
+                    session.removeNote(Constants.SESS_PASSWORD_NOTE);
+            }
+        }
+
+        // Construct a cookie to be returned to the client
+        if (sso == null)
+            return;
+
+        // Only create a new SSO entry if the SSO did not already set a note
+        // for an existing entry (as it would do with subsequent requests
+        // for DIGEST and SSL authenticated contexts)
+        String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
+        if (ssoId == null) {
+            // Construct a cookie to be returned to the client
+            ssoId = sessionIdGenerator.generateSessionId();
+            Cookie cookie = new Cookie(Constants.SINGLE_SIGN_ON_COOKIE, ssoId);
+            cookie.setMaxAge(-1);
+            cookie.setPath("/");
+            
+            // Bugzilla 41217
+            cookie.setSecure(request.isSecure());
+
+            // Bugzilla 34724
+            String ssoDomain = sso.getCookieDomain();
+            if(ssoDomain != null) {
+                cookie.setDomain(ssoDomain);
+            }
+
+            // Configure httpOnly on SSO cookie using same rules as session cookies
+            if (request.getServletContext().getSessionCookieConfig().isHttpOnly() ||
+                    request.getContext().getUseHttpOnly()) {
+                cookie.setHttpOnly(true);
+            }
+            
+            response.addCookie(cookie);
+
+            // Register this principal with our SSO valve
+            sso.register(ssoId, principal, authType, username, password);
+            request.setNote(Constants.REQ_SSOID_NOTE, ssoId);
+
+        } else {
+            if (principal == null) {
+                // Registering a programmatic logout
+                sso.deregister(ssoId);
+                return;
+            } else {
+                // Update the SSO session with the latest authentication data
+                sso.update(ssoId, principal, authType, username, password);
+            }
+        }
+
+        // Fix for Bug 10040
+        // Always associate a session with a new SSO reqistration.
+        // SSO entries are only removed from the SSO registry map when
+        // associated sessions are destroyed; if a new SSO entry is created
+        // above for this request and the user never revisits the context, the
+        // SSO entry will never be cleared if we don't associate the session
+        if (session == null)
+            session = request.getSessionInternal(true);
+        sso.associate(ssoId, session);
+
+    }
+
+    @Override
+    public void login(String username, String password, Request request)
+            throws ServletException {
+        Principal principal = doLogin(request, username, password);
+        register(request, request.getResponse(), principal,
+                    getAuthMethod(), username, password);
+    }
+
+    protected abstract String getAuthMethod();
+
+    /**
+     * Process the login request.
+     * 
+     * @param request   Associated request
+     * @param username  The user
+     * @param password  The password
+     * @return          The authenticated Principal
+     * @throws ServletException
+     */
+    protected Principal doLogin(Request request, String username,
+            String password) throws ServletException {
+        Principal p = context.getRealm().authenticate(username, password);
+        if (p == null) {
+            throw new ServletException(sm.getString("authenticator.loginFail"));
+        }
+        return p;
+    }
+
+    @Override
+    public void logout(Request request) throws ServletException {
+        register(request, request.getResponse(), null,
+                null, null, null);
+
+    }
+
+    /**
+     * 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 {
+        
+        // Look up the SingleSignOn implementation in our request processing
+        // path, if there is one
+        Container parent = context.getParent();
+        while ((sso == null) && (parent != null)) {
+            Valve valves[] = parent.getPipeline().getValves();
+            for (int i = 0; i < valves.length; i++) {
+                if (valves[i] instanceof SingleSignOn) {
+                    sso = (SingleSignOn) valves[i];
+                    break;
+                }
+            }
+            if (sso == null)
+                parent = parent.getParent();
+        }
+        if (log.isDebugEnabled()) {
+            if (sso != null)
+                log.debug("Found SingleSignOn Valve at " + sso);
+            else
+                log.debug("No SingleSignOn Valve is present");
+        }
+
+        sessionIdGenerator = new SessionIdGenerator();
+        sessionIdGenerator.setSecureRandomAlgorithm(getSecureRandomAlgorithm());
+        sessionIdGenerator.setSecureRandomClass(getSecureRandomClass());
+        sessionIdGenerator.setSecureRandomProvider(getSecureRandomProvider());
+
+        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();
+
+        sso = null;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/BasicAuthenticator.java b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/BasicAuthenticator.java
new file mode 100644
index 0000000..4d413c6
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/BasicAuthenticator.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.authenticator;
+
+
+import java.io.IOException;
+import java.security.Principal;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.deploy.LoginConfig;
+import org.apache.catalina.util.Base64;
+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.MessageBytes;
+
+
+
+/**
+ * An <b>Authenticator</b> and <b>Valve</b> implementation of HTTP BASIC
+ * Authentication, as outlined in RFC 2617:  "HTTP Authentication: Basic
+ * and Digest Access Authentication."
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: BasicAuthenticator.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ */
+
+public class BasicAuthenticator
+    extends AuthenticatorBase {
+    private static final Log log = LogFactory.getLog(BasicAuthenticator.class);
+
+   // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Descriptive information about this implementation.
+     */
+    protected static final String info =
+        "org.apache.catalina.authenticator.BasicAuthenticator/1.0";
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return descriptive information about this Valve implementation.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Authenticate the user making this request, based on the specified
+     * login configuration.  Return <code>true</code> if any specified
+     * constraint has been satisfied, or <code>false</code> if we have
+     * created a response challenge already.
+     *
+     * @param request Request we are processing
+     * @param response Response we are creating
+     * @param config    Login configuration describing how authentication
+     *              should be performed
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public boolean authenticate(Request request,
+                                HttpServletResponse response,
+                                LoginConfig config)
+        throws IOException {
+
+        // Have we already authenticated someone?
+        Principal principal = request.getUserPrincipal();
+        String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
+        if (principal != null) {
+            if (log.isDebugEnabled())
+                log.debug("Already authenticated '" + principal.getName() + "'");
+            // Associate the session with any existing SSO session
+            if (ssoId != null)
+                associate(ssoId, request.getSessionInternal(true));
+            return (true);
+        }
+
+        // Is there an SSO session against which we can try to reauthenticate?
+        if (ssoId != null) {
+            if (log.isDebugEnabled())
+                log.debug("SSO Id " + ssoId + " set; attempting " +
+                          "reauthentication");
+            /* Try to reauthenticate using data cached by SSO.  If this fails,
+               either the original SSO logon was of DIGEST or SSL (which
+               we can't reauthenticate ourselves because there is no
+               cached username and password), or the realm denied
+               the user's reauthentication for some reason.
+               In either case we have to prompt the user for a logon */
+            if (reauthenticateFromSSO(ssoId, request))
+                return true;
+        }
+
+        // Validate any credentials already included with this request
+        String username = null;
+        String password = null;
+
+        MessageBytes authorization = 
+            request.getCoyoteRequest().getMimeHeaders()
+            .getValue("authorization");
+        
+        if (authorization != null) {
+            authorization.toBytes();
+            ByteChunk authorizationBC = authorization.getByteChunk();
+            if (authorizationBC.startsWithIgnoreCase("basic ", 0)) {
+                authorizationBC.setOffset(authorizationBC.getOffset() + 6);
+                // FIXME: Add trimming
+                // authorizationBC.trim();
+                
+                CharChunk authorizationCC = authorization.getCharChunk();
+                Base64.decode(authorizationBC, authorizationCC);
+                
+                // Get username and password
+                int colon = authorizationCC.indexOf(':');
+                if (colon < 0) {
+                    username = authorizationCC.toString();
+                } else {
+                    char[] buf = authorizationCC.getBuffer();
+                    username = new String(buf, 0, colon);
+                    password = new String(buf, colon + 1, 
+                            authorizationCC.getEnd() - colon - 1);
+                }
+                
+                authorizationBC.setOffset(authorizationBC.getOffset() - 6);
+            }
+
+            principal = context.getRealm().authenticate(username, password);
+            if (principal != null) {
+                register(request, response, principal, Constants.BASIC_METHOD,
+                         username, password);
+                return (true);
+            }
+        }
+        
+        StringBuilder value = new StringBuilder(16);
+        value.append("Basic realm=\"");
+        if (config.getRealmName() == null) {
+            value.append(REALM_NAME);
+        } else {
+            value.append(config.getRealmName());
+        }
+        value.append('\"');        
+        response.setHeader(AUTH_HEADER_NAME, value.toString());
+        response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+        return (false);
+
+    }
+
+
+    @Override
+    protected String getAuthMethod() {
+        return Constants.BASIC_METHOD;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/Constants.java b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/Constants.java
new file mode 100644
index 0000000..cf3da0f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/Constants.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.catalina.authenticator;
+
+
+public class Constants {
+
+    public static final String Package = "org.apache.catalina.authenticator";
+
+    // Authentication methods for login configuration
+    // Servlet spec schemes
+    public static final String BASIC_METHOD = "BASIC";
+    public static final String CERT_METHOD = "CLIENT_CERT";
+    public static final String DIGEST_METHOD = "DIGEST";
+    public static final String FORM_METHOD = "FORM";
+    // Vendor specific schemes
+    public static final String SPNEGO_METHOD = "SPNEGO";
+
+    // Form based authentication constants
+    public static final String FORM_ACTION = "/j_security_check";
+    public static final String FORM_PASSWORD = "j_password";
+    public static final String FORM_USERNAME = "j_username";
+
+    // SPNEGO authentication constants
+    public static final String KRB5_CONF_PROPERTY = "java.security.krb5.conf";
+    public static final String DEFAULT_KRB5_CONF = "conf/krb5.ini";
+    public static final String JAAS_CONF_PROPERTY =
+            "java.security.auth.login.config";
+    public static final String DEFAULT_JAAS_CONF = "conf/jaas.conf";
+    public static final String DEFAULT_LOGIN_MODULE_NAME =
+        "com.sun.security.jgss.krb5.accept";
+    public static final String USE_SUBJECT_CREDS_ONLY_PROPERTY =
+            "javax.security.auth.useSubjectCredsOnly";
+
+    // Cookie name for single sign on support
+    public static final String SINGLE_SIGN_ON_COOKIE =
+        System.getProperty(
+                "org.apache.catalina.authenticator.Constants.SSO_SESSION_COOKIE_NAME",
+                "JSESSIONIDSSO");
+
+
+    // --------------------------------------------------------- Request Notes
+
+    /**
+     * The notes key to track the single-sign-on identity with which this
+     * request is associated.
+     */
+    public static final String REQ_SSOID_NOTE =
+      "org.apache.catalina.request.SSOID";
+
+
+    // ---------------------------------------------------------- Session Notes
+
+
+    /**
+     * If the <code>cache</code> property of our authenticator is set, and
+     * the current request is part of a session, authentication information
+     * will be cached to avoid the need for repeated calls to
+     * <code>Realm.authenticate()</code>, under the following keys:
+     */
+
+
+    /**
+     * The notes key for the password used to authenticate this user.
+     */
+    public static final String SESS_PASSWORD_NOTE =
+      "org.apache.catalina.session.PASSWORD";
+
+
+    /**
+     * The notes key for the username used to authenticate this user.
+     */
+    public static final String SESS_USERNAME_NOTE =
+      "org.apache.catalina.session.USERNAME";
+
+
+    /**
+     * The following note keys are used during form login processing to
+     * cache required information prior to the completion of authentication.
+     */
+
+
+    /**
+     * The previously authenticated principal (if caching is disabled).
+     */
+    public static final String FORM_PRINCIPAL_NOTE =
+        "org.apache.catalina.authenticator.PRINCIPAL";
+
+
+    /**
+     * The original request information, to which the user will be
+     * redirected if authentication succeeds.
+     */
+    public static final String FORM_REQUEST_NOTE =
+        "org.apache.catalina.authenticator.REQUEST";
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/DigestAuthenticator.java b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/DigestAuthenticator.java
new file mode 100644
index 0000000..8eebd90
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/DigestAuthenticator.java
@@ -0,0 +1,712 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.authenticator;
+
+
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.Realm;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.deploy.LoginConfig;
+import org.apache.catalina.util.MD5Encoder;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+
+
+/**
+ * An <b>Authenticator</b> and <b>Valve</b> implementation of HTTP DIGEST
+ * Authentication (see RFC 2069).
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @version $Id: DigestAuthenticator.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ */
+
+public class DigestAuthenticator extends AuthenticatorBase {
+
+    private static final Log log = LogFactory.getLog(DigestAuthenticator.class);
+
+
+    // -------------------------------------------------------------- Constants
+
+    /**
+     * The MD5 helper object for this class.
+     */
+    protected static final MD5Encoder md5Encoder = new MD5Encoder();
+
+
+    /**
+     * Descriptive information about this implementation.
+     */
+    protected static final String info =
+        "org.apache.catalina.authenticator.DigestAuthenticator/1.0";
+
+
+    /**
+     * Tomcat's DIGEST implementation only supports auth quality of protection.
+     */
+    protected static final String QOP = "auth";
+
+    // ----------------------------------------------------------- Constructors
+
+
+    public DigestAuthenticator() {
+        super();
+        try {
+            if (md5Helper == null)
+                md5Helper = MessageDigest.getInstance("MD5");
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+            throw new IllegalStateException();
+        }
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * MD5 message digest provider.
+     */
+    protected static volatile MessageDigest md5Helper;
+
+
+    /**
+     * List of client nonce values currently being tracked
+     */
+    protected Map<String,NonceInfo> cnonces;
+
+
+    /**
+     * Maximum number of client nonces to keep in the cache. If not specified,
+     * the default value of 1000 is used.
+     */
+    protected int cnonceCacheSize = 1000;
+
+
+    /**
+     * Private key.
+     */
+    protected String key = null;
+
+
+    /**
+     * How long server nonces are valid for in milliseconds. Defaults to 5
+     * minutes.
+     */
+    protected long nonceValidity = 5 * 60 * 1000;
+
+
+    /**
+     * Opaque string.
+     */
+    protected String opaque;
+
+
+    /**
+     * Should the URI be validated as required by RFC2617? Can be disabled in
+     * reverse proxies where the proxy has modified the URI.
+     */
+    protected boolean validateUri = true;
+
+    // ------------------------------------------------------------- Properties
+
+    /**
+     * Return descriptive information about this Valve implementation.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    public int getCnonceCacheSize() {
+        return cnonceCacheSize;
+    }
+
+
+    public void setCnonceCacheSize(int cnonceCacheSize) {
+        this.cnonceCacheSize = cnonceCacheSize;
+    }
+
+
+    public String getKey() {
+        return key;
+    }
+
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+
+    public long getNonceValidity() {
+        return nonceValidity;
+    }
+
+
+    public void setNonceValidity(long nonceValidity) {
+        this.nonceValidity = nonceValidity;
+    }
+
+
+    public String getOpaque() {
+        return opaque;
+    }
+
+
+    public void setOpaque(String opaque) {
+        this.opaque = opaque;
+    }
+
+
+    public boolean isValidateUri() {
+        return validateUri;
+    }
+
+
+    public void setValidateUri(boolean validateUri) {
+        this.validateUri = validateUri;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Authenticate the user making this request, based on the specified
+     * login configuration.  Return <code>true</code> if any specified
+     * constraint has been satisfied, or <code>false</code> if we have
+     * created a response challenge already.
+     *
+     * @param request Request we are processing
+     * @param response Response we are creating
+     * @param config    Login configuration describing how authentication
+     *              should be performed
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public boolean authenticate(Request request,
+                                HttpServletResponse response,
+                                LoginConfig config)
+        throws IOException {
+
+        // Have we already authenticated someone?
+        Principal principal = request.getUserPrincipal();
+        //String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
+        if (principal != null) {
+            if (log.isDebugEnabled())
+                log.debug("Already authenticated '" + principal.getName() + "'");
+            // Associate the session with any existing SSO session in order
+            // to get coordinated session invalidation at logout
+            String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
+            if (ssoId != null)
+                associate(ssoId, request.getSessionInternal(true));
+            return (true);
+        }
+
+        // NOTE: We don't try to reauthenticate using any existing SSO session,
+        // because that will only work if the original authentication was
+        // BASIC or FORM, which are less secure than the DIGEST auth-type
+        // specified for this webapp
+        //
+        // Uncomment below to allow previous FORM or BASIC authentications
+        // to authenticate users for this webapp
+        // TODO make this a configurable attribute (in SingleSignOn??)
+        /*
+        // Is there an SSO session against which we can try to reauthenticate?
+        if (ssoId != null) {
+            if (log.isDebugEnabled())
+                log.debug("SSO Id " + ssoId + " set; attempting " +
+                          "reauthentication");
+            // Try to reauthenticate using data cached by SSO.  If this fails,
+            // either the original SSO logon was of DIGEST or SSL (which
+            // we can't reauthenticate ourselves because there is no
+            // cached username and password), or the realm denied
+            // the user's reauthentication for some reason.
+            // In either case we have to prompt the user for a logon
+            if (reauthenticateFromSSO(ssoId, request))
+                return true;
+        }
+        */
+
+        // Validate any credentials already included with this request
+        String authorization = request.getHeader("authorization");
+        DigestInfo digestInfo = new DigestInfo(getOpaque(), getNonceValidity(),
+                getKey(), cnonces, isValidateUri());
+        if (authorization != null) {
+            if (digestInfo.validate(request, authorization, config)) {
+                principal = digestInfo.authenticate(context.getRealm());
+            }
+            
+            if (principal != null) {
+                String username = parseUsername(authorization);
+                register(request, response, principal,
+                         Constants.DIGEST_METHOD,
+                         username, null);
+                return (true);
+            }
+        }
+
+        // Send an "unauthorized" response and an appropriate challenge
+
+        // Next, generate a nOnce token (that is a token which is supposed
+        // to be unique).
+        String nonce = generateNonce(request);
+
+        setAuthenticateHeader(request, response, config, nonce,
+                digestInfo.isNonceStale());
+        response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+        //      hres.flushBuffer();
+        return (false);
+
+    }
+
+
+    @Override
+    protected String getAuthMethod() {
+        return Constants.DIGEST_METHOD;
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Parse the username from the specified authorization string.  If none
+     * can be identified, return <code>null</code>
+     *
+     * @param authorization Authorization string to be parsed
+     */
+    protected String parseUsername(String authorization) {
+
+        // Validate the authorization credentials format
+        if (authorization == null)
+            return (null);
+        if (!authorization.startsWith("Digest "))
+            return (null);
+        authorization = authorization.substring(7).trim();
+
+        StringTokenizer commaTokenizer =
+            new StringTokenizer(authorization, ",");
+
+        while (commaTokenizer.hasMoreTokens()) {
+            String currentToken = commaTokenizer.nextToken();
+            int equalSign = currentToken.indexOf('=');
+            if (equalSign < 0)
+                return null;
+            String currentTokenName =
+                currentToken.substring(0, equalSign).trim();
+            String currentTokenValue =
+                currentToken.substring(equalSign + 1).trim();
+            if ("username".equals(currentTokenName))
+                return (removeQuotes(currentTokenValue));
+        }
+
+        return (null);
+
+    }
+
+
+    /**
+     * Removes the quotes on a string. RFC2617 states quotes are optional for
+     * all parameters except realm.
+     */
+    protected static String removeQuotes(String quotedString,
+                                         boolean quotesRequired) {
+        //support both quoted and non-quoted
+        if (quotedString.length() > 0 && quotedString.charAt(0) != '"' &&
+                !quotesRequired) {
+            return quotedString;
+        } else if (quotedString.length() > 2) {
+            return quotedString.substring(1, quotedString.length() - 1);
+        } else {
+            return "";
+        }
+    }
+
+    /**
+     * Removes the quotes on a string.
+     */
+    protected static String removeQuotes(String quotedString) {
+        return removeQuotes(quotedString, false);
+    }
+
+    /**
+     * Generate a unique token. The token is generated according to the
+     * following pattern. NOnceToken = Base64 ( MD5 ( client-IP ":"
+     * time-stamp ":" private-key ) ).
+     *
+     * @param request HTTP Servlet request
+     */
+    protected String generateNonce(Request request) {
+
+        long currentTime = System.currentTimeMillis();
+
+        
+        String ipTimeKey =
+            request.getRemoteAddr() + ":" + currentTime + ":" + getKey();
+
+        byte[] buffer;
+        synchronized (md5Helper) {
+            buffer = md5Helper.digest(ipTimeKey.getBytes());
+        }
+
+        return currentTime + ":" + md5Encoder.encode(buffer);
+    }
+
+
+    /**
+     * Generates the WWW-Authenticate header.
+     * <p>
+     * The header MUST follow this template :
+     * <pre>
+     *      WWW-Authenticate    = "WWW-Authenticate" ":" "Digest"
+     *                            digest-challenge
+     *
+     *      digest-challenge    = 1#( realm | [ domain ] | nOnce |
+     *                  [ digest-opaque ] |[ stale ] | [ algorithm ] )
+     *
+     *      realm               = "realm" "=" realm-value
+     *      realm-value         = quoted-string
+     *      domain              = "domain" "=" <"> 1#URI <">
+     *      nonce               = "nonce" "=" nonce-value
+     *      nonce-value         = quoted-string
+     *      opaque              = "opaque" "=" quoted-string
+     *      stale               = "stale" "=" ( "true" | "false" )
+     *      algorithm           = "algorithm" "=" ( "MD5" | token )
+     * </pre>
+     *
+     * @param request HTTP Servlet request
+     * @param response HTTP Servlet response
+     * @param config    Login configuration describing how authentication
+     *              should be performed
+     * @param nOnce nonce token
+     */
+    protected void setAuthenticateHeader(HttpServletRequest request,
+                                         HttpServletResponse response,
+                                         LoginConfig config,
+                                         String nOnce,
+                                         boolean isNonceStale) {
+
+        // Get the realm name
+        String realmName = config.getRealmName();
+        if (realmName == null)
+            realmName = REALM_NAME;
+
+        String authenticateHeader;
+        if (isNonceStale) {
+            authenticateHeader = "Digest realm=\"" + realmName + "\", " +
+            "qop=\"" + QOP + "\", nonce=\"" + nOnce + "\", " + "opaque=\"" +
+            getOpaque() + "\", stale=true";
+        } else {
+            authenticateHeader = "Digest realm=\"" + realmName + "\", " +
+            "qop=\"" + QOP + "\", nonce=\"" + nOnce + "\", " + "opaque=\"" +
+            getOpaque() + "\"";
+        }
+
+        response.setHeader(AUTH_HEADER_NAME, authenticateHeader);
+
+    }
+
+
+    // ------------------------------------------------------- Lifecycle Methods
+    
+    @Override
+    protected synchronized void startInternal() throws LifecycleException {
+        super.startInternal();
+        
+        // Generate a random secret key
+        if (getKey() == null) {
+            setKey(sessionIdGenerator.generateSessionId());
+        }
+        
+        // Generate the opaque string the same way
+        if (getOpaque() == null) {
+            setOpaque(sessionIdGenerator.generateSessionId());
+        }
+        
+        cnonces = new LinkedHashMap<String, DigestAuthenticator.NonceInfo>() {
+
+            private static final long serialVersionUID = 1L;
+            private static final long LOG_SUPPRESS_TIME = 5 * 60 * 1000;
+
+            private long lastLog = 0;
+
+            @Override
+            protected boolean removeEldestEntry(
+                    Map.Entry<String,NonceInfo> eldest) {
+                // This is called from a sync so keep it simple
+                long currentTime = System.currentTimeMillis();
+                if (size() > getCnonceCacheSize()) {
+                    if (lastLog < currentTime &&
+                            currentTime - eldest.getValue().getTimestamp() <
+                            getNonceValidity()) {
+                        // Replay attack is possible
+                        log.warn(sm.getString(
+                                "digestAuthenticator.cacheRemove"));
+                        lastLog = currentTime + LOG_SUPPRESS_TIME;
+                    }
+                    return true;
+                }
+                return false;
+            }
+        };
+    }
+ 
+    private static class DigestInfo {
+
+        private String opaque;
+        private long nonceValidity;
+        private String key;
+        private Map<String,NonceInfo> cnonces;
+        private boolean validateUri = true;
+
+        private String userName = null;
+        private String method = null;
+        private String uri = null;
+        private String response = null;
+        private String nonce = null;
+        private String nc = null;
+        private String cnonce = null;
+        private String realmName = null;
+        private String qop = null;
+
+        private boolean nonceStale = false;
+
+
+        public DigestInfo(String opaque, long nonceValidity, String key,
+                Map<String,NonceInfo> cnonces, boolean validateUri) {
+            this.opaque = opaque;
+            this.nonceValidity = nonceValidity;
+            this.key = key;
+            this.cnonces = cnonces;
+            this.validateUri = validateUri;
+        }
+
+        public boolean validate(Request request, String authorization,
+                LoginConfig config) {
+            // Validate the authorization credentials format
+            if (authorization == null) {
+                return false;
+            }
+            if (!authorization.startsWith("Digest ")) {
+                return false;
+            }
+            authorization = authorization.substring(7).trim();
+
+            // Bugzilla 37132: http://issues.apache.org/bugzilla/show_bug.cgi?id=37132
+            String[] tokens = authorization.split(",(?=(?:[^\"]*\"[^\"]*\")+$)");
+
+            method = request.getMethod();
+            String opaque = null;
+
+            for (int i = 0; i < tokens.length; i++) {
+                String currentToken = tokens[i];
+                if (currentToken.length() == 0)
+                    continue;
+
+                int equalSign = currentToken.indexOf('=');
+                if (equalSign < 0) {
+                    return false;
+                }
+                String currentTokenName =
+                    currentToken.substring(0, equalSign).trim();
+                String currentTokenValue =
+                    currentToken.substring(equalSign + 1).trim();
+                if ("username".equals(currentTokenName))
+                    userName = removeQuotes(currentTokenValue);
+                if ("realm".equals(currentTokenName))
+                    realmName = removeQuotes(currentTokenValue, true);
+                if ("nonce".equals(currentTokenName))
+                    nonce = removeQuotes(currentTokenValue);
+                if ("nc".equals(currentTokenName))
+                    nc = removeQuotes(currentTokenValue);
+                if ("cnonce".equals(currentTokenName))
+                    cnonce = removeQuotes(currentTokenValue);
+                if ("qop".equals(currentTokenName))
+                    qop = removeQuotes(currentTokenValue);
+                if ("uri".equals(currentTokenName))
+                    uri = removeQuotes(currentTokenValue);
+                if ("response".equals(currentTokenName))
+                    response = removeQuotes(currentTokenValue);
+                if ("opaque".equals(currentTokenName))
+                    opaque = removeQuotes(currentTokenValue);
+            }
+
+            if ( (userName == null) || (realmName == null) || (nonce == null)
+                 || (uri == null) || (response == null) ) {
+                return false;
+            }
+
+            // Validate the URI - should match the request line sent by client
+            if (validateUri) {
+                String uriQuery;
+                String query = request.getQueryString();
+                if (query == null) {
+                    uriQuery = request.getRequestURI();
+                } else {
+                    uriQuery = request.getRequestURI() + "?" + query;
+                }
+                if (!uri.equals(uriQuery)) {
+                    return false;
+                }
+            }
+
+            // Validate the Realm name
+            String lcRealm = config.getRealmName();
+            if (lcRealm == null) {
+                lcRealm = REALM_NAME;
+            }
+            if (!lcRealm.equals(realmName)) {
+                return false;
+            }
+            
+            // Validate the opaque string
+            if (!this.opaque.equals(opaque)) {
+                return false;
+            }
+
+            // Validate nonce
+            int i = nonce.indexOf(":");
+            if (i < 0 || (i + 1) == nonce.length()) {
+                return false;
+            }
+            long nOnceTime;
+            try {
+                nOnceTime = Long.parseLong(nonce.substring(0, i));
+            } catch (NumberFormatException nfe) {
+                return false;
+            }
+            String md5clientIpTimeKey = nonce.substring(i + 1);
+            long currentTime = System.currentTimeMillis();
+            if ((currentTime - nOnceTime) > nonceValidity) {
+                nonceStale = true;
+                return false;
+            }
+            String serverIpTimeKey =
+                request.getRemoteAddr() + ":" + nOnceTime + ":" + key;
+            byte[] buffer = null;
+            synchronized (md5Helper) {
+                buffer = md5Helper.digest(serverIpTimeKey.getBytes());
+            }
+            String md5ServerIpTimeKey = md5Encoder.encode(buffer);
+            if (!md5ServerIpTimeKey.equals(md5clientIpTimeKey)) {
+                return false;
+            }
+
+            // Validate qop
+            if (qop != null && !QOP.equals(qop)) {
+                return false;
+            }
+
+            // Validate cnonce and nc
+            // Check if presence of nc and nonce is consistent with presence of qop
+            if (qop == null) {
+                if (cnonce != null || nc != null) {
+                    return false;
+                }
+            } else {
+                if (cnonce == null || nc == null) {
+                    return false;
+                }
+                if (nc.length() != 8) {
+                    return false;
+                }
+                long count;
+                try {
+                    count = Long.parseLong(nc, 16);
+                } catch (NumberFormatException nfe) {
+                    return false;
+                }
+                NonceInfo info;
+                synchronized (cnonces) {
+                    info = cnonces.get(cnonce);
+                }
+                if (info == null) {
+                    info = new NonceInfo();
+                } else {
+                    if (count <= info.getCount()) {
+                        return false;
+                    }
+                }
+                info.setCount(count);
+                info.setTimestamp(currentTime);
+                synchronized (cnonces) {
+                    cnonces.put(cnonce, info);
+                }
+            }
+            return true;
+        }
+
+        public boolean isNonceStale() {
+            return nonceStale;
+        }
+
+        public Principal authenticate(Realm realm) {
+            // Second MD5 digest used to calculate the digest :
+            // MD5(Method + ":" + uri)
+            String a2 = method + ":" + uri;
+
+            byte[] buffer;
+            synchronized (md5Helper) {
+                buffer = md5Helper.digest(a2.getBytes());
+            }
+            String md5a2 = md5Encoder.encode(buffer);
+
+            return realm.authenticate(userName, response, nonce, nc, cnonce,
+                    qop, realmName, md5a2);
+        }
+
+    }
+
+    private static class NonceInfo {
+        private volatile long count;
+        private volatile long timestamp;
+        
+        public void setCount(long l) {
+            count = l;
+        }
+        
+        public long getCount() {
+            return count;
+        }
+        
+        public void setTimestamp(long l) {
+            timestamp = l;
+        }
+        
+        public long getTimestamp() {
+            return timestamp;
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/FormAuthenticator.java b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/FormAuthenticator.java
new file mode 100644
index 0000000..4f5f568
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/FormAuthenticator.java
@@ -0,0 +1,629 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.authenticator;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Locale;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.Realm;
+import org.apache.catalina.Session;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.deploy.LoginConfig;
+import org.apache.coyote.ActionCode;
+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.CharChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.MimeHeaders;
+
+
+/**
+ * An <b>Authenticator</b> and <b>Valve</b> implementation of FORM BASED
+ * Authentication, as described in the Servlet API Specification, Version 2.2.
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @version $Id: FormAuthenticator.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ */
+
+public class FormAuthenticator
+    extends AuthenticatorBase {
+    
+    private static final Log log = LogFactory.getLog(FormAuthenticator.class);
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Descriptive information about this implementation.
+     */
+    protected static final String info =
+        "org.apache.catalina.authenticator.FormAuthenticator/1.0";
+
+    /**
+     * Character encoding to use to read the username and password parameters
+     * from the request. If not set, the encoding of the request body will be
+     * used.
+     */
+    protected String characterEncoding = null;
+
+    /**
+     * Landing page to use if a user tries to access the login page directly or
+     * if the session times out during login. If not set, error responses will
+     * be sent instead.
+     */
+    protected String landingPage = null;
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return descriptive information about this Valve implementation.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    /**
+     * Return the character encoding to use to read the username and password.
+     */
+    public String getCharacterEncoding() {
+        return characterEncoding;
+    }
+
+    
+    /**
+     * Set the character encoding to be used to read the username and password. 
+     */
+    public void setCharacterEncoding(String encoding) {
+        characterEncoding = encoding;
+    }
+
+
+    /**
+     * Return the landing page to use when FORM auth is mis-used.
+     */
+    public String getLandingPage() {
+        return landingPage;
+    }
+
+
+    /**
+     * Set the landing page to use when the FORM auth is mis-used.
+     */
+    public void setLandingPage(String landingPage) {
+        this.landingPage = landingPage;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Authenticate the user making this request, based on the specified
+     * login configuration.  Return <code>true</code> if any specified
+     * constraint has been satisfied, or <code>false</code> if we have
+     * created a response challenge already.
+     *
+     * @param request Request we are processing
+     * @param response Response we are creating
+     * @param config    Login configuration describing how authentication
+     *              should be performed
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public boolean authenticate(Request request,
+                                HttpServletResponse response,
+                                LoginConfig config)
+        throws IOException {
+
+        // References to objects we will need later
+        Session session = null;
+
+        // Have we already authenticated someone?
+        Principal principal = request.getUserPrincipal();
+        String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
+        if (principal != null) {
+            if (log.isDebugEnabled())
+                log.debug("Already authenticated '" +
+                    principal.getName() + "'");
+            // Associate the session with any existing SSO session
+            if (ssoId != null)
+                associate(ssoId, request.getSessionInternal(true));
+            return (true);
+        }
+
+        // Is there an SSO session against which we can try to reauthenticate?
+        if (ssoId != null) {
+            if (log.isDebugEnabled())
+                log.debug("SSO Id " + ssoId + " set; attempting " +
+                          "reauthentication");
+            // Try to reauthenticate using data cached by SSO.  If this fails,
+            // either the original SSO logon was of DIGEST or SSL (which
+            // we can't reauthenticate ourselves because there is no
+            // cached username and password), or the realm denied
+            // the user's reauthentication for some reason.
+            // In either case we have to prompt the user for a logon */
+            if (reauthenticateFromSSO(ssoId, request))
+                return true;
+        }
+
+        // Have we authenticated this user before but have caching disabled?
+        if (!cache) {
+            session = request.getSessionInternal(true);
+            if (log.isDebugEnabled())
+                log.debug("Checking for reauthenticate in session " + session);
+            String username =
+                (String) session.getNote(Constants.SESS_USERNAME_NOTE);
+            String password =
+                (String) session.getNote(Constants.SESS_PASSWORD_NOTE);
+            if ((username != null) && (password != null)) {
+                if (log.isDebugEnabled())
+                    log.debug("Reauthenticating username '" + username + "'");
+                principal =
+                    context.getRealm().authenticate(username, password);
+                if (principal != null) {
+                    session.setNote(Constants.FORM_PRINCIPAL_NOTE, principal);
+                    if (!matchRequest(request)) {
+                        register(request, response, principal,
+                                 Constants.FORM_METHOD,
+                                 username, password);
+                        return (true);
+                    }
+                }
+                if (log.isDebugEnabled())
+                    log.debug("Reauthentication failed, proceed normally");
+            }
+        }
+
+        // Is this the re-submit of the original request URI after successful
+        // authentication?  If so, forward the *original* request instead.
+        if (matchRequest(request)) {
+            session = request.getSessionInternal(true);
+            if (log.isDebugEnabled())
+                log.debug("Restore request from session '"
+                          + session.getIdInternal() 
+                          + "'");
+            principal = (Principal)
+                session.getNote(Constants.FORM_PRINCIPAL_NOTE);
+            register(request, response, principal, Constants.FORM_METHOD,
+                     (String) session.getNote(Constants.SESS_USERNAME_NOTE),
+                     (String) session.getNote(Constants.SESS_PASSWORD_NOTE));
+            // If we're caching principals we no longer need the username
+            // and password in the session, so remove them
+            if (cache) {
+                session.removeNote(Constants.SESS_USERNAME_NOTE);
+                session.removeNote(Constants.SESS_PASSWORD_NOTE);
+            }
+            if (restoreRequest(request, session)) {
+                if (log.isDebugEnabled())
+                    log.debug("Proceed to restored request");
+                return (true);
+            } else {
+                if (log.isDebugEnabled())
+                    log.debug("Restore of original request failed");
+                response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+                return (false);
+            }
+        }
+
+        // Acquire references to objects we will need to evaluate
+        MessageBytes uriMB = MessageBytes.newInstance();
+        CharChunk uriCC = uriMB.getCharChunk();
+        uriCC.setLimit(-1);
+        String contextPath = request.getContextPath();
+        String requestURI = request.getDecodedRequestURI();
+
+        // Is this the action request from the login page?
+        boolean loginAction =
+            requestURI.startsWith(contextPath) &&
+            requestURI.endsWith(Constants.FORM_ACTION);
+
+        // No -- Save this request and redirect to the form login page
+        if (!loginAction) {
+            session = request.getSessionInternal(true);
+            if (log.isDebugEnabled())
+                log.debug("Save request in session '" + session.getIdInternal() + "'");
+            try {
+                saveRequest(request, session);
+            } catch (IOException ioe) {
+                log.debug("Request body too big to save during authentication");
+                response.sendError(HttpServletResponse.SC_FORBIDDEN,
+                        sm.getString("authenticator.requestBodyTooBig"));
+                return (false);
+            }
+            forwardToLoginPage(request, response, config);
+            return (false);
+        }
+
+        // Yes -- Acknowledge the request, validate the specified credentials
+        // and redirect to the error page if they are not correct
+        request.getResponse().sendAcknowledgement();
+        Realm realm = context.getRealm();
+        if (characterEncoding != null) {
+            request.setCharacterEncoding(characterEncoding);
+        }
+        String username = request.getParameter(Constants.FORM_USERNAME);
+        String password = request.getParameter(Constants.FORM_PASSWORD);
+        if (log.isDebugEnabled())
+            log.debug("Authenticating username '" + username + "'");
+        principal = realm.authenticate(username, password);
+        if (principal == null) {
+            forwardToErrorPage(request, response, config);
+            return (false);
+        }
+
+        if (log.isDebugEnabled())
+            log.debug("Authentication of '" + username + "' was successful");
+
+        if (session == null)
+            session = request.getSessionInternal(false);
+        if (session == null) {
+            if (containerLog.isDebugEnabled())
+                containerLog.debug
+                    ("User took so long to log on the session expired");
+            if (landingPage == null) {
+                response.sendError(HttpServletResponse.SC_REQUEST_TIMEOUT,
+                        sm.getString("authenticator.sessionExpired"));
+            } else {
+                // Make the authenticator think the user originally requested
+                // the landing page
+                String uri = request.getContextPath() + landingPage;
+                SavedRequest saved = new SavedRequest();
+                saved.setRequestURI(uri);
+                request.getSessionInternal(true).setNote(
+                        Constants.FORM_REQUEST_NOTE, saved);
+                response.sendRedirect(response.encodeRedirectURL(uri));
+            }
+            return (false);
+        }
+
+        // Save the authenticated Principal in our session
+        session.setNote(Constants.FORM_PRINCIPAL_NOTE, principal);
+
+        // Save the username and password as well
+        session.setNote(Constants.SESS_USERNAME_NOTE, username);
+        session.setNote(Constants.SESS_PASSWORD_NOTE, password);
+
+        // Redirect the user to the original request URI (which will cause
+        // the original request to be restored)
+        requestURI = savedRequestURL(session);
+        if (log.isDebugEnabled())
+            log.debug("Redirecting to original '" + requestURI + "'");
+        if (requestURI == null)
+            if (landingPage == null) {
+                response.sendError(HttpServletResponse.SC_BAD_REQUEST,
+                        sm.getString("authenticator.formlogin"));
+            } else {
+                // Make the authenticator think the user originally requested
+                // the landing page
+                String uri = request.getContextPath() + landingPage;
+                SavedRequest saved = new SavedRequest();
+                saved.setRequestURI(uri);
+                session.setNote(Constants.FORM_REQUEST_NOTE, saved);
+                response.sendRedirect(response.encodeRedirectURL(uri));
+            }
+        else
+            response.sendRedirect(response.encodeRedirectURL(requestURI));
+        return (false);
+
+    }
+
+
+    @Override
+    protected String getAuthMethod() {
+        return Constants.FORM_METHOD;
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Called to forward to the login page
+     * 
+     * @param request Request we are processing
+     * @param response Response we are populating
+     * @param config    Login configuration describing how authentication
+     *              should be performed
+     * @throws IOException  If the forward to the login page fails and the call
+     *                      to {@link HttpServletResponse#sendError(int, String)}
+     *                      throws an {@link IOException}
+     */
+    protected void forwardToLoginPage(Request request,
+            HttpServletResponse response, LoginConfig config)
+            throws IOException {
+        RequestDispatcher disp =
+            context.getServletContext().getRequestDispatcher
+            (config.getLoginPage());
+        try {
+            if (context.fireRequestInitEvent(request)) {
+                disp.forward(request.getRequest(), response);
+                context.fireRequestDestroyEvent(request);
+            }
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            String msg = sm.getString("formAuthenticator.forwardLoginFail");
+            log.warn(msg, t);
+            request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
+            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                    msg);
+        }
+    }
+
+
+    /**
+     * Called to forward to the error page
+     * 
+     * @param request Request we are processing
+     * @param response Response we are populating
+     * @param config    Login configuration describing how authentication
+     *              should be performed
+     * @throws IOException  If the forward to the error page fails and the call
+     *                      to {@link HttpServletResponse#sendError(int, String)}
+     *                      throws an {@link IOException}
+     */
+    protected void forwardToErrorPage(Request request,
+            HttpServletResponse response, LoginConfig config)
+            throws IOException {
+        RequestDispatcher disp =
+            context.getServletContext().getRequestDispatcher
+            (config.getErrorPage());
+        try {
+            if (context.fireRequestInitEvent(request)) {
+                disp.forward(request.getRequest(), response);
+                context.fireRequestDestroyEvent(request);
+            }
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            String msg = sm.getString("formAuthenticator.forwardErrorFail");
+            log.warn(msg, t);
+            request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
+            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                    msg);
+        }
+    }
+
+
+    /**
+     * Does this request match the saved one (so that it must be the redirect
+     * we signaled after successful authentication?
+     *
+     * @param request The request to be verified
+     */
+    protected boolean matchRequest(Request request) {
+
+      // Has a session been created?
+      Session session = request.getSessionInternal(false);
+      if (session == null)
+          return (false);
+
+      // Is there a saved request?
+      SavedRequest sreq = (SavedRequest)
+          session.getNote(Constants.FORM_REQUEST_NOTE);
+      if (sreq == null)
+          return (false);
+
+      // Is there a saved principal?
+      if (session.getNote(Constants.FORM_PRINCIPAL_NOTE) == null)
+          return (false);
+
+      // Does the request URI match?
+      String requestURI = request.getRequestURI();
+      if (requestURI == null)
+          return (false);
+      return (requestURI.equals(sreq.getRequestURI()));
+
+    }
+
+
+    /**
+     * Restore the original request from information stored in our session.
+     * If the original request is no longer present (because the session
+     * timed out), return <code>false</code>; otherwise, return
+     * <code>true</code>.
+     *
+     * @param request The request to be restored
+     * @param session The session containing the saved information
+     */
+    protected boolean restoreRequest(Request request, Session session)
+            throws IOException {
+
+        // Retrieve and remove the SavedRequest object from our session
+        SavedRequest saved = (SavedRequest)
+            session.getNote(Constants.FORM_REQUEST_NOTE);
+        session.removeNote(Constants.FORM_REQUEST_NOTE);
+        session.removeNote(Constants.FORM_PRINCIPAL_NOTE);
+        if (saved == null)
+            return (false);
+
+        // Modify our current request to reflect the original one
+        request.clearCookies();
+        Iterator<Cookie> cookies = saved.getCookies();
+        while (cookies.hasNext()) {
+            request.addCookie(cookies.next());
+        }
+
+        MimeHeaders rmh = request.getCoyoteRequest().getMimeHeaders();
+        rmh.recycle();
+        boolean cachable = "GET".equalsIgnoreCase(saved.getMethod()) ||
+                           "HEAD".equalsIgnoreCase(saved.getMethod());
+        Iterator<String> names = saved.getHeaderNames();
+        while (names.hasNext()) {
+            String name = names.next();
+            // The browser isn't expecting this conditional response now.
+            // Assuming that it can quietly recover from an unexpected 412.
+            // BZ 43687
+            if(!("If-Modified-Since".equalsIgnoreCase(name) ||
+                 (cachable && "If-None-Match".equalsIgnoreCase(name)))) {
+                Iterator<String> values = saved.getHeaderValues(name);
+                while (values.hasNext()) {
+                    rmh.addValue(name).setString(values.next());
+                }
+            }
+        }
+        
+        request.clearLocales();
+        Iterator<Locale> locales = saved.getLocales();
+        while (locales.hasNext()) {
+            request.addLocale(locales.next());
+        }
+        
+        request.getCoyoteRequest().getParameters().recycle();
+        request.getCoyoteRequest().getParameters().setQueryStringEncoding(
+                request.getConnector().getURIEncoding());
+
+        // Swallow any request body since we will be replacing it
+        byte[] buffer = new byte[4096];
+        InputStream is = request.getInputStream();
+        while (is.read(buffer) >= 0) {
+            // Ignore request body
+        }
+        
+        if ("POST".equalsIgnoreCase(saved.getMethod())) {
+            ByteChunk body = saved.getBody();
+            
+            if (body != null) {
+                request.getCoyoteRequest().action
+                    (ActionCode.REQ_SET_BODY_REPLAY, body);
+    
+                // Set content type
+                MessageBytes contentType = MessageBytes.newInstance();
+                
+                //If no content type specified, use default for POST
+                String savedContentType = saved.getContentType();
+                if (savedContentType == null) {
+                    savedContentType = "application/x-www-form-urlencoded";
+                }
+
+                contentType.setString(savedContentType);
+                request.getCoyoteRequest().setContentType(contentType);
+            }
+        }
+        request.getCoyoteRequest().method().setString(saved.getMethod());
+
+        request.getCoyoteRequest().queryString().setString
+            (saved.getQueryString());
+
+        request.getCoyoteRequest().requestURI().setString
+            (saved.getRequestURI());
+        return (true);
+
+    }
+
+
+    /**
+     * Save the original request information into our session.
+     *
+     * @param request The request to be saved
+     * @param session The session to contain the saved information
+     * @throws IOException
+     */
+    protected void saveRequest(Request request, Session session)
+        throws IOException {
+
+        // Create and populate a SavedRequest object for this request
+        SavedRequest saved = new SavedRequest();
+        Cookie cookies[] = request.getCookies();
+        if (cookies != null) {
+            for (int i = 0; i < cookies.length; i++)
+                saved.addCookie(cookies[i]);
+        }
+        Enumeration<String> names = request.getHeaderNames();
+        while (names.hasMoreElements()) {
+            String name = names.nextElement();
+            Enumeration<String> values = request.getHeaders(name);
+            while (values.hasMoreElements()) {
+                String value = values.nextElement();
+                saved.addHeader(name, value);
+            }
+        }
+        Enumeration<Locale> locales = request.getLocales();
+        while (locales.hasMoreElements()) {
+            Locale locale = locales.nextElement();
+            saved.addLocale(locale);
+        }
+
+        if ("POST".equalsIgnoreCase(request.getMethod())) {
+            // May need to acknowledge a 100-continue expectation
+            request.getResponse().sendAcknowledgement();
+
+            ByteChunk body = new ByteChunk();
+            body.setLimit(request.getConnector().getMaxSavePostSize());
+
+            byte[] buffer = new byte[4096];
+            int bytesRead;
+            InputStream is = request.getInputStream();
+        
+            while ( (bytesRead = is.read(buffer) ) >= 0) {
+                body.append(buffer, 0, bytesRead);
+            }
+            saved.setContentType(request.getContentType());
+            saved.setBody(body);
+        }
+
+        saved.setMethod(request.getMethod());
+        saved.setQueryString(request.getQueryString());
+        saved.setRequestURI(request.getRequestURI());
+
+        // Stash the SavedRequest in our session for later use
+        session.setNote(Constants.FORM_REQUEST_NOTE, saved);
+
+    }
+
+
+    /**
+     * Return the request URI (with the corresponding query string, if any)
+     * from the saved request so that we can redirect to it.
+     *
+     * @param session Our current session
+     */
+    protected String savedRequestURL(Session session) {
+
+        SavedRequest saved =
+            (SavedRequest) session.getNote(Constants.FORM_REQUEST_NOTE);
+        if (saved == null)
+            return (null);
+        StringBuilder sb = new StringBuilder(saved.getRequestURI());
+        if (saved.getQueryString() != null) {
+            sb.append('?');
+            sb.append(saved.getQueryString());
+        }
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/LocalStrings.properties
new file mode 100644
index 0000000..cf5307a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/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.
+
+authenticator.certificates=No client certificate chain in this request
+authenticator.forbidden=Access to the requested resource has been denied
+authenticator.formlogin=Invalid direct reference to form login page
+authenticator.invalid=Invalid client certificate chain in this request
+authenticator.loginFail=Login failed
+authenticator.keystore=Exception loading key store
+authenticator.manager=Exception initializing trust managers
+authenticator.noAuthHeader=No authorization header sent by client
+authenticator.notAuthenticated=Configuration error:  Cannot perform access control without an authenticated principal
+authenticator.notContext=Configuration error:  Must be attached to a Context
+authenticator.requestBodyTooBig=The request body was too large to be cached during the authentication process
+authenticator.sessionExpired=The time allowed for the login process has been exceeded. If you wish to continue you must either click back twice and re-click the link you requested or close and re-open your browser
+authenticator.unauthorized=Cannot authenticate with the provided credentials
+authenticator.userDataConstraint=This request violates a User Data constraint for this application
+
+digestAuthenticator.cacheRemove=A valid entry has been removed from client nonce cache to make room for new entries. A replay attack is now possible. To prevent the possibility of replay attacks, reduce nonceValidity or increase cnonceCacheSize. Further warnings of this type will be suppressed for 5 minutes.
+ 
+formAuthenticator.forwardErrorFail=Unexpected error forwarding to error page
+formAuthenticator.forwardLoginFail=Unexpected error forwarding to login page
+
+spnegoAuthenticator.authHeaderNoToken=The Negotiate authorization header sent by the client did include a token
+spnegoAuthenticator.authHeaderNotNego=The authorization header sent by the client did not start with Negotiate
+spnegoAuthenticator.hostnameFail=Unable to determine the host name to construct the default SPN. Please set the spn attribute of the authenticator.
+spnegoAuthenticator.serviceLoginFail=Unable to login as the service principal
+spnegoAuthenticator.ticketValidateFail=Failed to validate client supplied ticket
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/LocalStrings_es.properties
new file mode 100644
index 0000000..8cabcfc
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/LocalStrings_es.properties
@@ -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.
+
+authenticator.certificates = No hay cadena de certificados del cliente en esta petici\u00F3n
+authenticator.forbidden = El acceso al recurso pedido ha sido denegado
+authenticator.formlogin = Referencia directa al formulario de conexi\u00F3n (p\u00E1gina de formulario de login) inv\u00E1lida
+authenticator.invalid = No es v\u00E1lida la cadena de certificados del cliente en esta petici\u00F3n
+authenticator.keystore = Excepci\u00F3n cargando el almac\u00E9n de claves
+authenticator.manager = Excepci\u00F3n inicializando administradores de confianza
+authenticator.notAuthenticated = Error de Configuraci\u00F3n\: No se pueden realizar funciones de control de acceso sin un principal autenticado
+authenticator.notContext = Error de Configuraci\u00F3n\: Debe de estar unido a un Contexto
+authenticator.requestBodyTooBig = El cuerpo del requerimiento era demasiado grande para realizar cach\u00E9 durante el proceso de autenticaci\u00F3n
+authenticator.sessionExpired = El tiempo permitido para realizar login ha sido excedido. Si deseas continuar, debes hacer clik dos veces y volver a hacer clik otra vez o cerrar y reabrir tu navegador
+authenticator.unauthorized = Imposible autenticar mediante las credenciales suministradas
+authenticator.userDataConstraint = Esta petici\u00F3n viola una Restrici\u00F3n de Datos de usuario para esta aplicaci\u00F3n
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/LocalStrings_fr.properties
new file mode 100644
index 0000000..3303ae6
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/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.
+
+authenticator.certificates=Aucune cha\u00eene de certificat client (client certificate chain) dans cette requ\u00eate
+authenticator.forbidden=L''acc\u00e8s \u00e0 la ressource demand\u00e9e a \u00e9t\u00e9 interdit
+authenticator.formlogin=R\u00e9f\u00e9rence directe \u00e0 la form de connexion (form login page) invalide
+authenticator.invalid=Cha\u00eene de certificat client invalide dans cette requ\u00eate
+authenticator.keystore=Exception lors du chargement du r\u00e9f\u00e9rentiel de clefs (key store)
+authenticator.manager=Exception lors de l''initialisation des gestionnaires d''authentification (trust managers)
+authenticator.notAuthenticated=Erreur de configuration:  Impossible de proc\u00e9der \u00e0 un contr\u00f4le d''acc\u00e8s sans un principal authentifi\u00e9 (authenticated principal)
+authenticator.notContext=Erreur de configuration:  Doit \u00eatre attach\u00e9 \u00e0 un contexte
+authenticator.unauthorized=Impossible d''authentifier avec les cr\u00e9dits fournis (provided credentials)
+authenticator.userDataConstraint=Cette requ\u00eate viole une contrainte donn\u00e9e utilisateur (user data constraint) pour cette application
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/LocalStrings_ja.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/LocalStrings_ja.properties
new file mode 100644
index 0000000..ec5fbf3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/LocalStrings_ja.properties
@@ -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.
+
+authenticator.certificates=\u3053\u306e\u30ea\u30af\u30a8\u30b9\u30c8\u306b\u306f\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u8a8d\u8a3c\u30c1\u30a7\u30fc\u30f3\u304c\u3042\u308a\u307e\u305b\u3093
+authenticator.forbidden=\u30ea\u30af\u30a8\u30b9\u30c8\u3055\u308c\u305f\u30ea\u30bd\u30fc\u30b9\u3078\u306e\u30a2\u30af\u30bb\u30b9\u304c\u62d2\u5426\u3055\u308c\u307e\u3057\u305f
+authenticator.formlogin=\u30d5\u30a9\u30fc\u30e0\u30ed\u30b0\u30a4\u30f3\u30da\u30fc\u30b8\u3078\u306e\u7121\u52b9\u306a\u76f4\u63a5\u53c2\u7167\u3067\u3059
+authenticator.invalid=\u3053\u306e\u30ea\u30af\u30a8\u30b9\u30c8\u306b\u7121\u52b9\u306a\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u8a8d\u8a3c\u30c1\u30a7\u30fc\u30f3\u304c\u3042\u308a\u307e\u3059
+authenticator.keystore=\u30ad\u30fc\u30b9\u30c8\u30a2\u3092\u30ed\u30fc\u30c9\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+authenticator.manager=\u30c8\u30e9\u30b9\u30c8\u30de\u30cd\u30fc\u30b8\u30e3\u3092\u521d\u671f\u5316\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+authenticator.notAuthenticated=\u8a2d\u5b9a\u30a8\u30e9\u30fc: \u8a8d\u8a3c\u3055\u308c\u305f\u4e3b\u4f53\u306a\u3057\u306b\u30a2\u30af\u30bb\u30b9\u5236\u5fa1\u3092\u5b9f\u884c\u3067\u304d\u307e\u305b\u3093
+authenticator.notContext=\u8a2d\u5b9a\u30a8\u30e9\u30fc: \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306b\u6307\u5b9a\u3057\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+authenticator.sessionExpired=\u30ed\u30b0\u30a4\u30f3\u30d7\u30ed\u30bb\u30b9\u306b\u8a8d\u3081\u3089\u308c\u3066\u3044\u305f\u6642\u9593\u304c\u904e\u304e\u307e\u3057\u305f\u3002\u7d99\u7d9a\u3057\u305f\u3044\u306a\u3089\u3070\uff0c\u30d0\u30c3\u30af\u30dc\u30bf\u30f3\u30922\u5ea6\u62bc\u3057\u3066\u304b\u3089\u518d\u5ea6\u30ea\u30f3\u30af\u3092\u62bc\u3059\u304b\uff0c\u30d6\u30e9\u30a6\u30b6\u3092\u7acb\u3061\u4e0a\u3052\u76f4\u3057\u3066\u304f\u3060\u3055\u3044
+authenticator.unauthorized=\u7528\u610f\u3055\u308c\u305f\u8a3c\u660e\u66f8\u3067\u8a8d\u8a3c\u3067\u304d\u307e\u305b\u3093
+authenticator.userDataConstraint=\u3053\u306e\u30ea\u30af\u30a8\u30b9\u30c8\u306f\u3001\u3053\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u30e6\u30fc\u30b6\u30c7\u30fc\u30bf\u306e\u5236\u9650\u306b\u9055\u53cd\u3057\u3066\u3044\u307e\u3059
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/NonLoginAuthenticator.java b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/NonLoginAuthenticator.java
new file mode 100644
index 0000000..c5e349e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/NonLoginAuthenticator.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.catalina.authenticator;
+
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.deploy.LoginConfig;
+
+
+
+/**
+ * An <b>Authenticator</b> and <b>Valve</b> implementation that checks
+ * only security constraints not involving user authentication.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: NonLoginAuthenticator.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ */
+
+public final class NonLoginAuthenticator
+    extends AuthenticatorBase {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Descriptive information about this implementation.
+     */
+    private static final String info =
+        "org.apache.catalina.authenticator.NonLoginAuthenticator/1.0";
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return descriptive information about this Valve implementation.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Authenticate the user making this request, based on the specified
+     * login configuration.  Return <code>true</code> if any specified
+     * constraint has been satisfied, or <code>false</code> if we have
+     * created a response challenge already.
+     *
+     * @param request Request we are processing
+     * @param response Response we are populating
+     * @param config    Login configuration describing how authentication
+     *              should be performed
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public boolean authenticate(Request request,
+                                HttpServletResponse response,
+                                LoginConfig config)
+        throws IOException {
+
+        /*  Associating this request's session with an SSO would allow
+            coordinated session invalidation, but should the session for
+            a webapp that the user didn't log into be invalidated when
+            another session is logged out?
+        String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
+        if (ssoId != null)
+            associate(ssoId, getSession(request, true));
+        */
+        
+        if (containerLog.isDebugEnabled())
+            containerLog.debug("User authentication is not required");
+        return (true);
+
+
+    }
+
+
+    @Override
+    protected String getAuthMethod() {
+        return "NONE";
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/SSLAuthenticator.java b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/SSLAuthenticator.java
new file mode 100644
index 0000000..0d21237
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/SSLAuthenticator.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.authenticator;
+
+
+import java.io.IOException;
+import java.security.Principal;
+import java.security.cert.X509Certificate;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.Globals;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.deploy.LoginConfig;
+import org.apache.coyote.ActionCode;
+
+
+
+/**
+ * An <b>Authenticator</b> and <b>Valve</b> implementation of authentication
+ * that utilizes SSL certificates to identify client users.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: SSLAuthenticator.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ */
+
+public class SSLAuthenticator
+    extends AuthenticatorBase {
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Descriptive information about this implementation.
+     */
+    protected static final String info =
+        "org.apache.catalina.authenticator.SSLAuthenticator/1.0";
+
+
+    /**
+     * Return descriptive information about this Valve implementation.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Authenticate the user by checking for the existence of a certificate
+     * chain, validating it against the trust manager for the connector and then
+     * validating the user's identity against the configured Realm.
+     *
+     * @param request Request we are processing
+     * @param response Response we are creating
+     * @param config    Login configuration describing how authentication
+     *              should be performed
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public boolean authenticate(Request request,
+                                HttpServletResponse response,
+                                LoginConfig config)
+        throws IOException {
+
+        // Have we already authenticated someone?
+        Principal principal = request.getUserPrincipal();
+        //String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
+        if (principal != null) {
+            if (containerLog.isDebugEnabled())
+                containerLog.debug("Already authenticated '" + principal.getName() + "'");
+            // Associate the session with any existing SSO session in order
+            // to get coordinated session invalidation at logout
+            String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
+            if (ssoId != null)
+                associate(ssoId, request.getSessionInternal(true));
+            return (true);
+        }
+
+        // NOTE: We don't try to reauthenticate using any existing SSO session,
+        // because that will only work if the original authentication was
+        // BASIC or FORM, which are less secure than the CLIENT_CERT auth-type
+        // specified for this webapp
+        //
+        // Uncomment below to allow previous FORM or BASIC authentications
+        // to authenticate users for this webapp
+        // TODO make this a configurable attribute (in SingleSignOn??)
+        /*
+        // Is there an SSO session against which we can try to reauthenticate?
+        if (ssoId != null) {
+            if (log.isDebugEnabled())
+                log.debug("SSO Id " + ssoId + " set; attempting " +
+                          "reauthentication");
+            // Try to reauthenticate using data cached by SSO.  If this fails,
+            // either the original SSO logon was of DIGEST or SSL (which
+            // we can't reauthenticate ourselves because there is no
+            // cached username and password), or the realm denied
+            // the user's reauthentication for some reason.
+            // In either case we have to prompt the user for a logon
+            if (reauthenticateFromSSO(ssoId, request))
+                return true;
+        }
+        */
+
+        // Retrieve the certificate chain for this client
+        if (containerLog.isDebugEnabled())
+            containerLog.debug(" Looking up certificates");
+
+        X509Certificate certs[] = (X509Certificate[])
+            request.getAttribute(Globals.CERTIFICATES_ATTR);
+        if ((certs == null) || (certs.length < 1)) {
+            try {
+                request.getCoyoteRequest().action
+                                  (ActionCode.REQ_SSL_CERTIFICATE, null);
+            } catch (IllegalStateException ise) {
+                // Request body was too large for save buffer
+                response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
+                        sm.getString("authenticator.certificates"));
+                return false;
+            }
+            certs = (X509Certificate[])
+                request.getAttribute(Globals.CERTIFICATES_ATTR);
+        }
+        if ((certs == null) || (certs.length < 1)) {
+            if (containerLog.isDebugEnabled())
+                containerLog.debug("  No certificates included with this request");
+            response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
+                               sm.getString("authenticator.certificates"));
+            return (false);
+        }
+
+        // Authenticate the specified certificate chain
+        principal = context.getRealm().authenticate(certs);
+        if (principal == null) {
+            if (containerLog.isDebugEnabled())
+                containerLog.debug("  Realm.authenticate() returned false");
+            response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
+                               sm.getString("authenticator.unauthorized"));
+            return (false);
+        }
+
+        // Cache the principal (if requested) and record this authentication
+        register(request, response, principal, Constants.CERT_METHOD,
+                 null, null);
+        return (true);
+
+    }
+
+
+    @Override
+    protected String getAuthMethod() {
+        return Constants.CERT_METHOD;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/SavedRequest.java b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/SavedRequest.java
new file mode 100644
index 0000000..d2763f9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/SavedRequest.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.catalina.authenticator;
+
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+
+import javax.servlet.http.Cookie;
+
+import org.apache.tomcat.util.buf.ByteChunk;
+
+
+/**
+ * Object that saves the critical information from a request so that
+ * form-based authentication can reproduce it once the user has been
+ * authenticated.
+ * <p>
+ * <b>IMPLEMENTATION NOTE</b> - It is assumed that this object is accessed
+ * only from the context of a single thread, so no synchronization around
+ * internal collection classes is performed.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: SavedRequest.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ */
+
+public final class SavedRequest {
+
+
+    /**
+     * The set of Cookies associated with this Request.
+     */
+    private ArrayList<Cookie> cookies = new ArrayList<Cookie>();
+
+    public void addCookie(Cookie cookie) {
+        cookies.add(cookie);
+    }
+
+    public Iterator<Cookie> getCookies() {
+        return (cookies.iterator());
+    }
+
+
+    /**
+     * The set of Headers associated with this Request.  Each key is a header
+     * name, while the value is a ArrayList containing one or more actual
+     * values for this header.  The values are returned as an Iterator when
+     * you ask for them.
+     */
+    private HashMap<String,ArrayList<String>> headers =
+        new HashMap<String,ArrayList<String>>();
+
+    public void addHeader(String name, String value) {
+        ArrayList<String> values = headers.get(name);
+        if (values == null) {
+            values = new ArrayList<String>();
+            headers.put(name, values);
+        }
+        values.add(value);
+    }
+
+    public Iterator<String> getHeaderNames() {
+        return (headers.keySet().iterator());
+    }
+
+    public Iterator<String> getHeaderValues(String name) {
+        ArrayList<String> values = headers.get(name);
+        if (values == null)
+            return ((new ArrayList<String>()).iterator());
+        else
+            return (values.iterator());
+    }
+
+
+    /**
+     * The set of Locales associated with this Request.
+     */
+    private ArrayList<Locale> locales = new ArrayList<Locale>();
+
+    public void addLocale(Locale locale) {
+        locales.add(locale);
+    }
+
+    public Iterator<Locale> getLocales() {
+        return (locales.iterator());
+    }
+
+
+    /**
+     * The request method used on this Request.
+     */
+    private String method = null;
+
+    public String getMethod() {
+        return (this.method);
+    }
+
+    public void setMethod(String method) {
+        this.method = method;
+    }
+
+
+    /**
+     * The query string associated with this Request.
+     */
+    private String queryString = null;
+
+    public String getQueryString() {
+        return (this.queryString);
+    }
+
+    public void setQueryString(String queryString) {
+        this.queryString = queryString;
+    }
+
+
+    /**
+     * The request URI associated with this Request.
+     */
+    private String requestURI = null;
+
+    public String getRequestURI() {
+        return (this.requestURI);
+    }
+
+    public void setRequestURI(String requestURI) {
+        this.requestURI = requestURI;
+    }
+
+    
+    /**
+     * The body of this request.
+     */
+    private ByteChunk body = null;
+    
+    public ByteChunk getBody() {
+        return (this.body);
+    }
+
+    public void setBody(ByteChunk body) {
+        this.body = body;
+    }
+    
+    /**
+     * The content type of the request, used if this is a POST.
+     */
+    private String contentType = null;
+    
+    public String getContentType() {
+        return (this.contentType);
+    }
+    
+    public void setContentType(String contentType) {
+        this.contentType = contentType;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/SingleSignOn.java b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/SingleSignOn.java
new file mode 100644
index 0000000..1744939
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/SingleSignOn.java
@@ -0,0 +1,598 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.authenticator;
+
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+
+import org.apache.catalina.Realm;
+import org.apache.catalina.Session;
+import org.apache.catalina.SessionEvent;
+import org.apache.catalina.SessionListener;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.valves.ValveBase;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * A <strong>Valve</strong> that supports a "single sign on" user experience,
+ * where the security identity of a user who successfully authenticates to one
+ * web application is propagated to other web applications 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 Craig R. McClanahan
+ * @version $Id: SingleSignOn.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ */
+
+public class SingleSignOn extends ValveBase implements SessionListener {
+
+    //------------------------------------------------------ Constructor
+    public SingleSignOn() {
+        super(true);
+    }
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The cache of SingleSignOnEntry instances for authenticated Principals,
+     * keyed by the cookie value that is used to select them.
+     */
+    protected Map<String,SingleSignOnEntry> cache =
+        new HashMap<String,SingleSignOnEntry>();
+
+
+    /**
+     * Descriptive information about this Valve implementation.
+     */
+    protected static final String info =
+        "org.apache.catalina.authenticator.SingleSignOn";
+
+
+    /**
+     * Indicates whether this valve should require a downstream Authenticator to
+     * reauthenticate each request, or if it itself can bind a UserPrincipal
+     * and AuthType object to the request.
+     */
+    private boolean requireReauthentication = false;
+
+    /**
+     * The cache of single sign on identifiers, keyed by the Session that is
+     * associated with them.
+     */
+    protected Map<Session,String> reverse = new HashMap<Session,String>();
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
+     * Optional SSO cookie domain.
+     */
+    private String cookieDomain;
+
+    // ------------------------------------------------------------- Properties
+
+    /**
+     * Returns the optional cookie domain.
+     * May return null.
+     *
+     * @return The cookie domain
+     */
+    public String getCookieDomain() {
+        return cookieDomain;
+    }
+    /**
+     * Sets the domain to be used for sso cookies.
+     *
+     * @param cookieDomain cookie domain name
+     */
+    public void setCookieDomain(String cookieDomain) {
+        if (cookieDomain != null && cookieDomain.trim().length() == 0) {
+            this.cookieDomain = null;
+        } else {
+            this.cookieDomain = cookieDomain;
+        }
+    }
+
+    /**
+     * Gets whether each request needs to be reauthenticated (by an
+     * Authenticator downstream in the pipeline) to the security
+     * <code>Realm</code>, or if this Valve can itself bind security info
+     * to the request based on the presence of a valid SSO entry without
+     * rechecking with the <code>Realm</code..
+     *
+     * @return  <code>true</code> if it is required that a downstream
+     *          Authenticator reauthenticate each request before calls to
+     *          <code>HttpServletRequest.setUserPrincipal()</code>
+     *          and <code>HttpServletRequest.setAuthType()</code> are made;
+     *          <code>false</code> if the <code>Valve</code> can itself make
+     *          those calls relying on the presence of a valid SingleSignOn
+     *          entry associated with the request.
+     *
+     * @see #setRequireReauthentication
+     */
+    public boolean getRequireReauthentication()
+    {
+        return requireReauthentication;
+    }
+
+
+    /**
+     * Sets whether each request needs to be reauthenticated (by an
+     * Authenticator downstream in the pipeline) to the security
+     * <code>Realm</code>, or if this Valve can itself bind security info
+     * to the request, based on the presence of a valid SSO entry, without
+     * rechecking with the <code>Realm</code.
+     * <p>
+     * If this property is <code>false</code> (the default), this
+     * <code>Valve</code> will bind a UserPrincipal and AuthType to the request
+     * if a valid SSO entry is associated with the request.  It will not notify
+     * the security <code>Realm</code> of the incoming request.
+     * <p>
+     * This property should be set to <code>true</code> if the overall server
+     * configuration requires that the <code>Realm</code> reauthenticate each
+     * request thread.  An example of such a configuration would be one where
+     * the <code>Realm</code> implementation provides security for both a
+     * web tier and an associated EJB tier, and needs to set security
+     * credentials on each request thread in order to support EJB access.
+     * <p>
+     * If this property is set to <code>true</code>, this Valve will set flags
+     * on the request notifying the downstream Authenticator that the request
+     * is associated with an SSO session.  The Authenticator will then call its
+     * {@link AuthenticatorBase#reauthenticateFromSSO reauthenticateFromSSO}
+     * method to attempt to reauthenticate the request to the
+     * <code>Realm</code>, using any credentials that were cached with this
+     * Valve.
+     * <p>
+     * The default value of this property is <code>false</code>, in order
+     * to maintain backward compatibility with previous versions of Tomcat.
+     *
+     * @param required  <code>true</code> if it is required that a downstream
+     *                  Authenticator reauthenticate each request before calls
+     *                  to  <code>HttpServletRequest.setUserPrincipal()</code>
+     *                  and <code>HttpServletRequest.setAuthType()</code> are
+     *                  made; <code>false</code> if the <code>Valve</code> can
+     *                  itself make those calls relying on the presence of a
+     *                  valid SingleSignOn entry associated with the request.
+     *
+     * @see AuthenticatorBase#reauthenticateFromSSO
+     */
+    public void setRequireReauthentication(boolean required)
+    {
+        this.requireReauthentication = required;
+    }
+
+
+    // ------------------------------------------------ SessionListener Methods
+
+
+    /**
+     * Acknowledge the occurrence of the specified event.
+     *
+     * @param event SessionEvent that has occurred
+     */
+    @Override
+    public void sessionEvent(SessionEvent event) {
+
+        // We only care about session destroyed events
+        if (!Session.SESSION_DESTROYED_EVENT.equals(event.getType())
+                && (!Session.SESSION_PASSIVATED_EVENT.equals(event.getType())))
+            return;
+
+        // Look up the single session id associated with this session (if any)
+        Session session = event.getSession();
+        if (containerLog.isDebugEnabled())
+            containerLog.debug("Process session destroyed on " + session);
+
+        String ssoId = null;
+        synchronized (reverse) {
+            ssoId = reverse.get(session);
+        }
+        if (ssoId == null)
+            return;
+
+        // Was the session destroyed as the result of a timeout?
+        // If so, we'll just remove the expired session from the
+        // SSO.  If the session was logged out, we'll log out
+        // of all session associated with the SSO.
+        if (((session.getMaxInactiveInterval() > 0)
+            && (System.currentTimeMillis() - session.getThisAccessedTimeInternal() >=
+                session.getMaxInactiveInterval() * 1000)) 
+            || (Session.SESSION_PASSIVATED_EVENT.equals(event.getType()))) {
+            removeSession(ssoId, session);
+        } else {
+            // The session was logged out.
+            // Deregister this single session id, invalidating 
+            // associated sessions
+            deregister(ssoId);
+        }
+
+    }
+
+
+    // ---------------------------------------------------------- Valve Methods
+
+
+    /**
+     * Return descriptive information about this Valve implementation.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    /**
+     * Perform single-sign-on support processing for this request.
+     *
+     * @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 error occurs
+     */
+    @Override
+    public void invoke(Request request, Response response)
+        throws IOException, ServletException {
+
+        request.removeNote(Constants.REQ_SSOID_NOTE);
+
+        // Has a valid user already been authenticated?
+        if (containerLog.isDebugEnabled())
+            containerLog.debug("Process request for '" + request.getRequestURI() + "'");
+        if (request.getUserPrincipal() != null) {
+            if (containerLog.isDebugEnabled())
+                containerLog.debug(" Principal '" + request.getUserPrincipal().getName() +
+                    "' has already been authenticated");
+            getNext().invoke(request, response);
+            return;
+        }
+
+        // Check for the single sign on cookie
+        if (containerLog.isDebugEnabled())
+            containerLog.debug(" Checking for SSO cookie");
+        Cookie cookie = null;
+        Cookie cookies[] = request.getCookies();
+        if (cookies == null)
+            cookies = new Cookie[0];
+        for (int i = 0; i < cookies.length; i++) {
+            if (Constants.SINGLE_SIGN_ON_COOKIE.equals(cookies[i].getName())) {
+                cookie = cookies[i];
+                break;
+            }
+        }
+        if (cookie == null) {
+            if (containerLog.isDebugEnabled())
+                containerLog.debug(" SSO cookie is not present");
+            getNext().invoke(request, response);
+            return;
+        }
+
+        // Look up the cached Principal associated with this cookie value
+        if (containerLog.isDebugEnabled())
+            containerLog.debug(" Checking for cached principal for " + cookie.getValue());
+        SingleSignOnEntry entry = lookup(cookie.getValue());
+        if (entry != null) {
+            if (containerLog.isDebugEnabled())
+                containerLog.debug(" Found cached principal '" +
+                    (entry.getPrincipal() != null ? entry.getPrincipal().getName() : "") + "' with auth type '" +
+                    entry.getAuthType() + "'");
+            request.setNote(Constants.REQ_SSOID_NOTE, cookie.getValue());
+            // Only set security elements if reauthentication is not required
+            if (!getRequireReauthentication()) {
+                request.setAuthType(entry.getAuthType());
+                request.setUserPrincipal(entry.getPrincipal());
+            }
+        } else {
+            if (containerLog.isDebugEnabled())
+                containerLog.debug(" No cached principal found, erasing SSO cookie");
+            cookie.setMaxAge(0);
+            response.addCookie(cookie);
+        }
+
+        // Invoke the next Valve in our pipeline
+        getNext().invoke(request, response);
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Associate the specified single sign on identifier with the
+     * specified Session.
+     *
+     * @param ssoId Single sign on identifier
+     * @param session Session to be associated
+     */
+    protected void associate(String ssoId, Session session) {
+
+        if (containerLog.isDebugEnabled())
+            containerLog.debug("Associate sso id " + ssoId + " with session " + session);
+
+        SingleSignOnEntry sso = lookup(ssoId);
+        if (sso != null)
+            sso.addSession(this, session);
+        synchronized (reverse) {
+            reverse.put(session, ssoId);
+        }
+
+    }
+
+    /**
+     * Deregister the specified session.  If it is the last session,
+     * then also get rid of the single sign on identifier
+     *
+     * @param ssoId Single sign on identifier
+     * @param session Session to be deregistered
+     */
+    protected void deregister(String ssoId, Session session) {
+
+        synchronized (reverse) {
+            reverse.remove(session);
+        }
+
+        SingleSignOnEntry sso = lookup(ssoId);
+        if (sso == null)
+            return;
+
+        sso.removeSession(session);
+
+        // see if we are the last session, if so blow away ssoId
+        Session sessions[] = sso.findSessions();
+        if (sessions == null || sessions.length == 0) {
+            synchronized (cache) {
+                cache.remove(ssoId);
+            }
+        }
+
+    }
+
+
+    /**
+     * Deregister the specified single sign on identifier, and invalidate
+     * any associated sessions.
+     *
+     * @param ssoId Single sign on identifier to deregister
+     */
+    protected void deregister(String ssoId) {
+
+        if (containerLog.isDebugEnabled())
+            containerLog.debug("Deregistering sso id '" + ssoId + "'");
+
+        // Look up and remove the corresponding SingleSignOnEntry
+        SingleSignOnEntry sso = null;
+        synchronized (cache) {
+            sso = cache.remove(ssoId);
+        }
+
+        if (sso == null)
+            return;
+
+        // Expire any associated sessions
+        Session sessions[] = sso.findSessions();
+        for (int i = 0; i < sessions.length; i++) {
+            if (containerLog.isTraceEnabled())
+                containerLog.trace(" Invalidating session " + sessions[i]);
+            // Remove from reverse cache first to avoid recursion
+            synchronized (reverse) {
+                reverse.remove(sessions[i]);
+            }
+            // Invalidate this session
+            sessions[i].expire();
+        }
+
+        // NOTE:  Clients may still possess the old single sign on cookie,
+        // but it will be removed on the next request since it is no longer
+        // in the cache
+
+    }
+
+
+    /**
+     * Attempts reauthentication to the given <code>Realm</code> using
+     * the credentials associated with the single sign-on session
+     * identified by argument <code>ssoId</code>.
+     * <p>
+     * If reauthentication is successful, the <code>Principal</code> and
+     * authorization type associated with the SSO session will be bound
+     * to the given <code>Request</code> object via calls to 
+     * {@link Request#setAuthType Request.setAuthType()} and 
+     * {@link Request#setUserPrincipal Request.setUserPrincipal()}
+     * </p>
+     *
+     * @param ssoId     identifier of SingleSignOn session with which the
+     *                  caller is associated
+     * @param realm     Realm implementation against which the caller is to
+     *                  be authenticated
+     * @param request   the request that needs to be authenticated
+     * 
+     * @return  <code>true</code> if reauthentication was successful,
+     *          <code>false</code> otherwise.
+     */
+    protected boolean reauthenticate(String ssoId, Realm realm,
+                                     Request request) {
+
+        if (ssoId == null || realm == null)
+            return false;
+
+        boolean reauthenticated = false;
+
+        SingleSignOnEntry entry = lookup(ssoId);
+        if (entry != null && entry.getCanReauthenticate()) {
+            
+            String username = entry.getUsername();
+            if (username != null) {
+                Principal reauthPrincipal =
+                        realm.authenticate(username, entry.getPassword());                
+                if (reauthPrincipal != null) {                    
+                    reauthenticated = true;                    
+                    // Bind the authorization credentials to the request
+                    request.setAuthType(entry.getAuthType());
+                    request.setUserPrincipal(reauthPrincipal);
+                }
+            }
+        }
+
+        return reauthenticated;
+    }
+
+
+    /**
+     * 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
+     */
+    protected void register(String ssoId, Principal principal, String authType,
+                  String username, String password) {
+
+        if (containerLog.isDebugEnabled())
+            containerLog.debug("Registering sso id '" + ssoId + "' for user '" +
+                (principal != null ? principal.getName() : "") + "' with auth type '" + authType + "'");
+
+        synchronized (cache) {
+            cache.put(ssoId, new SingleSignOnEntry(principal, authType,
+                                                   username, password));
+        }
+
+    }
+
+
+    /**
+     * 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
+     */
+    protected void update(String ssoId, Principal principal, String authType,
+                          String username, String password) {
+
+        SingleSignOnEntry sso = lookup(ssoId);
+        if (sso != null && !sso.getCanReauthenticate()) {
+            if (containerLog.isDebugEnabled())
+                containerLog.debug("Update sso id " + ssoId + " to auth type " + authType);
+
+            synchronized(sso) {
+                sso.updateCredentials(principal, authType, username, password);
+            }
+
+        }
+    }
+
+
+    /**
+     * Look up and return the cached SingleSignOn entry associated with this
+     * sso id value, if there is one; otherwise return <code>null</code>.
+     *
+     * @param ssoId Single sign on identifier to look up
+     */
+    protected SingleSignOnEntry lookup(String ssoId) {
+
+        synchronized (cache) {
+            return cache.get(ssoId);
+        }
+
+    }
+
+    
+    /**
+     * Remove a single Session from a SingleSignOn.  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.
+     */
+    protected void removeSession(String ssoId, Session session) {
+
+        if (containerLog.isDebugEnabled())
+            containerLog.debug("Removing session " + session.toString() + " from sso id " + 
+                ssoId );
+
+        // Get a reference to the SingleSignOn
+        SingleSignOnEntry entry = lookup(ssoId);
+        if (entry == null)
+            return;
+
+        // Remove the inactive session from SingleSignOnEntry
+        entry.removeSession(session);
+
+        // Remove the inactive session from the 'reverse' Map.
+        synchronized(reverse) {
+            reverse.remove(session);
+        }
+
+        // If there are not sessions left in the SingleSignOnEntry,
+        // deregister the entry.
+        if (entry.findSessions().length == 0) {
+            deregister(ssoId);
+        }
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/SingleSignOnEntry.java b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/SingleSignOnEntry.java
new file mode 100644
index 0000000..ecb6c8a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/SingleSignOnEntry.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.authenticator;
+
+import java.security.Principal;
+
+import org.apache.catalina.Session;
+
+/**
+ * A class that represents entries in the cache of authenticated users.
+ * This is necessary to make it available to
+ * <code>AuthenticatorBase</code> subclasses that need it in order to perform
+ * reauthentications when SingleSignOn is in use.
+ *
+ * @author  B Stansberry, based on work by Craig R. McClanahan
+ * @version $Revision: 1.1 $
+ *
+ * @see SingleSignOn
+ * @see AuthenticatorBase#reauthenticateFromSSO
+ */
+public class SingleSignOnEntry
+{
+    // ------------------------------------------------------  Instance Fields
+
+    protected String authType = null;
+
+    protected String password = null;
+
+    protected Principal principal = null;
+
+    protected Session sessions[] = new Session[0];
+
+    protected String username = null;
+
+    protected boolean canReauthenticate = false;
+
+    // ---------------------------------------------------------  Constructors
+
+    /**
+     * Creates a new SingleSignOnEntry
+     *
+     * @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
+     */
+    public SingleSignOnEntry(Principal principal, String authType,
+                             String username, String password) {
+
+        updateCredentials(principal, authType, username, password);
+    }
+
+    // ------------------------------------------------------- Package Methods
+
+    /**
+     * Adds a <code>Session</code> to the list of those associated with
+     * this SSO.
+     *
+     * @param sso       The <code>SingleSignOn</code> valve that is managing
+     *                  the SSO session.
+     * @param session   The <code>Session</code> being associated with the SSO.
+     */
+    public synchronized void addSession(SingleSignOn sso, Session session) {
+        for (int i = 0; i < sessions.length; i++) {
+            if (session == sessions[i])
+                return;
+        }
+        Session results[] = new Session[sessions.length + 1];
+        System.arraycopy(sessions, 0, results, 0, sessions.length);
+        results[sessions.length] = session;
+        sessions = results;
+        session.addSessionListener(sso);
+    }
+
+    /**
+     * Removes the given <code>Session</code> from the list of those
+     * associated with this SSO.
+     *
+     * @param session  the <code>Session</code> to remove.
+     */
+    public synchronized void removeSession(Session session) {
+        Session[] nsessions = new Session[sessions.length - 1];
+        for (int i = 0, j = 0; i < sessions.length; i++) {
+            if (session == sessions[i])
+                continue;
+            nsessions[j++] = sessions[i];
+        }
+        sessions = nsessions;
+    }
+
+    /**
+     * Returns the <code>Session</code>s associated with this SSO.
+     */
+    public synchronized Session[] findSessions() {
+        return (this.sessions);
+    }
+
+    /**
+     * Gets the name of the authentication type originally used to authenticate
+     * the user associated with the SSO.
+     *
+     * @return "BASIC", "CLIENT_CERT", "DIGEST", "FORM" or "NONE"
+     */
+    public String getAuthType() {
+        return (this.authType);
+    }
+
+    /**
+     * Gets whether the authentication type associated with the original
+     * authentication supports reauthentication.
+     *
+     * @return  <code>true</code> if <code>getAuthType</code> returns
+     *          "BASIC" or "FORM", <code>false</code> otherwise.
+     */
+    public boolean getCanReauthenticate() {
+        return (this.canReauthenticate);
+    }
+
+    /**
+     * Gets the password credential (if any) associated with the SSO.
+     *
+     * @return  the password credential associated with the SSO, or
+     *          <code>null</code> if the original authentication type
+     *          does not involve a password.
+     */
+    public String getPassword() {
+        return (this.password);
+    }
+
+    /**
+     * Gets the <code>Principal</code> that has been authenticated by
+     * the SSO.
+     */
+    public Principal getPrincipal() {
+        return (this.principal);
+    }
+
+    /**
+     * Gets the username provided by the user as part of the authentication
+     * process.
+     */
+    public String getUsername() {
+        return (this.username);
+    }
+
+
+    /**
+     * Updates the SingleSignOnEntry to reflect the latest security
+     * information associated with the caller.
+     *
+     * @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
+     */
+    public void updateCredentials(Principal principal, String authType,
+                                  String username, String password) {
+
+        this.principal = principal;
+        this.authType = authType;
+        this.username = username;
+        this.password = password;
+        this.canReauthenticate =
+            (Constants.BASIC_METHOD.equals(authType)
+                || Constants.FORM_METHOD.equals(authType));
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/SpnegoAuthenticator.java b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/SpnegoAuthenticator.java
new file mode 100644
index 0000000..a6389b1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/SpnegoAuthenticator.java
@@ -0,0 +1,263 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.authenticator;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.Principal;
+
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.deploy.LoginConfig;
+import org.apache.catalina.startup.Bootstrap;
+import org.apache.catalina.util.Base64;
+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.MessageBytes;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.Oid;
+
+
+/**
+ * A SPNEGO authenticator that uses the SPENGO/Kerberos support built in to Java
+ * 6. Successful Kerberos authentication depends on the correct configuration of
+ * multiple components. If the configuration is invalid, the error messages are
+ * often cryptic although a Google search will usually point you in the right
+ * direction.
+ */
+public class SpnegoAuthenticator extends AuthenticatorBase {
+
+    private static final Log log = LogFactory.getLog(SpnegoAuthenticator.class);
+    
+    private String loginConfigName = Constants.DEFAULT_LOGIN_MODULE_NAME;
+    public String getLoginConfigName() {
+        return loginConfigName;
+    }
+    public void setLoginConfigName(String loginConfigName) {
+        this.loginConfigName = loginConfigName;
+    }
+
+    private boolean storeDelegatedCredential = true;
+    public boolean isStoreDelegatedCredential() {
+        return storeDelegatedCredential;
+    }
+    public void setStoreDelegatedCredential(
+            boolean storeDelegatedCredential) {
+        this.storeDelegatedCredential = storeDelegatedCredential;
+    }
+
+
+    @Override
+    protected String getAuthMethod() {
+        return Constants.SPNEGO_METHOD;
+    }
+
+
+    @Override
+    public String getInfo() {
+        return "org.apache.catalina.authenticator.SpnegoAuthenticator/1.0";
+    }
+
+
+    @Override
+    protected void initInternal() throws LifecycleException {
+        super.initInternal();
+
+        // Kerberos configuration file location
+        String krb5Conf = System.getProperty(Constants.KRB5_CONF_PROPERTY);
+        if (krb5Conf == null) {
+            // System property not set, use the Tomcat default
+            File krb5ConfFile = new File(Bootstrap.getCatalinaBase(),
+                    Constants.DEFAULT_KRB5_CONF);
+            System.setProperty(Constants.KRB5_CONF_PROPERTY,
+                    krb5ConfFile.getAbsolutePath());
+        }
+
+        // JAAS configuration file location
+        String jaasConf = System.getProperty(Constants.JAAS_CONF_PROPERTY);
+        if (jaasConf == null) {
+            // System property not set, use the Tomcat default
+            File jaasConfFile = new File(Bootstrap.getCatalinaBase(),
+                    Constants.DEFAULT_JAAS_CONF);
+            System.setProperty(Constants.JAAS_CONF_PROPERTY,
+                    jaasConfFile.getAbsolutePath());
+        }
+        
+        // This property must be false for SPNEGO to work
+        System.setProperty(Constants.USE_SUBJECT_CREDS_ONLY_PROPERTY, "false");
+    }
+
+
+    @Override
+    public boolean authenticate(Request request, HttpServletResponse response,
+            LoginConfig config) throws IOException {
+
+        // Have we already authenticated someone?
+        Principal principal = request.getUserPrincipal();
+        String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
+        if (principal != null) {
+            if (log.isDebugEnabled())
+                log.debug("Already authenticated '" + principal.getName() + "'");
+            // Associate the session with any existing SSO session
+            if (ssoId != null)
+                associate(ssoId, request.getSessionInternal(true));
+            return true;
+        }
+
+        // Is there an SSO session against which we can try to reauthenticate?
+        if (ssoId != null) {
+            if (log.isDebugEnabled())
+                log.debug("SSO Id " + ssoId + " set; attempting " +
+                          "reauthentication");
+            /* Try to reauthenticate using data cached by SSO.  If this fails,
+               either the original SSO logon was of DIGEST or SSL (which
+               we can't reauthenticate ourselves because there is no
+               cached username and password), or the realm denied
+               the user's reauthentication for some reason.
+               In either case we have to prompt the user for a logon */
+            if (reauthenticateFromSSO(ssoId, request))
+                return true;
+        }
+
+        MessageBytes authorization = 
+            request.getCoyoteRequest().getMimeHeaders()
+            .getValue("authorization");
+        
+        if (authorization == null) {
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString("authenticator.noAuthHeader"));
+            }
+            response.setHeader("WWW-Authenticate", "Negotiate");
+            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+            return false;
+        }
+        
+        authorization.toBytes();
+        ByteChunk authorizationBC = authorization.getByteChunk();
+
+        if (!authorizationBC.startsWithIgnoreCase("negotiate ", 0)) {
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString(
+                        "spnegoAuthenticator.authHeaderNotNego"));
+            }
+            response.setHeader("WWW-Authenticate", "Negotiate");
+            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+            return false;
+        }
+
+        authorizationBC.setOffset(authorizationBC.getOffset() + 10);
+        // FIXME: Add trimming
+        // authorizationBC.trim();
+                
+        ByteChunk decoded = new ByteChunk();
+        Base64.decode(authorizationBC, decoded);
+
+        if (decoded.getLength() == 0) {
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString(
+                        "spnegoAuthenticator.authHeaderNoToken"));
+            }
+            response.setHeader("WWW-Authenticate", "Negotiate");
+            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+            return false;
+        }
+
+        LoginContext lc = null;
+        GSSContext gssContext = null;
+        byte[] outToken = null;
+        try {
+            try {
+                lc = new LoginContext(loginConfigName);
+                lc.login();
+            } catch (LoginException e) {
+                log.error(sm.getString("spnegoAuthenticator.serviceLoginFail"),
+                        e);
+                response.sendError(
+                        HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+                return false;
+            }
+            // Assume the GSSContext is stateless
+            // TODO: Confirm this assumption
+            GSSManager manager = GSSManager.getInstance();
+            gssContext = manager.createContext(manager.createCredential(null,
+                    GSSCredential.DEFAULT_LIFETIME,
+                    new Oid("1.3.6.1.5.5.2"),
+                    GSSCredential.ACCEPT_ONLY));
+
+            outToken = gssContext.acceptSecContext(decoded.getBytes(),
+                    decoded.getOffset(), decoded.getLength());
+
+            if (outToken == null) {
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString(
+                            "spnegoAuthenticator.ticketValidateFail"));
+                }
+                // Start again
+                response.setHeader("WWW-Authenticate", "Negotiate");
+                response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+                return false;
+            }
+
+            principal = context.getRealm().authenticate(gssContext,
+                    storeDelegatedCredential);
+        } catch (GSSException e) {
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString("spnegoAuthenticator.ticketValidateFail",
+                        e));
+            }
+            response.setHeader("WWW-Authenticate", "Negotiate");
+            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+            return false;
+        } finally {
+            if (gssContext != null) {
+                try {
+                    gssContext.dispose();
+                } catch (GSSException e) {
+                    // Ignore
+                }
+            }
+            if (lc != null) {
+                try {
+                    lc.logout();
+                } catch (LoginException e) {
+                    // Ignore
+                }
+            }
+        }
+
+        // Send response token on success and failure
+        response.setHeader("WWW-Authenticate", "Negotiate "
+                + Base64.encode(outToken));
+
+        if (principal != null) {
+            register(request, response, principal, Constants.SPNEGO_METHOD,
+                    principal.getName(), null);
+            return true;
+        }
+
+        response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+        return false;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/mbeans-descriptors.xml b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/mbeans-descriptors.xml
new file mode 100644
index 0000000..5800ce0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/mbeans-descriptors.xml
@@ -0,0 +1,293 @@
+<?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="BasicAuthenticator"
+         description="An Authenticator and Valve implementation of HTTP BASIC Authentication"
+         domain="Catalina"
+         group="Valve"
+         type="org.apache.catalina.authenticator.BasicAuthenticator">
+    
+    <attribute name="alwaysUseSession"
+               description="Should a session always be used once a user is authenticated?"
+               type="boolean"/>
+      
+    <attribute name="cache"
+               description="Should we cache authenticated Principals if the request is part of an HTTP session?"
+               type="boolean"/>
+      
+    <attribute name="changeSessionIdOnAuthentication"
+               description="Controls if the session ID is changed if a session exists at the point where users are authenticated"
+               type="boolean"/>
+      
+    <attribute name="className"
+               description="Fully qualified class name of the managed object"
+               type="java.lang.String"
+               writeable="false"/>
+      
+    <attribute name="disableProxyCaching"
+               description="Controls the caching of pages that are protected by security constraints"
+               type="boolean"/>
+      
+    <attribute name="securePagesWithPragma"
+               description="Controls the caching of pages that are protected by security constraints"
+               type="boolean"/>
+      
+    <attribute name="secureRandomAlgorithm"
+               description="The name of the algorithm to use for SSO session ID generation"
+               type="java.lang.String"/>
+      
+    <attribute name="secureRandomClass"
+               description="The name of the class to use for SSO session ID generation"
+               type="java.lang.String"/>
+      
+    <attribute name="secureRandomProvider"
+               description="The name of the provider to use for SSO session ID generation"
+               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"/>
+  </mbean>
+  
+  
+  <mbean name="DigestAuthenticator"
+         description="An Authenticator and Valve implementation of HTTP DIGEST Authentication"
+         domain="Catalina"
+         group="Valve"
+         type="org.apache.catalina.authenticator.DigestAuthenticator">
+    
+    <attribute name="alwaysUseSession"
+               description="Should a session always be used once a user is authenticated?"
+               type="boolean"/>
+      
+    <attribute name="cache"
+               description="Should we cache authenticated Principals if the request is part of an HTTP session?"
+               type="boolean"/>
+
+    <attribute name="changeSessionIdOnAuthentication"
+               description="Controls if the session ID is changed if a session exists at the point where users are authenticated"
+               type="boolean"/>
+      
+    <attribute name="className"
+               description="Fully qualified class name of the managed object"
+               type="java.lang.String"
+               writeable="false"/>
+      
+    <attribute name="cnonceCacheSize"
+               description="The size of the cnonce cache used to prevent replay attacks"
+               type="int"/>
+      
+    <attribute name="disableProxyCaching"
+               description="Controls the caching of pages that are protected by security constraints"
+               type="boolean"/>
+      
+    <attribute name="key"
+               description="The secret key used by digest authentication"
+               type="java.lang.String"/>
+      
+    <attribute name="nonceValidity"
+               description="The time, in milliseconds, for which a server issued nonce will be valid"
+               type="long"/>
+
+    <attribute name="opaque"
+               description="The opaque server string used by digest authentication"
+               type="java.lang.String"/>
+      
+    <attribute name="securePagesWithPragma"
+               description="Controls the caching of pages that are protected by security constraints"
+               type="boolean"/>
+      
+    <attribute name="secureRandomAlgorithm"
+               description="The name of the algorithm to use for SSO session ID generation"
+               type="java.lang.String"/>
+      
+    <attribute name="secureRandomClass"
+               description="The name of the class to use for SSO session ID generation"
+               type="java.lang.String"/>
+      
+    <attribute name="secureRandomProvider"
+               description="The name of the provider to use for SSO session ID generation"
+               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="validateUri"
+               description="Should the uri be validated as required by RFC2617?"
+               type="boolean"/>
+  </mbean>
+  
+  <mbean name="FormAuthenticator"
+         description="An Authenticator and Valve implementation of FORM BASED Authentication"
+         domain="Catalina"
+         group="Valve"
+         type="org.apache.catalina.authenticator.FormAuthenticator">
+    
+    <attribute name="changeSessionIdOnAuthentication"
+               description="Controls if the session ID is changed if a session exists at the point where users are authenticated"
+               type="boolean"/>
+
+    <attribute name="characterEncoding"
+               description="Character encoding to use to read the username and password parameters from the request"
+               type="java.lang.String"/>
+
+    <attribute name="className"
+               description="Fully qualified class name of the managed object"
+               type="java.lang.String"
+               writeable="false"/>
+
+    <attribute name="disableProxyCaching"
+               description="Controls the caching of pages that are protected by security constraints"
+               type="boolean"/>
+      
+    <attribute name="landingPage"
+               description="Controls the behavior of the FORM authentication process if the process is misused, for example by directly requesting the login page or delaying logging in for so long that the session expires"
+               type="java.lang.String"/>
+
+    <attribute name="securePagesWithPragma"
+               description="Controls the caching of pages that are protected by security constraints"
+               type="boolean"/>
+      
+    <attribute name="secureRandomAlgorithm"
+               description="The name of the algorithm to use for SSO session ID generation"
+               type="java.lang.String"/>
+      
+    <attribute name="secureRandomClass"
+               description="The name of the class to use for SSO session ID generation"
+               type="java.lang.String"/>
+      
+    <attribute name="secureRandomProvider"
+               description="The name of the provider to use for SSO session ID generation"
+               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"/>
+  </mbean>
+  
+  <mbean name="NonLoginAuthenticator"
+         description="An Authenticator and Valve implementation that checks only security constraints not involving user authentication"
+         domain="Catalina"
+         group="Valve"
+         type="org.apache.catalina.authenticator.NonLoginAuthenticator">
+    
+    <attribute name="cache"
+               description="Should we cache authenticated Principals if the request is part of an HTTP session?"
+               type="boolean"/>
+      
+    <attribute name="changeSessionIdOnAuthentication"
+               description="Controls if the session ID is changed if a session exists at the point where users are authenticated"
+               type="boolean"/>
+
+    <attribute name="className"
+               description="Fully qualified class name of the managed object"
+               type="java.lang.String"
+               writeable="false"/>
+
+    <attribute name="disableProxyCaching"
+               description="Controls the caching of pages that are protected by security constraints"
+               type="boolean"/>
+      
+    <attribute name="securePagesWithPragma"
+               description="Controls the caching of pages that are protected by security constraints"
+               type="boolean"/>
+      
+    <attribute name="stateName"
+               description="The name of the LifecycleState that this component is currently in"
+               type="java.lang.String"
+               writeable="false"/>
+  </mbean>
+  
+  
+  <mbean name="SingleSignOn"
+         description="A Valve that supports a 'single signon' user experience"
+         domain="Catalina"
+         group="Valve"
+         type="org.apache.catalina.authenticator.SingleSignOn">
+    
+    <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" />
+
+    <attribute name="stateName"
+               description="The name of the LifecycleState that this component is currently in"
+               type="java.lang.String"
+               writeable="false"/>
+  </mbean>
+
+
+  <mbean name="SSLAuthenticator"
+         description="An Authenticator and Valve implementation of authentication that utilizes SSL certificates to identify client users"
+         domain="Catalina"
+         group="Valve"
+         type="org.apache.catalina.authenticator.SSLAuthenticator">
+
+    <attribute name="cache"
+               description="Should we cache authenticated Principals if the request is part of an HTTP session?"
+               type="boolean"/>
+
+    <attribute name="changeSessionIdOnAuthentication"
+               description="Controls if the session ID is changed if a session exists at the point where users are authenticated"
+               type="boolean"/>
+
+    <attribute name="className"
+               description="Fully qualified class name of the managed object"
+               type="java.lang.String"
+               writeable="false"/>
+
+    <attribute name="disableProxyCaching"
+               description="Controls the caching of pages that are protected by security constraints"
+               type="boolean"/>
+      
+    <attribute name="securePagesWithPragma"
+               description="Controls the caching of pages that are protected by security constraints"
+               type="boolean"/>
+      
+    <attribute name="secureRandomAlgorithm"
+               description="The name of the algorithm to use for SSO session ID generation"
+               type="java.lang.String"/>
+      
+    <attribute name="secureRandomClass"
+               description="The name of the class to use for SSO session ID generation"
+               type="java.lang.String"/>
+      
+    <attribute name="secureRandomProvider"
+               description="The name of the provider to use for SSO session ID generation"
+               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"/>
+  </mbean>
+  
+</mbeans-descriptors>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/package.html b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/package.html
new file mode 100644
index 0000000..95318fc
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/authenticator/package.html
@@ -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.
+-->
+<body>
+
+<p>This package contains <code>Authenticator</code> implementations for the
+various supported authentication methods (BASIC, DIGEST, and FORM).  In
+addition, there is a convenience base class,
+<code>AuthenticatorBase</code>, for customized <code>Authenticator</code>
+implementations.</p>
+
+<p>If you are using the standard context configuration class
+(<code>org.apache.catalina.startup.ContextConfig</code>) to configure the
+Authenticator associated with a particular context, you can register the Java
+class to be used for each possible authentication method by modifying the
+following Properties file:</p>
+<pre>
+    src/share/org/apache/catalina/startup/Authenticators.properties
+</pre>
+
+<p>Each of the standard implementations extends a common base class
+(<code>AuthenticatorBase</code>), which is configured by setting the
+following JavaBeans properties (with default values in square brackets):</p>
+<ul>
+<li><b>cache</b> - Should we cache authenticated Principals (thus avoiding
+    per-request lookups in our underyling <code>Realm</code>) if this request
+    is part of an HTTP session?  [true]</li>
+<li><b>debug</b> - Debugging detail level for this component.  [0]</li>
+</ul>
+
+<p>The standard authentication methods that are currently provided include:</p>
+<ul>
+<li><b>BasicAuthenticator</b> - Implements HTTP BASIC authentication, as
+    described in RFC 2617.</li>
+<li><b>DigestAuthenticator</b> - Implements HTTP DIGEST authentication, as
+    described in RFC 2617.</li>
+<li><b>FormAuthenticator</b> - Implements FORM-BASED authentication, as
+    described in the Servlet API Specification, version 2.2.</li>
+</ul>
+
+</body>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/comet/CometEvent.java b/bundles/org.apache.tomcat/src/org/apache/catalina/comet/CometEvent.java
new file mode 100644
index 0000000..29a884a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/comet/CometEvent.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.comet;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * The CometEvent interface.
+ * 
+ * @author Filip Hanik
+ * @author Remy Maucherat
+ */
+public interface CometEvent {
+
+    /**
+     * Enumeration describing the major events that the container can invoke 
+     * the CometProcessors event() method with
+     * BEGIN - will be called at the beginning 
+     *  of the processing of the connection. It can be used to initialize any relevant 
+     *  fields using the request and response objects. Between the end of the processing 
+     *  of this event, and the beginning of the processing of the end or error events,
+     *  it is possible to use the response object to write data on the open connection.
+     *  Note that the response object and dependent OutputStream and Writer are still 
+     *  not synchronized, so when they are accessed by multiple threads, 
+     *  synchronization is mandatory. After processing the initial event, the request 
+     *  is considered to be committed.
+     * READ - This indicates that input data is available, and that one read can be made
+     *  without blocking. The available and ready methods of the InputStream or
+     *  Reader may be used to determine if there is a risk of blocking: the servlet
+     *  should read while data is reported available. When encountering a read error, 
+     *  the servlet should report it by propagating the exception properly. Throwing 
+     *  an exception will cause the error event to be invoked, and the connection 
+     *  will be closed. 
+     *  Alternately, it is also possible to catch any exception, perform clean up
+     *  on any data structure the servlet may be using, and using the close method
+     *  of the event. It is not allowed to attempt reading data from the request 
+     *  object outside of the execution of this method.
+     * END - End may be called to end the processing of the request. Fields that have
+     *  been initialized in the begin method should be reset. After this event has
+     *  been processed, the request and response objects, as well as all their dependent
+     *  objects will be recycled and used to process other requests. End will also be 
+     *  called when data is available and the end of file is reached on the request input
+     *  (this usually indicates the client has pipelined a request).
+     * ERROR - Error will be called by the container in the case where an IO exception
+     *  or a similar unrecoverable error occurs on the connection. Fields that have
+     *  been initialized in the begin method should be reset. After this event has
+     *  been processed, the request and response objects, as well as all their dependent
+     *  objects will be recycled and used to process other requests.
+     */
+    public enum EventType {BEGIN, READ, END, ERROR}
+    
+    
+    /**
+     * Event details
+     * TIMEOUT - the connection timed out (sub type of ERROR); note that this ERROR type is not fatal, and
+     *   the connection will not be closed unless the servlet uses the close method of the event
+     * CLIENT_DISCONNECT - the client connection was closed (sub type of ERROR)
+     * IOEXCEPTION - an IO exception occurred, such as invalid content, for example, an invalid chunk block (sub type of ERROR)
+     * WEBAPP_RELOAD - the webapplication is being reloaded (sub type of END)
+     * SERVER_SHUTDOWN - the server is shutting down (sub type of END)
+     * SESSION_END - the servlet ended the session (sub type of END)
+     */
+    public enum EventSubType { TIMEOUT, CLIENT_DISCONNECT, IOEXCEPTION, WEBAPP_RELOAD, SERVER_SHUTDOWN, SESSION_END }
+    
+    
+    /**
+     * Returns the HttpServletRequest.
+     * 
+     * @return HttpServletRequest
+     */
+    public HttpServletRequest getHttpServletRequest();
+    
+    /**
+     * Returns the HttpServletResponse.
+     * 
+     * @return HttpServletResponse
+     */
+    public HttpServletResponse getHttpServletResponse();
+    
+    /**
+     * Returns the event type.
+     * 
+     * @return EventType
+     */
+    public EventType getEventType();
+    
+    /**
+     * Returns the sub type of this event.
+     * 
+     * @return EventSubType
+     */
+    public EventSubType getEventSubType();
+    
+    /**
+     * Ends the Comet session. This signals to the container that 
+     * the container wants to end the comet session. This will send back to the
+     * client a notice that the server has no more data to send as part of this
+     * request. The servlet should perform any needed cleanup as if it had received
+     * an END or ERROR event. 
+     * 
+     * @throws IOException if an IO exception occurs
+     */
+    public void close() throws IOException;
+    
+    /**
+     * Sets the timeout for this Comet connection. Please NOTE, that the implementation 
+     * of a per connection timeout is OPTIONAL and MAY NOT be implemented.<br/>
+     * This method sets the timeout in milliseconds of idle time on the connection.
+     * The timeout is reset every time data is received from the connection or data is flushed
+     * using <code>response.flushBuffer()</code>. If a timeout occurs, the 
+     * <code>error(HttpServletRequest, HttpServletResponse)</code> method is invoked. The 
+     * web application SHOULD NOT attempt to reuse the request and response objects after a timeout
+     * as the <code>error(HttpServletRequest, HttpServletResponse)</code> method indicates.<br/>
+     * This method should not be called asynchronously, as that will have no effect.
+     * 
+     * @param timeout The timeout in milliseconds for this connection, must be a positive value, larger than 0
+     * @throws IOException An IOException may be thrown to indicate an IO error, 
+     *         or that the EOF has been reached on the connection
+     * @throws ServletException An exception has occurred, as specified by the root
+     *         cause
+     * @throws UnsupportedOperationException if per connection timeout is not supported, either at all or at this phase
+     *         of the invocation.
+     */
+    public void setTimeout(int timeout)
+        throws IOException, ServletException, UnsupportedOperationException;
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/comet/CometFilter.java b/bundles/org.apache.tomcat/src/org/apache/catalina/comet/CometFilter.java
new file mode 100644
index 0000000..2b69bc4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/comet/CometFilter.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.comet;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.ServletException;
+
+/**
+ * A Comet filter, similar to regular filters, performs filtering tasks on either 
+ * the request to a resource (a Comet servlet), or on the response from a resource, or both.
+ * <br><br>
+ * Filters perform filtering in the <code>doFilterEvent</code> method. Every Filter has access to 
+ * a FilterConfig object from which it can obtain its initialization parameters, a
+ * reference to the ServletContext which it can use, for example, to load resources
+ * needed for filtering tasks.
+ * <p>
+ * Filters are configured in the deployment descriptor of a web application
+ * <p>
+ * Examples that have been identified for this design are<br>
+ * 1) Authentication Filters <br>
+ * 2) Logging and Auditing Filters <br>
+ * 3) Image conversion Filters <br>
+ * 4) Data compression Filters <br>
+ * 5) Encryption Filters <br>
+ * 6) Tokenizing Filters <br>
+ * 7) Filters that trigger resource access events <br>
+ * 8) XSL/T filters <br>
+ * 9) Mime-type chain Filter <br>
+ * <br>
+ * 
+ * @author Remy Maucherat
+ * @author Filip Hanik
+ */
+public interface CometFilter extends Filter {
+
+    
+    /**
+     * The <code>doFilterEvent</code> method of the CometFilter is called by the container
+     * each time a request/response pair is passed through the chain due
+     * to a client event for a resource at the end of the chain. The CometFilterChain passed in to this
+     * method allows the Filter to pass on the event to the next entity in the
+     * chain.<p>
+     * A typical implementation of this method would follow the following pattern:- <br>
+     * 1. Examine the request<br>
+     * 2. Optionally wrap the request object contained in the event with a custom implementation to
+     * filter content or headers for input filtering and pass a CometEvent instance containing
+     * the wrapped request to the next filter<br>
+     * 3. Optionally wrap the response object contained in the event with a custom implementation to
+     * filter content or headers for output filtering and pass a CometEvent instance containing
+     * the wrapped request to the next filter<br>
+     * 4. a) <strong>Either</strong> invoke the next entity in the chain using the CometFilterChain object (<code>chain.doFilterEvent()</code>), <br>   
+     * 4. b) <strong>or</strong> not pass on the request/response pair to the next entity in the filter chain to block the event processing<br>
+     * 5. Directly set fields on the response after invocation of the next entity in the filter chain.
+     * 
+     * @param event the event that is being processed. Another event may be passed along the chain.
+     * @param chain 
+     * @throws IOException
+     * @throws ServletException
+     */
+    public void doFilterEvent(CometEvent event, CometFilterChain chain)
+        throws IOException, ServletException;
+    
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/comet/CometFilterChain.java b/bundles/org.apache.tomcat/src/org/apache/catalina/comet/CometFilterChain.java
new file mode 100644
index 0000000..b4ca50e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/comet/CometFilterChain.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.comet;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+
+/**
+ * A CometFilterChain is an object provided by the servlet container to the developer
+ * giving a view into the invocation chain of a filtered event for a resource. Filters
+ * use the CometFilterChain to invoke the next filter in the chain, or if the calling filter
+ * is the last filter in the chain, to invoke the resource at the end of the chain.
+ * 
+ * @author Remy Maucherat
+ * @author Filip Hanik
+ */
+public interface CometFilterChain {
+
+    
+    /**
+     * Causes the next filter in the chain to be invoked, or if the calling filter is the last filter
+     * in the chain, causes the resource at the end of the chain to be invoked.
+     *
+     * @param event the event to pass along the chain.
+     */
+    public void doFilterEvent(CometEvent event) throws IOException, ServletException;
+    
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/comet/CometProcessor.java b/bundles/org.apache.tomcat/src/org/apache/catalina/comet/CometProcessor.java
new file mode 100644
index 0000000..ad4a3dd
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/comet/CometProcessor.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.comet;
+
+import java.io.IOException;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+
+/**
+ * This interface should be implemented by servlets which would like to handle
+ * asynchronous IO, receiving events when data is available for reading, and
+ * being able to output data without the need for being invoked by the container.
+ * Note: When this interface is implemented, the service method of the servlet will
+ * never be called, and will be replaced with a begin event.
+ */
+public interface CometProcessor extends Servlet{
+
+    /**
+     * Process the given Comet event.
+     * 
+     * @param event The Comet event that will be processed
+     * @throws IOException
+     * @throws ServletException
+     */
+    public void event(CometEvent event)
+        throws IOException, ServletException;
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/connector/ClientAbortException.java b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/ClientAbortException.java
new file mode 100644
index 0000000..2e2e00f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/ClientAbortException.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.connector;
+
+import java.io.IOException;
+
+/**
+ * Wrap an IOException identifying it as being caused by an abort
+ * of a request by a remote client.
+ *
+ * @author Glenn L. Nielsen
+ * @version $Id: ClientAbortException.java,v 1.1 2011/06/28 21:08:19 rherrmann Exp $
+ */
+
+public final class ClientAbortException extends IOException {
+
+    private static final long serialVersionUID = 1L;
+
+    
+    //------------------------------------------------------------ Constructors
+
+    /**
+     * Construct a new ClientAbortException with no other information.
+     */
+    public ClientAbortException() {
+
+        this(null, null);
+
+    }
+
+
+    /**
+     * Construct a new ClientAbortException for the specified message.
+     *
+     * @param message Message describing this exception
+     */
+    public ClientAbortException(String message) {
+
+        this(message, null);
+
+    }
+
+
+    /**
+     * Construct a new ClientAbortException for the specified throwable.
+     *
+     * @param throwable Throwable that caused this exception
+     */
+    public ClientAbortException(Throwable throwable) {
+
+        this(null, throwable);
+
+    }
+
+
+    /**
+     * Construct a new ClientAbortException for the specified message
+     * and throwable.
+     *
+     * @param message Message describing this exception
+     * @param throwable Throwable that caused this exception
+     */
+    public ClientAbortException(String message, Throwable throwable) {
+
+        super();
+        this.message = message;
+        this.throwable = throwable;
+
+    }
+
+
+    //------------------------------------------------------ Instance Variables
+
+
+    /**
+     * The error message passed to our constructor (if any)
+     */
+    protected String message = null;
+
+
+    /**
+     * The underlying exception or error passed to our constructor (if any)
+     */
+    protected Throwable throwable = null;
+
+
+    //---------------------------------------------------------- Public Methods
+
+
+    /**
+     * Returns the message associated with this exception, if any.
+     */
+    @Override
+    public String getMessage() {
+
+        return (message);
+
+    }
+
+
+    /**
+     * Returns the cause that caused this exception, if any.
+     */
+    @Override
+    public Throwable getCause() {
+        
+        return (throwable);
+        
+    }
+
+    
+    /**
+     * Return a formatted string that describes this exception.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("ClientAbortException:  ");
+        if (message != null) {
+            sb.append(message);
+            if (throwable != null) {
+                sb.append(":  ");
+            }
+        }
+        if (throwable != null) {
+            sb.append(throwable.toString());
+        }
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/connector/CometEventImpl.java b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/CometEventImpl.java
new file mode 100644
index 0000000..34e9f20
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/CometEventImpl.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.connector;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.comet.CometEvent;
+import org.apache.tomcat.util.res.StringManager;
+
+public class CometEventImpl implements CometEvent {
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    public CometEventImpl(Request request, Response response) {
+        this.request = request;
+        this.response = response;
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+    
+    /**
+     * Associated request.
+     */
+    protected Request request = null;
+
+
+    /**
+     * Associated response.
+     */
+    protected Response response = null;
+
+    
+    /**
+     * Event type.
+     */
+    protected EventType eventType = EventType.BEGIN;
+    
+
+    /**
+     * Event sub type.
+     */
+    protected EventSubType eventSubType = null;
+    
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Clear the event.
+     */
+    public void clear() {
+        request = null;
+        response = null;
+    }
+
+    public void setEventType(EventType eventType) {
+        this.eventType = eventType;
+    }
+    
+    public void setEventSubType(EventSubType eventSubType) {
+        this.eventSubType = eventSubType;
+    }
+    
+    @Override
+    public void close() throws IOException {
+        if (request == null) {
+            throw new IllegalStateException(sm.getString("cometEvent.nullRequest"));
+        }
+        boolean iscomet = request.isComet();
+        request.setComet(false);
+        request.finishRequest();
+        response.finishResponse();
+        if (iscomet) request.cometClose();
+    }
+
+    @Override
+    public EventSubType getEventSubType() {
+        return eventSubType;
+    }
+
+    @Override
+    public EventType getEventType() {
+        return eventType;
+    }
+
+    @Override
+    public HttpServletRequest getHttpServletRequest() {
+        return request.getRequest();
+    }
+
+    @Override
+    public HttpServletResponse getHttpServletResponse() {
+        return response.getResponse();
+    }
+
+    @Override
+    public void setTimeout(int timeout) throws IOException, ServletException,
+            UnsupportedOperationException {
+        if (request.getAttribute("org.apache.tomcat.comet.timeout.support") == Boolean.TRUE) {
+            request.setAttribute("org.apache.tomcat.comet.timeout", Integer.valueOf(timeout));
+            if (request.isComet()) request.setCometTimeout(timeout);
+        } else {
+            throw new UnsupportedOperationException();
+        }
+    }
+    
+    @Override
+    public String toString() {
+        StringBuilder buf = new StringBuilder();
+        buf.append(super.toString());
+        buf.append("[EventType:");
+        buf.append(eventType);
+        buf.append(", EventSubType:");
+        buf.append(eventSubType);
+        buf.append("]");
+        return buf.toString();
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/connector/Connector.java b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/Connector.java
new file mode 100644
index 0000000..210f6ab
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/Connector.java
@@ -0,0 +1,1018 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.connector;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+
+import javax.management.ObjectName;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.Service;
+import org.apache.catalina.core.AprLifecycleListener;
+import org.apache.catalina.mbeans.MBeanUtils;
+import org.apache.catalina.util.LifecycleMBeanBase;
+import org.apache.coyote.Adapter;
+import org.apache.coyote.ProtocolHandler;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.IntrospectionUtils;
+import org.apache.tomcat.util.http.mapper.Mapper;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Implementation of a Coyote connector.
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @version $Id: Connector.java,v 1.1 2011/06/28 21:08:19 rherrmann Exp $
+ */
+
+
+public class Connector extends LifecycleMBeanBase  {
+
+    private static final Log log = LogFactory.getLog(Connector.class);
+
+
+    /**
+     * Alternate flag to enable recycling of facades.
+     */
+    public static final boolean RECYCLE_FACADES =
+        Boolean.valueOf(System.getProperty("org.apache.catalina.connector.RECYCLE_FACADES", "false")).booleanValue();
+
+
+    // ------------------------------------------------------------ Constructor
+
+
+    public Connector() {
+        this(null);
+    }
+
+    public Connector(String protocol) {
+        setProtocol(protocol);
+        // Instantiate protocol handler
+        try {
+            Class<?> clazz = Class.forName(protocolHandlerClassName);
+            this.protocolHandler = (ProtocolHandler) clazz.newInstance();
+        } catch (Exception e) {
+            log.error
+                (sm.getString
+                 ("coyoteConnector.protocolHandlerInstantiationFailed", e));
+        }
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The <code>Service</code> we are associated with (if any).
+     */
+    protected Service service = null;
+
+
+    /**
+     * Do we allow TRACE ?
+     */
+    protected boolean allowTrace = false;
+
+
+    /**
+     * Default timeout for asynchronous requests (ms).
+     */
+    protected  long asyncTimeout = 10000;
+
+
+    /**
+     * The "enable DNS lookups" flag for this Connector.
+     */
+    protected boolean enableLookups = false;
+
+
+    /*
+     * Is generation of X-Powered-By response header enabled/disabled?
+     */
+    protected boolean xpoweredBy = false;
+
+
+    /**
+     * Descriptive information about this Connector implementation.
+     */
+    protected static final String info =
+        "org.apache.catalina.connector.Connector/2.1";
+
+
+    /**
+     * The port number on which we listen for requests.
+     */
+    protected int port = 0;
+
+
+    /**
+     * The server name to which we should pretend requests to this Connector
+     * were directed.  This is useful when operating Tomcat behind a proxy
+     * server, so that redirects get constructed accurately.  If not specified,
+     * the server name included in the <code>Host</code> header is used.
+     */
+    protected String proxyName = null;
+
+
+    /**
+     * The server port to which we should pretend requests to this Connector
+     * were directed.  This is useful when operating Tomcat behind a proxy
+     * server, so that redirects get constructed accurately.  If not specified,
+     * the port number specified by the <code>port</code> property is used.
+     */
+    protected int proxyPort = 0;
+
+
+    /**
+     * The redirect port for non-SSL to SSL redirects.
+     */
+    protected int redirectPort = 443;
+
+
+    /**
+     * The request scheme that will be set on all requests received
+     * through this connector.
+     */
+    protected String scheme = "http";
+
+
+    /**
+     * The secure connection flag that will be set on all requests received
+     * through this connector.
+     */
+    protected boolean secure = false;
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
+     * Maximum size of a POST which will be automatically parsed by the
+     * container. 2MB by default.
+     */
+    protected int maxPostSize = 2 * 1024 * 1024;
+
+
+    /**
+     * Maximum size of a POST which will be saved by the container
+     * during authentication. 4kB by default
+     */
+    protected int maxSavePostSize = 4 * 1024;
+
+    /**
+     * Comma-separated list of HTTP methods that will be parsed according
+     * to POST-style rules for application/x-www-form-urlencoded request bodies.
+     */
+    protected String parseBodyMethods = "POST";
+
+    /**
+     * A Set of methods determined by {@link #parseBodyMethods}.
+     */
+    protected HashSet<String> parseBodyMethodsSet;
+
+
+    /**
+     * Flag to use IP-based virtual hosting.
+     */
+    protected boolean useIPVHosts = false;
+
+
+    /**
+     * Coyote Protocol handler class name.
+     * Defaults to the Coyote HTTP/1.1 protocolHandler.
+     */
+    protected String protocolHandlerClassName =
+        "org.apache.coyote.http11.Http11Protocol";
+
+
+    /**
+     * Coyote protocol handler.
+     */
+    protected ProtocolHandler protocolHandler = null;
+
+
+    /**
+     * Coyote adapter.
+     */
+    protected Adapter adapter = null;
+
+
+     /**
+      * Mapper.
+      */
+     protected Mapper mapper = new Mapper();
+
+
+     /**
+      * Mapper listener.
+      */
+     protected MapperListener mapperListener = new MapperListener(mapper, this);
+
+
+     /**
+      * URI encoding.
+      */
+     protected String URIEncoding = null;
+
+
+     /**
+      * URI encoding as body.
+      */
+     protected boolean useBodyEncodingForURI = false;
+
+
+     protected static HashMap<String,String> replacements =
+         new HashMap<String,String>();
+     static {
+         replacements.put("acceptCount", "backlog");
+         replacements.put("connectionLinger", "soLinger");
+         replacements.put("connectionTimeout", "soTimeout");
+         replacements.put("randomFile", "randomfile");
+         replacements.put("rootFile", "rootfile");
+     }
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return a configured property.
+     */
+    public Object getProperty(String name) {
+        String repl = name;
+        if (replacements.get(name) != null) {
+            repl = replacements.get(name);
+        }
+        return IntrospectionUtils.getProperty(protocolHandler, repl);
+    }
+
+
+    /**
+     * Set a configured property.
+     */
+    public boolean setProperty(String name, String value) {
+        String repl = name;
+        if (replacements.get(name) != null) {
+            repl = replacements.get(name);
+        }
+        return IntrospectionUtils.setProperty(protocolHandler, repl, value);
+    }
+
+    /**
+     * Return a configured property.
+     */
+    public Object getAttribute(String name) {
+        return getProperty(name);
+    }
+
+
+    /**
+     * Set a configured property.
+     */
+    public void setAttribute(String name, Object value) {
+        setProperty(name, String.valueOf(value));
+    }
+
+
+    /**
+     * Return the <code>Service</code> with which we are associated (if any).
+     */
+    public Service getService() {
+
+        return (this.service);
+
+    }
+
+
+    /**
+     * Set the <code>Service</code> with which we are associated (if any).
+     *
+     * @param service The service that owns this Engine
+     */
+    public void setService(Service service) {
+
+        this.service = service;
+
+    }
+
+
+    /**
+     * True if the TRACE method is allowed.  Default value is "false".
+     */
+    public boolean getAllowTrace() {
+
+        return (this.allowTrace);
+
+    }
+
+
+    /**
+     * Set the allowTrace flag, to disable or enable the TRACE HTTP method.
+     *
+     * @param allowTrace The new allowTrace flag
+     */
+    public void setAllowTrace(boolean allowTrace) {
+
+        this.allowTrace = allowTrace;
+        setProperty("allowTrace", String.valueOf(allowTrace));
+
+    }
+
+    
+    /**
+     * Return the default timeout for async requests in ms.
+     */
+    public long getAsyncTimeout() {
+
+        return asyncTimeout;
+
+    }
+
+
+    /**
+     * Set the default timeout for async requests.
+     *
+     * @param asyncTimeout The new timeout in ms.
+     */
+    public void setAsyncTimeout(long asyncTimeout) {
+
+        this.asyncTimeout= asyncTimeout;
+        setProperty("asyncTimeout", String.valueOf(asyncTimeout));
+
+    }
+
+    
+    /**
+     * Return the "enable DNS lookups" flag.
+     */
+    public boolean getEnableLookups() {
+
+        return (this.enableLookups);
+
+    }
+
+
+    /**
+     * Set the "enable DNS lookups" flag.
+     *
+     * @param enableLookups The new "enable DNS lookups" flag value
+     */
+    public void setEnableLookups(boolean enableLookups) {
+
+        this.enableLookups = enableLookups;
+        setProperty("enableLookups", String.valueOf(enableLookups));
+
+    }
+
+
+    /**
+     * Return descriptive information about this Connector implementation.
+     */
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+     /**
+      * Return the mapper.
+      */
+     public Mapper getMapper() {
+
+         return (mapper);
+
+     }
+
+
+    /**
+     * Return the maximum size of a POST which will be automatically
+     * parsed by the container.
+     */
+    public int getMaxPostSize() {
+
+        return (maxPostSize);
+
+    }
+
+
+    /**
+     * Set the maximum size of a POST which will be automatically
+     * parsed by the container.
+     *
+     * @param maxPostSize The new maximum size in bytes of a POST which will
+     * be automatically parsed by the container
+     */
+    public void setMaxPostSize(int maxPostSize) {
+
+        this.maxPostSize = maxPostSize;
+    }
+
+
+    /**
+     * Return the maximum size of a POST which will be saved by the container
+     * during authentication.
+     */
+    public int getMaxSavePostSize() {
+
+        return (maxSavePostSize);
+
+    }
+
+
+    /**
+     * Set the maximum size of a POST which will be saved by the container
+     * during authentication.
+     *
+     * @param maxSavePostSize The new maximum size in bytes of a POST which will
+     * be saved by the container during authentication.
+     */
+    public void setMaxSavePostSize(int maxSavePostSize) {
+
+        this.maxSavePostSize = maxSavePostSize;
+        setProperty("maxSavePostSize", String.valueOf(maxSavePostSize));
+    }
+
+
+    public String getParseBodyMethods() {
+
+        return this.parseBodyMethods;
+
+    }
+
+    public void setParseBodyMethods(String methods) {
+
+        HashSet<String> methodSet = new HashSet<String>();
+
+        if( null != methods )
+            methodSet.addAll(Arrays.asList(methods.split("\\s*,\\s*")));
+
+        if( methodSet.contains("TRACE") )
+            throw new IllegalArgumentException(sm.getString("coyoteConnector.parseBodyMethodNoTrace"));
+
+        this.parseBodyMethods = methods;
+        this.parseBodyMethodsSet = methodSet;
+
+    }
+
+    protected boolean isParseBodyMethod(String method) {
+
+        return parseBodyMethodsSet.contains(method);
+
+    }
+
+    /**
+     * Return the port number on which we listen for requests.
+     */
+    public int getPort() {
+
+        return (this.port);
+
+    }
+
+
+    /**
+     * Set the port number on which we listen for requests.
+     *
+     * @param port The new port number
+     */
+    public void setPort(int port) {
+
+        this.port = port;
+        setProperty("port", String.valueOf(port));
+
+    }
+
+
+    /**
+     * Return the Coyote protocol handler in use.
+     */
+    public String getProtocol() {
+
+        if ("org.apache.coyote.http11.Http11Protocol".equals
+            (getProtocolHandlerClassName())
+            || "org.apache.coyote.http11.Http11AprProtocol".equals
+            (getProtocolHandlerClassName())) {
+            return "HTTP/1.1";
+        } else if ("org.apache.coyote.ajp.AjpProtocol".equals
+                   (getProtocolHandlerClassName())
+                   || "org.apache.coyote.ajp.AjpAprProtocol".equals
+                   (getProtocolHandlerClassName())) {
+            return "AJP/1.3";
+        }
+        return getProtocolHandlerClassName();
+
+    }
+    
+
+    /**
+     * Set the Coyote protocol which will be used by the connector.
+     *
+     * @param protocol The Coyote protocol name
+     */
+    public void setProtocol(String protocol) {
+
+        if (AprLifecycleListener.isAprAvailable()) {
+            if ("HTTP/1.1".equals(protocol)) {
+                setProtocolHandlerClassName
+                    ("org.apache.coyote.http11.Http11AprProtocol");
+            } else if ("AJP/1.3".equals(protocol)) {
+                setProtocolHandlerClassName
+                    ("org.apache.coyote.ajp.AjpAprProtocol");
+            } else if (protocol != null) {
+                setProtocolHandlerClassName(protocol);
+            } else {
+                setProtocolHandlerClassName
+                    ("org.apache.coyote.http11.Http11AprProtocol");
+            }
+        } else {
+            if ("HTTP/1.1".equals(protocol)) {
+                setProtocolHandlerClassName
+                    ("org.apache.coyote.http11.Http11Protocol");
+            } else if ("AJP/1.3".equals(protocol)) {
+                setProtocolHandlerClassName
+                    ("org.apache.coyote.ajp.AjpProtocol");
+            } else if (protocol != null) {
+                setProtocolHandlerClassName(protocol);
+            }
+        }
+
+    }
+
+
+    /**
+     * Return the class name of the Coyote protocol handler in use.
+     */
+    public String getProtocolHandlerClassName() {
+
+        return (this.protocolHandlerClassName);
+
+    }
+
+
+    /**
+     * Set the class name of the Coyote protocol handler which will be used
+     * by the connector.
+     *
+     * @param protocolHandlerClassName The new class name
+     */
+    public void setProtocolHandlerClassName(String protocolHandlerClassName) {
+
+        this.protocolHandlerClassName = protocolHandlerClassName;
+
+    }
+
+
+    /**
+     * Return the protocol handler associated with the connector.
+     */
+    public ProtocolHandler getProtocolHandler() {
+
+        return (this.protocolHandler);
+
+    }
+
+
+    /**
+     * Return the proxy server name for this Connector.
+     */
+    public String getProxyName() {
+
+        return (this.proxyName);
+
+    }
+
+
+    /**
+     * Set the proxy server name for this Connector.
+     *
+     * @param proxyName The new proxy server name
+     */
+    public void setProxyName(String proxyName) {
+
+        if(proxyName != null && proxyName.length() > 0) {
+            this.proxyName = proxyName;
+            setProperty("proxyName", proxyName);
+        } else {
+            this.proxyName = null;
+        }
+
+    }
+
+
+    /**
+     * Return the proxy server port for this Connector.
+     */
+    public int getProxyPort() {
+
+        return (this.proxyPort);
+
+    }
+
+
+    /**
+     * Set the proxy server port for this Connector.
+     *
+     * @param proxyPort The new proxy server port
+     */
+    public void setProxyPort(int proxyPort) {
+
+        this.proxyPort = proxyPort;
+        setProperty("proxyPort", String.valueOf(proxyPort));
+
+    }
+
+
+    /**
+     * Return the port number to which a request should be redirected if
+     * it comes in on a non-SSL port and is subject to a security constraint
+     * with a transport guarantee that requires SSL.
+     */
+    public int getRedirectPort() {
+
+        return (this.redirectPort);
+
+    }
+
+
+    /**
+     * Set the redirect port number.
+     *
+     * @param redirectPort The redirect port number (non-SSL to SSL)
+     */
+    public void setRedirectPort(int redirectPort) {
+
+        this.redirectPort = redirectPort;
+        setProperty("redirectPort", String.valueOf(redirectPort));
+
+    }
+
+
+    /**
+     * Return the scheme that will be assigned to requests received
+     * through this connector.  Default value is "http".
+     */
+    public String getScheme() {
+
+        return (this.scheme);
+
+    }
+
+
+    /**
+     * Set the scheme that will be assigned to requests received through
+     * this connector.
+     *
+     * @param scheme The new scheme
+     */
+    public void setScheme(String scheme) {
+
+        this.scheme = scheme;
+
+    }
+
+
+    /**
+     * Return the secure connection flag that will be assigned to requests
+     * received through this connector.  Default value is "false".
+     */
+    public boolean getSecure() {
+
+        return (this.secure);
+
+    }
+
+
+    /**
+     * Set the secure connection flag that will be assigned to requests
+     * received through this connector.
+     *
+     * @param secure The new secure connection flag
+     */
+    public void setSecure(boolean secure) {
+
+        this.secure = secure;
+        setProperty("secure", Boolean.toString(secure));
+    }
+
+     /**
+      * Return the character encoding to be used for the URI.
+      */
+     public String getURIEncoding() {
+
+         return (this.URIEncoding);
+
+     }
+
+
+     /**
+      * Set the URI encoding to be used for the URI.
+      *
+      * @param URIEncoding The new URI character encoding.
+      */
+     public void setURIEncoding(String URIEncoding) {
+
+         this.URIEncoding = URIEncoding;
+         setProperty("uRIEncoding", URIEncoding);
+
+     }
+
+
+     /**
+      * Return the true if the entity body encoding should be used for the URI.
+      */
+     public boolean getUseBodyEncodingForURI() {
+
+         return (this.useBodyEncodingForURI);
+
+     }
+
+
+     /**
+      * Set if the entity body encoding should be used for the URI.
+      *
+      * @param useBodyEncodingForURI The new value for the flag.
+      */
+     public void setUseBodyEncodingForURI(boolean useBodyEncodingForURI) {
+
+         this.useBodyEncodingForURI = useBodyEncodingForURI;
+         setProperty
+             ("useBodyEncodingForURI", String.valueOf(useBodyEncodingForURI));
+
+     }
+
+    /**
+     * Indicates whether the generation of an X-Powered-By response header for
+     * servlet-generated responses is enabled or disabled for this Connector.
+     *
+     * @return true if generation of X-Powered-By response header is enabled,
+     * false otherwise
+     */
+    public boolean getXpoweredBy() {
+        return xpoweredBy;
+    }
+
+
+    /**
+     * Enables or disables the generation of an X-Powered-By header (with value
+     * Servlet/2.5) for all servlet-generated responses returned by this
+     * Connector.
+     *
+     * @param xpoweredBy true if generation of X-Powered-By response header is
+     * to be enabled, false otherwise
+     */
+    public void setXpoweredBy(boolean xpoweredBy) {
+        this.xpoweredBy = xpoweredBy;
+        setProperty("xpoweredBy", String.valueOf(xpoweredBy));
+    }
+
+    /**
+     * Enable the use of IP-based virtual hosting.
+     *
+     * @param useIPVHosts <code>true</code> if Hosts are identified by IP,
+     *                    <code>false/code> if Hosts are identified by name.
+     */
+    public void setUseIPVHosts(boolean useIPVHosts) {
+        this.useIPVHosts = useIPVHosts;
+        setProperty("useIPVHosts", String.valueOf(useIPVHosts));
+    }
+
+    /**
+     * Test if IP-based virtual hosting is enabled.
+     */
+    public boolean getUseIPVHosts() {
+        return useIPVHosts;
+    }
+
+
+    public String getExecutorName() {
+        Object obj = protocolHandler.getExecutor();
+        if (obj instanceof org.apache.catalina.Executor) {
+            return ((org.apache.catalina.Executor) obj).getName();
+        }
+        return "Internal";
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Create (or allocate) and return a Request object suitable for
+     * specifying the contents of a Request to the responsible Container.
+     */
+    public Request createRequest() {
+
+        Request request = new Request();
+        request.setConnector(this);
+        return (request);
+
+    }
+
+
+    /**
+     * Create (or allocate) and return a Response object suitable for
+     * receiving the contents of a Response from the responsible Container.
+     */
+    public Response createResponse() {
+
+        Response response = new Response();
+        response.setConnector(this);
+        return (response);
+
+    }
+
+
+    protected String createObjectNameKeyProperties(String type) {
+        
+        Object addressObj = getProperty("address");
+
+        StringBuilder sb = new StringBuilder("type=");
+        sb.append(type);
+        sb.append(",port=");
+        sb.append(getPort());
+        if (addressObj != null) {
+            String address = addressObj.toString();
+            if (address.length() > 0) {
+                sb.append(",address=");
+                sb.append(ObjectName.quote(address));
+            }
+        }
+        return sb.toString();
+    }
+
+
+    /**
+     * Pause the connector.
+     */
+    public void pause() {
+        try {
+            protocolHandler.pause();
+        } catch (Exception e) {
+            log.error(sm.getString
+                      ("coyoteConnector.protocolHandlerPauseFailed"), e);
+        }
+    }
+
+
+    /**
+     * Pause the connector.
+     */
+    public void resume() {
+        try {
+            protocolHandler.resume();
+        } catch (Exception e) {
+            log.error(sm.getString
+                      ("coyoteConnector.protocolHandlerResumeFailed"), e);
+        }
+    }
+
+
+    @Override
+    protected void initInternal() throws LifecycleException {
+
+        super.initInternal();
+        
+        // Initialize adapter
+        adapter = new CoyoteAdapter(this);
+        protocolHandler.setAdapter(adapter);
+
+        // Make sure parseBodyMethodsSet has a default
+        if( null == parseBodyMethodsSet )
+            setParseBodyMethods(getParseBodyMethods());
+
+        try {
+            protocolHandler.init();
+        } catch (Exception e) {
+            throw new LifecycleException
+                (sm.getString
+                 ("coyoteConnector.protocolHandlerInitializationFailed"), e);
+        }
+
+        // Initialize mapper listener
+        mapperListener.init();
+    }
+
+
+    /**
+     * Begin processing requests via this Connector.
+     *
+     * @exception LifecycleException if a fatal startup error occurs
+     */
+    @Override
+    protected void startInternal() throws LifecycleException {
+
+        setState(LifecycleState.STARTING);
+
+        try {
+            protocolHandler.start();
+        } catch (Exception e) {
+            String errPrefix = "";
+            if(this.service != null) {
+                errPrefix += "service.getName(): \"" + this.service.getName() + "\"; ";
+            }
+
+            throw new LifecycleException
+                (errPrefix + " " + sm.getString
+                 ("coyoteConnector.protocolHandlerStartFailed"), e);
+        }
+
+        mapperListener.start();
+    }
+
+
+    /**
+     * Terminate processing requests via this Connector.
+     *
+     * @exception LifecycleException if a fatal shutdown error occurs
+     */
+    @Override
+    protected void stopInternal() throws LifecycleException {
+
+        setState(LifecycleState.STOPPING);
+
+        try {
+            protocolHandler.stop();
+        } catch (Exception e) {
+            throw new LifecycleException
+                (sm.getString
+                 ("coyoteConnector.protocolHandlerStopFailed"), e);
+        }
+
+        mapperListener.stop();
+    }
+
+
+    @Override
+    protected void destroyInternal() throws LifecycleException {
+        mapperListener.destroy();
+        
+        try {
+            protocolHandler.destroy();
+        } catch (Exception e) {
+            throw new LifecycleException
+                (sm.getString
+                 ("coyoteConnector.protocolHandlerDestroyFailed"), e);
+        }
+
+        if (getService() != null) {
+            getService().removeConnector(this);
+        }
+        
+        super.destroyInternal();
+    }
+
+
+    /**
+     * Provide a useful toString() implementation as it may be used when logging
+     * Lifecycle errors to identify the component.
+     */
+    @Override
+    public String toString() {
+        // Not worth caching this right now
+        StringBuilder sb = new StringBuilder("Connector[");
+        sb.append(getProtocol());
+        sb.append('-');
+        sb.append(getPort());
+        sb.append(']');
+        return sb.toString();
+    }
+
+    // -------------------- JMX registration  --------------------
+
+    @Override
+    protected String getDomainInternal() {
+        return MBeanUtils.getDomain(getService());
+    }
+
+    @Override
+    protected String getObjectNameKeyProperties() {
+        return createObjectNameKeyProperties("Connector");
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/connector/Constants.java b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/Constants.java
new file mode 100644
index 0000000..34fd656
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/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.connector;
+
+/**
+ * Static constants for this package.
+ */
+public final class Constants {
+
+    public static final String Package = "org.apache.catalina.connector";
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/connector/CoyoteAdapter.java b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/CoyoteAdapter.java
new file mode 100644
index 0000000..4be669e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/CoyoteAdapter.java
@@ -0,0 +1,1192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.connector;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.EnumSet;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.SessionTrackingMode;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.comet.CometEvent;
+import org.apache.catalina.comet.CometEvent.EventType;
+import org.apache.catalina.core.ApplicationSessionCookieConfig;
+import org.apache.catalina.core.AsyncContextImpl;
+import org.apache.catalina.util.ServerInfo;
+import org.apache.catalina.util.URLEncoder;
+import org.apache.coyote.ActionCode;
+import org.apache.coyote.Adapter;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.buf.B2CConverter;
+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.Cookies;
+import org.apache.tomcat.util.http.ServerCookie;
+import org.apache.tomcat.util.net.SSLSupport;
+import org.apache.tomcat.util.net.SocketStatus;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Implementation of a request processor which delegates the processing to a
+ * Coyote processor.
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @version $Id: CoyoteAdapter.java,v 1.1 2011/06/28 21:08:19 rherrmann Exp $
+ */
+public class CoyoteAdapter implements Adapter {
+    
+    private static final Log log = LogFactory.getLog(CoyoteAdapter.class);
+
+    // -------------------------------------------------------------- Constants
+
+    private static final String POWERED_BY = "Servlet/3.0 JSP/2.2 " +
+            "(" + ServerInfo.getServerInfo() + " Java/" +
+            System.getProperty("java.vm.vendor") + "/" +
+            System.getProperty("java.runtime.version") + ")";
+
+    private static final EnumSet<SessionTrackingMode> SSL_ONLY =
+        EnumSet.of(SessionTrackingMode.SSL);
+
+    public static final int ADAPTER_NOTES = 1;
+
+
+    protected static final boolean ALLOW_BACKSLASH = 
+        Boolean.valueOf(System.getProperty("org.apache.catalina.connector.CoyoteAdapter.ALLOW_BACKSLASH", "false")).booleanValue();
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new CoyoteProcessor associated with the specified connector.
+     *
+     * @param connector CoyoteConnector that owns this processor
+     */
+    public CoyoteAdapter(Connector connector) {
+
+        super();
+        this.connector = connector;
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The CoyoteConnector with which this processor is associated.
+     */
+    private Connector connector = null;
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
+     * Encoder for the Location URL in HTTP redirects.
+     */
+    protected static URLEncoder urlEncoder;
+
+
+    // ----------------------------------------------------- Static Initializer
+
+
+    /**
+     * The safe character set.
+     */
+    static {
+        urlEncoder = new URLEncoder();
+        urlEncoder.addSafeCharacter('-');
+        urlEncoder.addSafeCharacter('_');
+        urlEncoder.addSafeCharacter('.');
+        urlEncoder.addSafeCharacter('*');
+        urlEncoder.addSafeCharacter('/');
+    }
+
+
+    // -------------------------------------------------------- Adapter Methods
+
+    
+    /**
+     * Event method.
+     * 
+     * @return false to indicate an error, expected or not
+     */
+    @Override
+    public boolean event(org.apache.coyote.Request req, 
+            org.apache.coyote.Response res, SocketStatus status) {
+
+        Request request = (Request) req.getNote(ADAPTER_NOTES);
+        Response response = (Response) res.getNote(ADAPTER_NOTES);
+
+        if (request.getWrapper() == null) {
+            return false;
+        }
+            
+        boolean error = false;
+        boolean read = false;
+        try {
+            if (status == SocketStatus.OPEN) {
+                if (response.isClosed()) {
+                    // The event has been closed asynchronously, so call end instead of
+                    // read to cleanup the pipeline
+                    request.getEvent().setEventType(CometEvent.EventType.END);
+                    request.getEvent().setEventSubType(null);
+                } else {
+                    try {
+                        // Fill the read buffer of the servlet layer
+                        if (request.read()) {
+                            read = true;
+                        }
+                    } catch (IOException e) {
+                        error = true;
+                    }
+                    if (read) {
+                        request.getEvent().setEventType(CometEvent.EventType.READ);
+                        request.getEvent().setEventSubType(null);
+                    } else if (error) {
+                        request.getEvent().setEventType(CometEvent.EventType.ERROR);
+                        request.getEvent().setEventSubType(CometEvent.EventSubType.CLIENT_DISCONNECT);
+                    } else {
+                        request.getEvent().setEventType(CometEvent.EventType.END);
+                        request.getEvent().setEventSubType(null);
+                    }
+                }
+            } else if (status == SocketStatus.DISCONNECT) {
+                request.getEvent().setEventType(CometEvent.EventType.ERROR);
+                request.getEvent().setEventSubType(CometEvent.EventSubType.CLIENT_DISCONNECT);
+                error = true;
+            } else if (status == SocketStatus.ERROR) {
+                request.getEvent().setEventType(CometEvent.EventType.ERROR);
+                request.getEvent().setEventSubType(CometEvent.EventSubType.IOEXCEPTION);
+                error = true;
+            } else if (status == SocketStatus.STOP) {
+                request.getEvent().setEventType(CometEvent.EventType.END);
+                request.getEvent().setEventSubType(CometEvent.EventSubType.SERVER_SHUTDOWN);
+            } else if (status == SocketStatus.TIMEOUT) {
+                if (response.isClosed()) {
+                    // The event has been closed asynchronously, so call end instead of
+                    // read to cleanup the pipeline
+                    request.getEvent().setEventType(CometEvent.EventType.END);
+                    request.getEvent().setEventSubType(null);
+                } else {
+                    request.getEvent().setEventType(CometEvent.EventType.ERROR);
+                    request.getEvent().setEventSubType(CometEvent.EventSubType.TIMEOUT);
+                }
+            }
+
+            req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
+            
+            // Calling the container
+            connector.getService().getContainer().getPipeline().getFirst().event(request, response, request.getEvent());
+
+            if (!error && !response.isClosed() && (request.getAttribute(
+                    RequestDispatcher.ERROR_EXCEPTION) != null)) {
+                // An unexpected exception occurred while processing the event, so
+                // error should be called
+                request.getEvent().setEventType(CometEvent.EventType.ERROR);
+                request.getEvent().setEventSubType(null);
+                error = true;
+                connector.getService().getContainer().getPipeline().getFirst().event(request, response, request.getEvent());
+            }
+            if (response.isClosed() || !request.isComet()) {
+                if (status==SocketStatus.OPEN &&
+                        request.getEvent().getEventType() != EventType.END) {
+                    //CometEvent.close was called during an event other than END
+                    request.getEvent().setEventType(CometEvent.EventType.END);
+                    request.getEvent().setEventSubType(null);
+                    error = true;
+                    connector.getService().getContainer().getPipeline().getFirst().event(request, response, request.getEvent());
+                }
+                res.action(ActionCode.COMET_END, null);
+            } else if (!error && read && request.getAvailable()) {
+                // If this was a read and not all bytes have been read, or if no data
+                // was read from the connector, then it is an error
+                request.getEvent().setEventType(CometEvent.EventType.ERROR);
+                request.getEvent().setEventSubType(CometEvent.EventSubType.IOEXCEPTION);
+                error = true;
+                connector.getService().getContainer().getPipeline().getFirst().event(request, response, request.getEvent());
+            }
+            return (!error);
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            if (!(t instanceof IOException)) {
+                log.error(sm.getString("coyoteAdapter.service"), t);
+            }
+            error = true;
+            return false;
+        } finally {
+            req.getRequestProcessor().setWorkerThreadName(null);
+            // Recycle the wrapper request and response
+            if (error || response.isClosed() || !request.isComet()) {
+                request.recycle();
+                request.setFilterChain(null);
+                response.recycle();
+            }
+        }
+    }
+    
+    @Override
+    public boolean asyncDispatch(org.apache.coyote.Request req,
+            org.apache.coyote.Response res, SocketStatus status) throws Exception {
+        Request request = (Request) req.getNote(ADAPTER_NOTES);
+        Response response = (Response) res.getNote(ADAPTER_NOTES);
+
+        if (request == null) {
+            throw new IllegalStateException(
+                    "Dispatch may only happen on an existing request.");
+        }
+        boolean comet = false;
+        boolean success = true;
+        AsyncContextImpl asyncConImpl = (AsyncContextImpl)request.getAsyncContext();
+        try {
+            if (!request.isAsync() && !comet) {
+                // Error or timeout - need to tell listeners the request is over
+                // Have to test this first since state may change while in this
+                // method and this is only required if entering this methos in
+                // this state 
+                Context ctxt = (Context) request.getMappingData().context;
+                if (ctxt != null) {
+                    ctxt.fireRequestDestroyEvent(request);
+                }
+            }
+
+            if (status==SocketStatus.TIMEOUT) {
+                success = true;
+                if (!asyncConImpl.timeout()) {
+                    asyncConImpl.setErrorState(null);
+                }
+            }
+            if (request.isAsyncDispatching()) {
+                success = true;
+                connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
+                Throwable t = (Throwable) request.getAttribute(
+                        RequestDispatcher.ERROR_EXCEPTION);
+                if (t != null) {
+                    asyncConImpl.setErrorState(t);
+                }
+            }
+
+            if (request.isComet()) {
+                if (!response.isClosed() && !response.isError()) {
+                    if (request.getAvailable() || (request.getContentLength() > 0 && (!request.isParametersParsed()))) {
+                        // Invoke a read event right away if there are available bytes
+                        if (event(req, res, SocketStatus.OPEN)) {
+                            comet = true;
+                            res.action(ActionCode.COMET_BEGIN, null);
+                        }
+                    } else {
+                        comet = true;
+                        res.action(ActionCode.COMET_BEGIN, null);
+                    }
+                } else {
+                    // Clear the filter chain, as otherwise it will not be reset elsewhere
+                    // since this is a Comet request
+                    request.setFilterChain(null);
+                }
+            }
+            if (!request.isAsync() && !comet) {
+                request.finishRequest();
+                response.finishResponse();
+                req.action(ActionCode.POST_REQUEST , null);
+            }
+
+        } catch (IOException e) {
+            success = false;
+            // Ignore
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            success = false;
+            log.error(sm.getString("coyoteAdapter.service"), t);
+        } finally {
+            req.getRequestProcessor().setWorkerThreadName(null);
+            // Recycle the wrapper request and response
+            if (!success || (!comet && !request.isAsync())) {
+                request.recycle();
+                response.recycle();
+            } else {
+                // Clear converters so that the minimum amount of memory 
+                // is used by this processor
+                request.clearEncoders();
+                response.clearEncoders();
+            }
+        }
+        return success;
+    }
+    
+    /**
+     * Service method.
+     */
+    @Override
+    public void service(org.apache.coyote.Request req, 
+                        org.apache.coyote.Response res)
+        throws Exception {
+
+        Request request = (Request) req.getNote(ADAPTER_NOTES);
+        Response response = (Response) res.getNote(ADAPTER_NOTES);
+
+        if (request == null) {
+
+            // Create objects
+            request = connector.createRequest();
+            request.setCoyoteRequest(req);
+            response = connector.createResponse();
+            response.setCoyoteResponse(res);
+
+            // Link objects
+            request.setResponse(response);
+            response.setRequest(request);
+
+            // Set as notes
+            req.setNote(ADAPTER_NOTES, request);
+            res.setNote(ADAPTER_NOTES, response);
+
+            // Set query string encoding
+            req.getParameters().setQueryStringEncoding
+                (connector.getURIEncoding());
+
+        }
+
+        if (connector.getXpoweredBy()) {
+            response.addHeader("X-Powered-By", POWERED_BY);
+        }
+
+        boolean comet = false;
+        boolean async = false;
+        
+        try {
+
+            // Parse and set Catalina and configuration specific 
+            // request parameters
+            req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
+            boolean postParseSuccess = postParseRequest(req, request, res, response);
+            if (postParseSuccess) {
+                //check valves if we support async
+                request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
+                // Calling the container
+                connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
+
+                if (request.isComet()) {
+                    if (!response.isClosed() && !response.isError()) {
+                        if (request.getAvailable() || (request.getContentLength() > 0 && (!request.isParametersParsed()))) {
+                            // Invoke a read event right away if there are available bytes
+                            if (event(req, res, SocketStatus.OPEN)) {
+                                comet = true;
+                                res.action(ActionCode.COMET_BEGIN, null);
+                            }
+                        } else {
+                            comet = true;
+                            res.action(ActionCode.COMET_BEGIN, null);
+                        }
+                    } else {
+                        // Clear the filter chain, as otherwise it will not be reset elsewhere
+                        // since this is a Comet request
+                        request.setFilterChain(null);
+                    }
+                }
+
+            }
+            AsyncContextImpl asyncConImpl = (AsyncContextImpl)request.getAsyncContext();
+            if (asyncConImpl != null) {
+                async = true;
+            } else if (!comet) {
+                request.finishRequest();
+                response.finishResponse();
+                if (postParseSuccess) {
+                    // Log only if processing was invoked.
+                    // If postParseRequest() failed, it has already logged it.
+                    ((Context) request.getMappingData().context).logAccess(
+                            request, response,
+                            System.currentTimeMillis() - req.getStartTime(),
+                            false);
+                }
+                req.action(ActionCode.POST_REQUEST , null);
+            }
+
+        } catch (IOException e) {
+            // Ignore
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.error(sm.getString("coyoteAdapter.service"), t);
+        } finally {
+            req.getRequestProcessor().setWorkerThreadName(null);
+            // Recycle the wrapper request and response
+            if (!comet && !async) {
+                request.recycle();
+                response.recycle();
+            } else {
+                // Clear converters so that the minimum amount of memory 
+                // is used by this processor
+                request.clearEncoders();
+                response.clearEncoders();
+            }
+        }
+
+    }
+
+
+    @Override
+    public void log(org.apache.coyote.Request req,
+            org.apache.coyote.Response res, long time) {
+
+        Request request = (Request) req.getNote(ADAPTER_NOTES);
+        Response response = (Response) res.getNote(ADAPTER_NOTES);
+        boolean create = false;
+        
+        if (request == null) {
+            create = true;
+            // Create objects
+            request = connector.createRequest();
+            request.setCoyoteRequest(req);
+            response = connector.createResponse();
+            response.setCoyoteResponse(res);
+
+            // Link objects
+            request.setResponse(response);
+            response.setRequest(request);
+
+            // Set as notes
+            req.setNote(ADAPTER_NOTES, request);
+            res.setNote(ADAPTER_NOTES, response);
+
+            // Set query string encoding
+            req.getParameters().setQueryStringEncoding
+                (connector.getURIEncoding());
+        }
+        
+        try {
+            connector.getService().getContainer().logAccess(
+                    request, response, time, true);
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.warn(sm.getString("coyoteAdapter.accesslogFail"), t);
+        }
+        
+        if (create) {
+            request.recycle();
+            response.recycle();
+        }
+    }
+    
+    
+    @Override
+    public String getDomain() {
+        return connector.getDomain();
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Parse additional request parameters.
+     */
+    protected boolean postParseRequest(org.apache.coyote.Request req, 
+                                       Request request,
+                                       org.apache.coyote.Response res, 
+                                       Response response)
+            throws Exception {
+
+        // XXX the processor may have set a correct scheme and port prior to this point, 
+        // in ajp13 protocols dont make sense to get the port from the connector...
+        // otherwise, use connector configuration
+        if (! req.scheme().isNull()) {
+            // use processor specified scheme to determine secure state
+            request.setSecure(req.scheme().equals("https"));
+        } else {
+            // use connector scheme and secure configuration, (defaults to
+            // "http" and false respectively)
+            req.scheme().setString(connector.getScheme());
+            request.setSecure(connector.getSecure());
+        }
+
+        // FIXME: the code below doesnt belongs to here, 
+        // this is only have sense 
+        // in Http11, not in ajp13..
+        // At this point the Host header has been processed.
+        // Override if the proxyPort/proxyHost are set 
+        String proxyName = connector.getProxyName();
+        int proxyPort = connector.getProxyPort();
+        if (proxyPort != 0) {
+            req.setServerPort(proxyPort);
+        }
+        if (proxyName != null) {
+            req.serverName().setString(proxyName);
+        }
+
+        // Copy the raw URI to the decodedURI
+        MessageBytes decodedURI = req.decodedURI();
+        decodedURI.duplicate(req.requestURI());
+        
+        // Parse the path parameters. This will:
+        //   - strip out the path parameters
+        //   - convert the decodedURI to bytes
+        parsePathParameters(req, request);
+        
+        // URI decoding
+        // %xx decoding of the URL
+        try {
+            req.getURLDecoder().convert(decodedURI, false);
+        } catch (IOException ioe) {
+            res.setStatus(400);
+            res.setMessage("Invalid URI: " + ioe.getMessage());
+            connector.getService().getContainer().logAccess(
+                    request, response, 0, true);
+            return false;
+        }
+        // Normalization
+        if (!normalize(req.decodedURI())) {
+            res.setStatus(400);
+            res.setMessage("Invalid URI");
+            connector.getService().getContainer().logAccess(
+                    request, response, 0, true);
+            return false;
+        }
+        // Character decoding
+        convertURI(decodedURI, request);
+        // Check that the URI is still normalized
+        if (!checkNormalize(req.decodedURI())) {
+            res.setStatus(400);
+            res.setMessage("Invalid URI character encoding");
+            connector.getService().getContainer().logAccess(
+                    request, response, 0, true);
+            return false;
+        }
+
+        // Set the remote principal
+        String principal = req.getRemoteUser().toString();
+        if (principal != null) {
+            request.setUserPrincipal(new CoyotePrincipal(principal));
+        }
+
+        // Set the authorization type
+        String authtype = req.getAuthType().toString();
+        if (authtype != null) {
+            request.setAuthType(authtype);
+        }
+
+        // Request mapping.
+        MessageBytes serverName;
+        if (connector.getUseIPVHosts()) {
+            serverName = req.localName();
+            if (serverName.isNull()) {
+                // well, they did ask for it
+                res.action(ActionCode.REQ_LOCAL_NAME_ATTRIBUTE, null);
+            }
+        } else {
+            serverName = req.serverName();
+        }
+        if (request.isAsyncStarted()) {
+            //TODO SERVLET3 - async
+            //reset mapping data, should prolly be done elsewhere
+            request.getMappingData().recycle();
+        }
+        
+        boolean mapRequired = true;
+        String version = null;
+        
+        while (mapRequired) {
+            if (version != null) {
+                // Once we have a version - that is it
+                mapRequired = false;
+            }
+            // This will map the the latest version by default
+            connector.getMapper().map(serverName, decodedURI, version,
+                                      request.getMappingData());
+            request.setContext((Context) request.getMappingData().context);
+            request.setWrapper((Wrapper) request.getMappingData().wrapper);
+
+            // Single contextVersion therefore no possibility of remap
+            if (request.getMappingData().contexts == null) {
+                mapRequired = false;
+            }
+
+            // If there is no context at this point, it is likely no ROOT context
+            // has been deployed
+            if (request.getContext() == null) {
+                res.setStatus(404);
+                res.setMessage("Not found");
+                // No context, so use host
+                request.getHost().logAccess(request, response, 0, true);
+                return false;
+            }
+        
+            // Now we have the context, we can parse the session ID from the URL
+            // (if any). Need to do this before we redirect in case we need to
+            // include the session id in the redirect
+            String sessionID = null;
+            if (request.getServletContext().getEffectiveSessionTrackingModes()
+                    .contains(SessionTrackingMode.URL)) {
+                
+                // Get the session ID if there was one
+                sessionID = request.getPathParameter(
+                        ApplicationSessionCookieConfig.getSessionUriParamName(
+                                request.getContext()));
+                if (sessionID != null) {
+                    request.setRequestedSessionId(sessionID);
+                    request.setRequestedSessionURL(true);
+                }
+            }
+
+            // Look for session ID in cookies and SSL session
+            parseSessionCookiesId(req, request);
+            parseSessionSslId(request);
+            
+            sessionID = request.getRequestedSessionId();
+            
+            if (mapRequired) {
+                if (sessionID == null) {
+                    // No session means no possibility of needing to remap
+                    mapRequired = false;
+                } else {
+                    // Find the context associated with the session
+                    Object[] objs = request.getMappingData().contexts;
+                    for (int i = (objs.length); i > 0; i--) {
+                        Context ctxt = (Context) objs[i - 1];
+                        if (ctxt.getManager().findSession(sessionID) != null) {
+                            // Was the correct context already mapped?
+                            if (ctxt.equals(request.getMappingData().context)) {
+                                mapRequired = false;
+                            } else {
+                                // Set version so second time through mapping the
+                                // correct context is found
+                                version = ctxt.getWebappVersion();
+                                // Reset mapping 
+                                request.getMappingData().recycle();
+                                break;
+                            }
+                        }
+                    }
+                    if (version == null) {
+                        // No matching context found. No need to re-map
+                        mapRequired = false;
+                    }
+                }                
+            }
+
+        }
+
+        // Possible redirect
+        MessageBytes redirectPathMB = request.getMappingData().redirectPath;
+        if (!redirectPathMB.isNull()) {
+            String redirectPath = urlEncoder.encode(redirectPathMB.toString());
+            String query = request.getQueryString();
+            if (request.isRequestedSessionIdFromURL()) {
+                // This is not optimal, but as this is not very common, it
+                // shouldn't matter
+                redirectPath = redirectPath + ";" +
+                    ApplicationSessionCookieConfig.getSessionUriParamName(
+                            request.getContext()) +
+                    "=" + request.getRequestedSessionId();
+            }
+            if (query != null) {
+                // This is not optimal, but as this is not very common, it
+                // shouldn't matter
+                redirectPath = redirectPath + "?" + query;
+            }
+            response.sendRedirect(redirectPath);
+            request.getContext().logAccess(request, response, 0, true);
+            return false;
+        }
+
+        // Filter trace method
+        if (!connector.getAllowTrace() 
+                && req.method().equalsIgnoreCase("TRACE")) {
+            Wrapper wrapper = request.getWrapper();
+            String header = null;
+            if (wrapper != null) {
+                String[] methods = wrapper.getServletMethods();
+                if (methods != null) {
+                    for (int i=0; i<methods.length; i++) {
+                        if ("TRACE".equals(methods[i])) {
+                            continue;
+                        }
+                        if (header == null) {
+                            header = methods[i];
+                        } else {
+                            header += ", " + methods[i];
+                        }
+                    }
+                }
+            }                               
+            res.setStatus(405);
+            res.addHeader("Allow", header);
+            res.setMessage("TRACE method is not allowed");
+            request.getContext().logAccess(request, response, 0, true);
+            return false;
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Extract the path parameters from the request. This assumes parameters are
+     * of the form /path;name=value;name2=value2/ etc. Currently only really
+     * interested in the session ID that will be in this form. Other parameters
+     * can safely be ignored.
+     * 
+     * @param req
+     * @param request
+     */
+    protected void parsePathParameters(org.apache.coyote.Request req,
+            Request request) {
+
+        // Process in bytes (this is default format so this is normally a NO-OP
+        req.decodedURI().toBytes();
+
+        ByteChunk uriBC = req.decodedURI().getByteChunk();
+        int semicolon = uriBC.indexOf(';', 0);
+
+        // What encoding to use? Some platforms, eg z/os, use a default
+        // encoding that doesn't give the expected result so be explicit
+        String enc = connector.getURIEncoding();
+        if (enc == null) {
+            enc = "ISO-8859-1";
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug(sm.getString("coyoteAdapter.debug", "uriBC",
+                    uriBC.toString()));
+            log.debug(sm.getString("coyoteAdapter.debug", "semicolon",
+                    String.valueOf(semicolon)));
+            log.debug(sm.getString("coyoteAdapter.debug", "enc", enc));
+        }
+
+        boolean warnedEncoding = false;
+
+        while (semicolon > -1) {
+            // Parse path param, and extract it from the decoded request URI
+            int start = uriBC.getStart();
+            int end = uriBC.getEnd();
+
+            int pathParamStart = semicolon + 1;
+            int pathParamEnd = ByteChunk.findBytes(uriBC.getBuffer(),
+                    start + pathParamStart, end,
+                    new byte[] {';', '/'});
+
+            String pv = null;
+
+            if (pathParamEnd >= 0) {
+                try {
+                    pv = (new String(uriBC.getBuffer(), start + pathParamStart,
+                                pathParamEnd - pathParamStart, enc));
+                } catch (UnsupportedEncodingException e) {
+                    if (!warnedEncoding) {
+                        log.warn(sm.getString("coyoteAdapter.parsePathParam",
+                                enc));
+                        warnedEncoding = true;
+                    }
+                }
+                // Extract path param from decoded request URI
+                byte[] buf = uriBC.getBuffer();
+                for (int i = 0; i < end - start - pathParamEnd; i++) {
+                    buf[start + semicolon + i] 
+                        = buf[start + i + pathParamEnd];
+                }
+                uriBC.setBytes(buf, start,
+                        end - start - pathParamEnd + semicolon);
+            } else {
+                try {
+                    pv = (new String(uriBC.getBuffer(), start + pathParamStart, 
+                                (end - start) - pathParamStart, enc));
+                } catch (UnsupportedEncodingException e) {
+                    if (!warnedEncoding) {
+                        log.warn(sm.getString("coyoteAdapter.parsePathParam",
+                                enc));
+                        warnedEncoding = true;
+                    }
+                }
+                uriBC.setEnd(start + semicolon);
+            }
+
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString("coyoteAdapter.debug", "pathParamStart",
+                        String.valueOf(pathParamStart)));
+                log.debug(sm.getString("coyoteAdapter.debug", "pathParamEnd",
+                        String.valueOf(pathParamEnd)));
+                log.debug(sm.getString("coyoteAdapter.debug", "pv", pv));
+            }
+
+            if (pv != null) {
+                int equals = pv.indexOf('=');
+                if (equals > -1) {
+                    String name = pv.substring(0, equals);
+                    String value = pv.substring(equals + 1); 
+                    request.addPathParameter(name, value);
+                    if (log.isDebugEnabled()) {
+                        log.debug(sm.getString("coyoteAdapter.debug", "equals",
+                                String.valueOf(equals)));
+                        log.debug(sm.getString("coyoteAdapter.debug", "name",
+                                name));
+                        log.debug(sm.getString("coyoteAdapter.debug", "value",
+                                value));
+                    }
+                }
+            }
+
+            semicolon = uriBC.indexOf(';', semicolon);
+        }
+    }
+    
+    
+    /**
+     * Look for SSL session ID if required. Only look for SSL Session ID if it
+     * is the only tracking method enabled.
+     */
+    protected void parseSessionSslId(Request request) {
+        if (request.getRequestedSessionId() == null &&
+                SSL_ONLY.equals(request.getServletContext()
+                        .getEffectiveSessionTrackingModes()) &&
+                        request.connector.secure) {
+            // TODO Is there a better way to map SSL sessions to our sesison ID?
+            // TODO The request.getAttribute() will cause a number of other SSL
+            //      attribute to be populated. Is this a performance concern?
+            request.setRequestedSessionId(
+                    request.getAttribute(SSLSupport.SESSION_ID_KEY).toString());
+            request.setRequestedSessionSSL(true);
+        }
+    }
+    
+    
+    /**
+     * Parse session id in URL.
+     */
+    protected void parseSessionCookiesId(org.apache.coyote.Request req, Request request) {
+
+        // If session tracking via cookies has been disabled for the current
+        // context, don't go looking for a session ID in a cookie as a cookie
+        // from a parent context with a session ID may be present which would
+        // overwrite the valid session ID encoded in the URL
+        Context context = (Context) request.getMappingData().context;
+        if (context != null && !context.getServletContext()
+                .getEffectiveSessionTrackingModes().contains(
+                        SessionTrackingMode.COOKIE))
+            return;
+        
+        // Parse session id from cookies
+        Cookies serverCookies = req.getCookies();
+        int count = serverCookies.getCookieCount();
+        if (count <= 0)
+            return;
+
+        String sessionCookieName =
+            ApplicationSessionCookieConfig.getSessionCookieName(context);
+
+        for (int i = 0; i < count; i++) {
+            ServerCookie scookie = serverCookies.getCookie(i);
+            if (scookie.getName().equals(sessionCookieName)) {
+                // Override anything requested in the URL
+                if (!request.isRequestedSessionIdFromCookie()) {
+                    // Accept only the first session id cookie
+                    convertMB(scookie.getValue());
+                    request.setRequestedSessionId
+                        (scookie.getValue().toString());
+                    request.setRequestedSessionCookie(true);
+                    request.setRequestedSessionURL(false);
+                    if (log.isDebugEnabled())
+                        log.debug(" Requested cookie session id is " +
+                            request.getRequestedSessionId());
+                } else {
+                    if (!request.isRequestedSessionIdValid()) {
+                        // Replace the session id until one is valid
+                        convertMB(scookie.getValue());
+                        request.setRequestedSessionId
+                            (scookie.getValue().toString());
+                    }
+                }
+            }
+        }
+
+    }
+
+
+    /**
+     * Character conversion of the URI.
+     */
+    protected void convertURI(MessageBytes uri, Request request) 
+        throws Exception {
+
+        ByteChunk bc = uri.getByteChunk();
+        int length = bc.getLength();
+        CharChunk cc = uri.getCharChunk();
+        cc.allocate(length, -1);
+
+        String enc = connector.getURIEncoding();
+        if (enc != null) {
+            B2CConverter conv = request.getURIConverter();
+            try {
+                if (conv == null) {
+                    conv = new B2CConverter(enc);
+                    request.setURIConverter(conv);
+                }
+            } catch (IOException e) {
+                // Ignore
+                log.error("Invalid URI encoding; using HTTP default");
+                connector.setURIEncoding(null);
+            }
+            if (conv != null) {
+                try {
+                    conv.convert(bc, cc, cc.getBuffer().length - cc.getEnd());
+                    uri.setChars(cc.getBuffer(), cc.getStart(), 
+                                 cc.getLength());
+                    return;
+                } catch (IOException e) {
+                    log.error("Invalid URI character encoding; trying ascii");
+                    cc.recycle();
+                }
+            }
+        }
+
+        // Default encoding: fast conversion
+        byte[] bbuf = bc.getBuffer();
+        char[] cbuf = cc.getBuffer();
+        int start = bc.getStart();
+        for (int i = 0; i < length; i++) {
+            cbuf[i] = (char) (bbuf[i + start] & 0xff);
+        }
+        uri.setChars(cbuf, 0, length);
+
+    }
+
+
+    /**
+     * Character conversion of the a US-ASCII MessageBytes.
+     */
+    protected void convertMB(MessageBytes mb) {
+
+        // This is of course only meaningful for bytes
+        if (mb.getType() != MessageBytes.T_BYTES)
+            return;
+        
+        ByteChunk bc = mb.getByteChunk();
+        CharChunk cc = mb.getCharChunk();
+        int length = bc.getLength();
+        cc.allocate(length, -1);
+
+        // Default encoding: fast conversion
+        byte[] bbuf = bc.getBuffer();
+        char[] cbuf = cc.getBuffer();
+        int start = bc.getStart();
+        for (int i = 0; i < length; i++) {
+            cbuf[i] = (char) (bbuf[i + start] & 0xff);
+        }
+        mb.setChars(cbuf, 0, length);
+
+    }
+
+
+    /**
+     * Normalize URI.
+     * <p>
+     * This method normalizes "\", "//", "/./" and "/../". This method will
+     * return false when trying to go above the root, or if the URI contains
+     * a null byte.
+     * 
+     * @param uriMB URI to be normalized
+     */
+    public static boolean normalize(MessageBytes uriMB) {
+
+        ByteChunk uriBC = uriMB.getByteChunk();
+        final byte[] b = uriBC.getBytes();
+        final int start = uriBC.getStart();
+        int end = uriBC.getEnd();
+
+        // An empty URL is not acceptable
+        if (start == end)
+            return false;
+
+        // URL * is acceptable
+        if ((end - start == 1) && b[start] == (byte) '*')
+          return true;
+
+        int pos = 0;
+        int index = 0;
+
+        // Replace '\' with '/'
+        // Check for null byte
+        for (pos = start; pos < end; pos++) {
+            if (b[pos] == (byte) '\\') {
+                if (ALLOW_BACKSLASH) {
+                    b[pos] = (byte) '/';
+                } else {
+                    return false;
+                }
+            }
+            if (b[pos] == (byte) 0) {
+                return false;
+            }
+        }
+
+        // The URL must start with '/'
+        if (b[start] != (byte) '/') {
+            return false;
+        }
+
+        // Replace "//" with "/"
+        for (pos = start; pos < (end - 1); pos++) {
+            if (b[pos] == (byte) '/') {
+                while ((pos + 1 < end) && (b[pos + 1] == (byte) '/')) {
+                    copyBytes(b, pos, pos + 1, end - pos - 1);
+                    end--;
+                }
+            }
+        }
+
+        // If the URI ends with "/." or "/..", then we append an extra "/"
+        // Note: It is possible to extend the URI by 1 without any side effect
+        // as the next character is a non-significant WS.
+        if (((end - start) >= 2) && (b[end - 1] == (byte) '.')) {
+            if ((b[end - 2] == (byte) '/') 
+                || ((b[end - 2] == (byte) '.') 
+                    && (b[end - 3] == (byte) '/'))) {
+                b[end] = (byte) '/';
+                end++;
+            }
+        }
+
+        uriBC.setEnd(end);
+
+        index = 0;
+
+        // Resolve occurrences of "/./" in the normalized path
+        while (true) {
+            index = uriBC.indexOf("/./", 0, 3, index);
+            if (index < 0)
+                break;
+            copyBytes(b, start + index, start + index + 2, 
+                      end - start - index - 2);
+            end = end - 2;
+            uriBC.setEnd(end);
+        }
+
+        index = 0;
+
+        // Resolve occurrences of "/../" in the normalized path
+        while (true) {
+            index = uriBC.indexOf("/../", 0, 4, index);
+            if (index < 0)
+                break;
+            // Prevent from going outside our context
+            if (index == 0)
+                return false;
+            int index2 = -1;
+            for (pos = start + index - 1; (pos >= 0) && (index2 < 0); pos --) {
+                if (b[pos] == (byte) '/') {
+                    index2 = pos;
+                }
+            }
+            copyBytes(b, start + index2, start + index + 3,
+                      end - start - index - 3);
+            end = end + index2 - index - 3;
+            uriBC.setEnd(end);
+            index = index2;
+        }
+
+        return true;
+
+    }
+
+
+    /**
+     * Check that the URI is normalized following character decoding.
+     * <p>
+     * This method checks for "\", 0, "//", "/./" and "/../". This method will
+     * return false if sequences that are supposed to be normalized are still 
+     * present in the URI.
+     * 
+     * @param uriMB URI to be checked (should be chars)
+     */
+    public static boolean checkNormalize(MessageBytes uriMB) {
+
+        CharChunk uriCC = uriMB.getCharChunk();
+        char[] c = uriCC.getChars();
+        int start = uriCC.getStart();
+        int end = uriCC.getEnd();
+
+        int pos = 0;
+
+        // Check for '\' and 0
+        for (pos = start; pos < end; pos++) {
+            if (c[pos] == '\\') {
+                return false;
+            }
+            if (c[pos] == 0) {
+                return false;
+            }
+        }
+
+        // Check for "//"
+        for (pos = start; pos < (end - 1); pos++) {
+            if (c[pos] == '/') {
+                if (c[pos + 1] == '/') {
+                    return false;
+                }
+            }
+        }
+
+        // Check for ending with "/." or "/.."
+        if (((end - start) >= 2) && (c[end - 1] == '.')) {
+            if ((c[end - 2] == '/') 
+                    || ((c[end - 2] == '.') 
+                    && (c[end - 3] == '/'))) {
+                return false;
+            }
+        }
+
+        // Check for "/./"
+        if (uriCC.indexOf("/./", 0, 3, 0) >= 0) {
+            return false;
+        }
+
+        // Check for "/../"
+        if (uriCC.indexOf("/../", 0, 4, 0) >= 0) {
+            return false;
+        }
+
+        return true;
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Copy an array of bytes to a different position. Used during 
+     * normalization.
+     */
+    protected static void copyBytes(byte[] b, int dest, int src, int len) {
+        for (int pos = 0; pos < len; pos++) {
+            b[pos + dest] = b[pos + src];
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/connector/CoyoteInputStream.java b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/CoyoteInputStream.java
new file mode 100644
index 0000000..dfdca5b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/CoyoteInputStream.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.connector;
+
+import java.io.IOException;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+
+import javax.servlet.ServletInputStream;
+
+import org.apache.catalina.security.SecurityUtil;
+
+/**
+ * This class handles reading bytes.
+ * 
+ * @author Remy Maucherat
+ * @author Jean-Francois Arcand
+ */
+public class CoyoteInputStream
+    extends ServletInputStream {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    protected InputBuffer ib;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    protected CoyoteInputStream(InputBuffer ib) {
+        this.ib = ib;
+    }
+
+
+    // -------------------------------------------------------- Package Methods
+
+
+    /**
+     * Clear facade.
+     */
+    void clear() {
+        ib = null;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Prevent cloning the facade.
+     */
+    @Override
+    protected Object clone()
+        throws CloneNotSupportedException {
+        throw new CloneNotSupportedException();
+    }
+
+
+    // --------------------------------------------- ServletInputStream Methods
+
+
+    @Override
+    public int read()
+        throws IOException {    
+        if (SecurityUtil.isPackageProtectionEnabled()){
+            
+            try{
+                Integer result = 
+                    AccessController.doPrivileged(
+                        new PrivilegedExceptionAction<Integer>(){
+
+                            @Override
+                            public Integer run() throws IOException{
+                                Integer integer = Integer.valueOf(ib.readByte());
+                                return integer;
+                            }
+
+                });
+                return result.intValue();
+            } catch(PrivilegedActionException pae){
+                Exception e = pae.getException();
+                if (e instanceof IOException){
+                    throw (IOException)e;
+                } else {
+                    throw new RuntimeException(e.getMessage(), e);
+                }
+            }
+        } else {
+            return ib.readByte();
+        }
+    }
+
+    @Override
+    public int available() throws IOException {
+        
+        if (SecurityUtil.isPackageProtectionEnabled()){
+            try{
+                Integer result = 
+                    AccessController.doPrivileged(
+                        new PrivilegedExceptionAction<Integer>(){
+
+                            @Override
+                            public Integer run() throws IOException{
+                                Integer integer = Integer.valueOf(ib.available());
+                                return integer;
+                            }
+
+                });
+                return result.intValue();
+            } catch(PrivilegedActionException pae){
+                Exception e = pae.getException();
+                if (e instanceof IOException){
+                    throw (IOException)e;
+                } else {
+                    throw new RuntimeException(e.getMessage(), e);
+                }
+            }
+        } else {
+           return ib.available();
+        }    
+    }
+
+    @Override
+    public int read(final byte[] b) throws IOException {
+        
+        if (SecurityUtil.isPackageProtectionEnabled()){
+            try{
+                Integer result = 
+                    AccessController.doPrivileged(
+                        new PrivilegedExceptionAction<Integer>(){
+
+                            @Override
+                            public Integer run() throws IOException{
+                                Integer integer = 
+                                    Integer.valueOf(ib.read(b, 0, b.length));
+                                return integer;
+                            }
+
+                });
+                return result.intValue();
+            } catch(PrivilegedActionException pae){
+                Exception e = pae.getException();
+                if (e instanceof IOException){
+                    throw (IOException)e;
+                } else {
+                    throw new RuntimeException(e.getMessage() ,e);
+                }
+            }
+        } else {
+            return ib.read(b, 0, b.length);
+         }        
+    }
+
+
+    @Override
+    public int read(final byte[] b, final int off, final int len)
+        throws IOException {
+            
+        if (SecurityUtil.isPackageProtectionEnabled()){
+            try{
+                Integer result = 
+                    AccessController.doPrivileged(
+                        new PrivilegedExceptionAction<Integer>(){
+
+                            @Override
+                            public Integer run() throws IOException{
+                                Integer integer = 
+                                    Integer.valueOf(ib.read(b, off, len));
+                                return integer;
+                            }
+
+                });
+                return result.intValue();
+            } catch(PrivilegedActionException pae){
+                Exception e = pae.getException();
+                if (e instanceof IOException){
+                    throw (IOException)e;
+                } else {
+                    throw new RuntimeException(e.getMessage(), e);
+                }
+            }
+        } else {
+            return ib.read(b, off, len);
+        }            
+    }
+
+
+    @Override
+    public int readLine(byte[] b, int off, int len) throws IOException {
+        return super.readLine(b, off, len);
+    }
+
+
+    /** 
+     * Close the stream
+     * Since we re-cycle, we can't allow the call to super.close()
+     * which would permanently disable us.
+     */
+    @Override
+    public void close() throws IOException {
+        
+        if (SecurityUtil.isPackageProtectionEnabled()){
+            try{
+                AccessController.doPrivileged(
+                    new PrivilegedExceptionAction<Void>(){
+
+                        @Override
+                        public Void run() throws IOException{
+                            ib.close();
+                            return null;
+                        }
+
+                });
+            } catch(PrivilegedActionException pae){
+                Exception e = pae.getException();
+                if (e instanceof IOException){
+                    throw (IOException)e;
+                } else {
+                    throw new RuntimeException(e.getMessage(), e);
+                }
+            }
+        } else {
+             ib.close();
+        }
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/connector/CoyoteOutputStream.java b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/CoyoteOutputStream.java
new file mode 100644
index 0000000..b264634
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/CoyoteOutputStream.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 org.apache.catalina.connector;
+
+import java.io.IOException;
+
+import javax.servlet.ServletOutputStream;
+
+/**
+ * Coyote implementation of the servlet output stream.
+ * 
+ * @author Costin Manolache
+ * @author Remy Maucherat
+ */
+public class CoyoteOutputStream 
+    extends ServletOutputStream {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    protected OutputBuffer ob;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    protected CoyoteOutputStream(OutputBuffer ob) {
+        this.ob = ob;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Prevent cloning the facade.
+     */
+    @Override
+    protected Object clone()
+        throws CloneNotSupportedException {
+        throw new CloneNotSupportedException();
+    }
+
+
+    // -------------------------------------------------------- Package Methods
+
+
+    /**
+     * Clear facade.
+     */
+    void clear() {
+        ob = null;
+    }
+
+
+    // --------------------------------------------------- OutputStream Methods
+
+
+    @Override
+    public void write(int i)
+        throws IOException {
+        ob.writeByte(i);
+    }
+
+
+    @Override
+    public void write(byte[] b)
+        throws IOException {
+        write(b, 0, b.length);
+    }
+
+
+    @Override
+    public void write(byte[] b, int off, int len)
+        throws IOException {
+        ob.write(b, off, len);
+    }
+
+
+    /**
+     * Will send the buffer to the client.
+     */
+    @Override
+    public void flush()
+        throws IOException {
+        ob.flush();
+    }
+
+
+    @Override
+    public void close()
+        throws IOException {
+        ob.close();
+    }
+
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/connector/CoyotePrincipal.java b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/CoyotePrincipal.java
new file mode 100644
index 0000000..13a3ede
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/CoyotePrincipal.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.connector;
+
+import java.io.Serializable;
+import java.security.Principal;
+
+/**
+ * Generic implementation of <strong>java.security.Principal</strong> that
+ * is used to represent principals authenticated at the protocol handler level.
+ *
+ * @author Remy Maucherat
+ * @version $Id: CoyotePrincipal.java,v 1.1 2011/06/28 21:08:19 rherrmann Exp $
+ */
+
+public class CoyotePrincipal implements Principal, Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+
+    // ----------------------------------------------------------- Constructors
+
+    public CoyotePrincipal(String name) {
+
+        this.name = name;
+
+    }
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * The username of the user represented by this Principal.
+     */
+    protected String name = null;
+
+    @Override
+    public String getName() {
+        return (this.name);
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return a String representation of this object, which exposes only
+     * information that should be public.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("CoyotePrincipal[");
+        sb.append(this.name);
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/connector/CoyoteReader.java b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/CoyoteReader.java
new file mode 100644
index 0000000..eb24dd8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/CoyoteReader.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.catalina.connector;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+
+
+/**
+ * Coyote implementation of the buffered reader.
+ * 
+ * @author Remy Maucherat
+ */
+public class CoyoteReader
+    extends BufferedReader {
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    private static final char[] LINE_SEP = { '\r', '\n' };
+    private static final int MAX_LINE_LENGTH = 4096;
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    protected InputBuffer ib;
+
+
+    protected char[] lineBuffer = null;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    public CoyoteReader(InputBuffer ib) {
+        super(ib, 1);
+        this.ib = ib;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Prevent cloning the facade.
+     */
+    @Override
+    protected Object clone()
+        throws CloneNotSupportedException {
+        throw new CloneNotSupportedException();
+    }
+
+
+    // -------------------------------------------------------- Package Methods
+
+
+    /**
+     * Clear facade.
+     */
+    void clear() {
+        ib = null;
+    }
+
+
+    // --------------------------------------------------------- Reader Methods
+
+
+    @Override
+    public void close()
+        throws IOException {
+        ib.close();
+    }
+
+
+    @Override
+    public int read()
+        throws IOException {
+        return ib.read();
+    }
+
+
+    @Override
+    public int read(char[] cbuf)
+        throws IOException {
+        return ib.read(cbuf, 0, cbuf.length);
+    }
+
+
+    @Override
+    public int read(char[] cbuf, int off, int len)
+        throws IOException {
+        return ib.read(cbuf, off, len);
+    }
+
+
+    @Override
+    public long skip(long n)
+        throws IOException {
+        return ib.skip(n);
+    }
+
+
+    @Override
+    public boolean ready()
+        throws IOException {
+        return ib.ready();
+    }
+
+
+    @Override
+    public boolean markSupported() {
+        return true;
+    }
+
+
+    @Override
+    public void mark(int readAheadLimit)
+        throws IOException {
+        ib.mark(readAheadLimit);
+    }
+
+
+    @Override
+    public void reset()
+        throws IOException {
+        ib.reset();
+    }
+
+
+    @Override
+    public String readLine()
+        throws IOException {
+
+        if (lineBuffer == null) {
+            lineBuffer = new char[MAX_LINE_LENGTH];
+       }
+
+        String result = null;
+
+        int pos = 0;
+        int end = -1;
+        int skip = -1;
+        StringBuilder aggregator = null;
+        while (end < 0) {
+            mark(MAX_LINE_LENGTH);
+            while ((pos < MAX_LINE_LENGTH) && (end < 0)) {
+                int nRead = read(lineBuffer, pos, MAX_LINE_LENGTH - pos);
+                if (nRead < 0) {
+                    if (pos == 0 && aggregator == null) {
+                        return null;
+                    }
+                    end = pos;
+                    skip = pos;
+                }
+                for (int i = pos; (i < (pos + nRead)) && (end < 0); i++) {
+                    if (lineBuffer[i] == LINE_SEP[0]) {
+                        end = i;
+                        skip = i + 1;
+                        char nextchar;
+                        if (i == (pos + nRead - 1)) {
+                            nextchar = (char) read();
+                        } else {
+                            nextchar = lineBuffer[i+1];
+                        }
+                        if (nextchar == LINE_SEP[1]) {
+                            skip++;
+                        }
+                    } else if (lineBuffer[i] == LINE_SEP[1]) {
+                        end = i;
+                        skip = i + 1;
+                    }
+                }
+                if (nRead > 0) {
+                    pos += nRead;
+                }
+            }
+            if (end < 0) {
+                if (aggregator == null) {
+                    aggregator = new StringBuilder();
+                }
+                aggregator.append(lineBuffer);
+                pos = 0;
+            } else {
+                reset();
+                skip(skip);
+            }
+        }
+
+        if (aggregator == null) {
+            result = new String(lineBuffer, 0, end);
+        } else {
+            aggregator.append(lineBuffer, 0, end);
+            result = aggregator.toString();
+        }
+
+        return result;
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/connector/CoyoteWriter.java b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/CoyoteWriter.java
new file mode 100644
index 0000000..fbf2d90
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/CoyoteWriter.java
@@ -0,0 +1,319 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.connector;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * Coyote implementation of the servlet writer.
+ * 
+ * @author Remy Maucherat
+ */
+public class CoyoteWriter
+    extends PrintWriter {
+
+
+    // -------------------------------------------------------------- Constants
+
+    // No need for a do privileged block - every web app has permission to read
+    // this by default
+    private static final char[] LINE_SEP =
+        System.getProperty("line.separator").toCharArray();
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    protected OutputBuffer ob;
+    protected boolean error = false;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    public CoyoteWriter(OutputBuffer ob) {
+        super(ob);
+        this.ob = ob;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Prevent cloning the facade.
+     */
+    @Override
+    protected Object clone()
+        throws CloneNotSupportedException {
+        throw new CloneNotSupportedException();
+    }
+
+
+    // -------------------------------------------------------- Package Methods
+
+
+    /**
+     * Clear facade.
+     */
+    void clear() {
+        ob = null;
+    }
+
+
+    /**
+     * Recycle.
+     */
+    void recycle() {
+        error = false;
+    }
+
+
+    // --------------------------------------------------------- Writer Methods
+
+
+    @Override
+    public void flush() {
+
+        if (error)
+            return;
+
+        try {
+            ob.flush();
+        } catch (IOException e) {
+            error = true;
+        }
+
+    }
+
+
+    @Override
+    public void close() {
+
+        // We don't close the PrintWriter - super() is not called,
+        // so the stream can be reused. We close ob.
+        try {
+            ob.close();
+        } catch (IOException ex ) {
+            // Ignore
+        }
+        error = false;
+
+    }
+
+
+    @Override
+    public boolean checkError() {
+        flush();
+        return error;
+    }
+
+
+    @Override
+    public void write(int c) {
+
+        if (error)
+            return;
+
+        try {
+            ob.write(c);
+        } catch (IOException e) {
+            error = true;
+        }
+
+    }
+
+
+    @Override
+    public void write(char buf[], int off, int len) {
+
+        if (error)
+            return;
+
+        try {
+            ob.write(buf, off, len);
+        } catch (IOException e) {
+            error = true;
+        }
+
+    }
+
+
+    @Override
+    public void write(char buf[]) {
+        write(buf, 0, buf.length);
+    }
+
+
+    @Override
+    public void write(String s, int off, int len) {
+
+        if (error)
+            return;
+
+        try {
+            ob.write(s, off, len);
+        } catch (IOException e) {
+            error = true;
+        }
+
+    }
+
+
+    @Override
+    public void write(String s) {
+        write(s, 0, s.length());
+    }
+
+
+    // ---------------------------------------------------- PrintWriter Methods
+
+
+    @Override
+    public void print(boolean b) {
+        if (b) {
+            write("true");
+        } else {
+            write("false");
+        }
+    }
+
+
+    @Override
+    public void print(char c) {
+        write(c);
+    }
+
+
+    @Override
+    public void print(int i) {
+        write(String.valueOf(i));
+    }
+
+
+    @Override
+    public void print(long l) {
+        write(String.valueOf(l));
+    }
+
+
+    @Override
+    public void print(float f) {
+        write(String.valueOf(f));
+    }
+
+
+    @Override
+    public void print(double d) {
+        write(String.valueOf(d));
+    }
+
+
+    @Override
+    public void print(char s[]) {
+        write(s);
+    }
+
+
+    @Override
+    public void print(String s) {
+        if (s == null) {
+            s = "null";
+        }
+        write(s);
+    }
+
+
+    @Override
+    public void print(Object obj) {
+        write(String.valueOf(obj));
+    }
+
+
+    @Override
+    public void println() {
+        write(LINE_SEP);
+    }
+
+
+    @Override
+    public void println(boolean b) {
+        print(b);
+        println();
+    }
+
+
+    @Override
+    public void println(char c) {
+        print(c);
+        println();
+    }
+
+
+    @Override
+    public void println(int i) {
+        print(i);
+        println();
+    }
+
+
+    @Override
+    public void println(long l) {
+        print(l);
+        println();
+    }
+
+
+    @Override
+    public void println(float f) {
+        print(f);
+        println();
+    }
+
+
+    @Override
+    public void println(double d) {
+        print(d);
+        println();
+    }
+
+
+    @Override
+    public void println(char c[]) {
+        print(c);
+        println();
+    }
+
+
+    @Override
+    public void println(String s) {
+        print(s);
+        println();
+    }
+
+
+    @Override
+    public void println(Object o) {
+        print(o);
+        println();
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/connector/InputBuffer.java b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/InputBuffer.java
new file mode 100644
index 0000000..4d03bb2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/InputBuffer.java
@@ -0,0 +1,544 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.connector;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.HashMap;
+
+import org.apache.catalina.security.SecurityUtil;
+import org.apache.coyote.ActionCode;
+import org.apache.coyote.Request;
+import org.apache.tomcat.util.buf.B2CConverter;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.CharChunk;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * The buffer used by Tomcat request. This is a derivative of the Tomcat 3.3
+ * OutputBuffer, adapted to handle input instead of output. This allows 
+ * complete recycling of the facade objects (the ServletInputStream and the
+ * BufferedReader).
+ *
+ * @author Remy Maucherat
+ */
+public class InputBuffer extends Reader
+    implements ByteChunk.ByteInputChannel, CharChunk.CharInputChannel,
+               CharChunk.CharOutputChannel {
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    public static final String DEFAULT_ENCODING = 
+        org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING;
+    public static final int DEFAULT_BUFFER_SIZE = 8*1024;
+
+    // The buffer can be used for byte[] and char[] reading
+    // ( this is needed to support ServletInputStream and BufferedReader )
+    public final int INITIAL_STATE = 0;
+    public final int CHAR_STATE = 1;
+    public final int BYTE_STATE = 2;
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The byte buffer.
+     */
+    private ByteChunk bb;
+
+
+    /**
+     * The chunk buffer.
+     */
+    private CharChunk cb;
+
+
+    /**
+     * State of the output buffer.
+     */
+    private int state = 0;
+
+
+    /**
+     * Flag which indicates if the input buffer is closed.
+     */
+    private boolean closed = false;
+
+
+    /**
+     * Encoding to use.
+     */
+    private String enc;
+
+
+    /**
+     * Encoder is set.
+     */
+    private boolean gotEnc = false;
+
+
+    /**
+     * List of encoders.
+     */
+    protected HashMap<String,B2CConverter> encoders =
+        new HashMap<String,B2CConverter>();
+
+
+    /**
+     * Current byte to char converter.
+     */
+    protected B2CConverter conv;
+
+
+    /**
+     * Associated Coyote request.
+     */
+    private Request coyoteRequest;
+
+
+    /**
+     * Buffer position.
+     */
+    private int markPos = -1;
+
+
+    /**
+     * Buffer size.
+     */
+    private int size = -1;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Default constructor. Allocate the buffer with the default buffer size.
+     */
+    public InputBuffer() {
+
+        this(DEFAULT_BUFFER_SIZE);
+
+    }
+
+
+    /**
+     * Alternate constructor which allows specifying the initial buffer size.
+     * 
+     * @param size Buffer size to use
+     */
+    public InputBuffer(int size) {
+
+        this.size = size;
+        bb = new ByteChunk(size);
+        bb.setLimit(size);
+        bb.setByteInputChannel(this);
+        cb = new CharChunk(size);
+        cb.setLimit(size);
+        cb.setOptimizedWrite(false);
+        cb.setCharInputChannel(this);
+        cb.setCharOutputChannel(this);
+
+    }
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Associated Coyote request.
+     * 
+     * @param coyoteRequest Associated Coyote request
+     */
+    public void setRequest(Request coyoteRequest) {
+        this.coyoteRequest = coyoteRequest;
+    }
+
+
+    /**
+     * Get associated Coyote request.
+     * 
+     * @return the associated Coyote request
+     */
+    public Request getRequest() {
+        return this.coyoteRequest;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Recycle the output buffer.
+     */
+    public void recycle() {
+        
+        state = INITIAL_STATE;
+        
+        // If usage of mark made the buffer too big, reallocate it
+        if (cb.getChars().length > size) {
+            cb = new CharChunk(size);
+            cb.setLimit(size);
+            cb.setOptimizedWrite(false);
+            cb.setCharInputChannel(this);
+            cb.setCharOutputChannel(this);
+        } else {
+            cb.recycle();
+        }
+        markPos = -1;
+        bb.recycle(); 
+        closed = false;
+        
+        if (conv != null) {
+            conv.recycle();
+        }
+        
+        gotEnc = false;
+        enc = null;
+        
+    }
+
+
+    /**
+     * Clear cached encoders (to save memory for Comet requests).
+     */
+    public void clearEncoders() {
+        encoders.clear();
+    }
+    
+    
+    /**
+     * Close the input buffer.
+     * 
+     * @throws IOException An underlying IOException occurred
+     */
+    @Override
+    public void close()
+        throws IOException {
+        closed = true;
+    }
+
+
+    public int available() {
+        int available = 0;
+        if (state == BYTE_STATE) {
+            available = bb.getLength();
+        } else if (state == CHAR_STATE) {
+            available = cb.getLength();
+        }
+        if (available == 0) {
+            coyoteRequest.action(ActionCode.AVAILABLE, null);
+            available = (coyoteRequest.getAvailable() > 0) ? 1 : 0;
+        }
+        return available;
+    }
+
+
+    // ------------------------------------------------- Bytes Handling Methods
+
+
+    /** 
+     * Reads new bytes in the byte chunk.
+     * 
+     * @param cbuf Byte buffer to be written to the response
+     * @param off Offset
+     * @param len Length
+     * 
+     * @throws IOException An underlying IOException occurred
+     */
+    @Override
+    public int realReadBytes(byte cbuf[], int off, int len)
+            throws IOException {
+
+        if (closed)
+            return -1;
+        if (coyoteRequest == null)
+            return -1;
+
+        if(state == INITIAL_STATE)
+            state = BYTE_STATE;
+
+        int result = coyoteRequest.doRead(bb);
+
+        return result;
+
+    }
+
+
+    public int readByte()
+        throws IOException {
+
+        if (closed)
+            throw new IOException(sm.getString("inputBuffer.streamClosed"));
+
+        return bb.substract();
+    }
+
+
+    public int read(byte[] b, int off, int len)
+        throws IOException {
+
+        if (closed)
+            throw new IOException(sm.getString("inputBuffer.streamClosed"));
+
+        return bb.substract(b, off, len);
+    }
+
+
+    // ------------------------------------------------- Chars Handling Methods
+
+
+    /**
+     * Since the converter will use append, it is possible to get chars to
+     * be removed from the buffer for "writing". Since the chars have already
+     * been read before, they are ignored. If a mark was set, then the
+     * mark is lost.
+     */
+    @Override
+    public void realWriteChars(char c[], int off, int len) 
+        throws IOException {
+        markPos = -1;
+        cb.setOffset(0);
+        cb.setEnd(0);
+    }
+
+
+    public void setEncoding(String s) {
+        enc = s;
+    }
+
+
+    @Override
+    public int realReadChars(char cbuf[], int off, int len)
+        throws IOException {
+
+        if (!gotEnc)
+            setConverter();
+
+        if (bb.getLength() <= 0) {
+            int nRead = realReadBytes(bb.getBytes(), 0, bb.getBytes().length);
+            if (nRead < 0) {
+                return -1;
+            }
+        }
+
+        if (markPos == -1) {
+            cb.setOffset(0);
+            cb.setEnd(0);
+        }
+        int limit = bb.getLength()+cb.getStart();
+        if ( cb.getLimit() < limit )
+            cb.setLimit(limit);
+        state = CHAR_STATE;
+        conv.convert(bb, cb, bb.getLength());
+        bb.setOffset(bb.getEnd());
+
+        return cb.getLength();
+
+    }
+
+
+    @Override
+    public int read()
+        throws IOException {
+
+        if (closed)
+            throw new IOException(sm.getString("inputBuffer.streamClosed"));
+
+        return cb.substract();
+    }
+
+
+    @Override
+    public int read(char[] cbuf)
+        throws IOException {
+
+        if (closed)
+            throw new IOException(sm.getString("inputBuffer.streamClosed"));
+
+        return read(cbuf, 0, cbuf.length);
+    }
+
+
+    @Override
+    public int read(char[] cbuf, int off, int len)
+        throws IOException {
+
+        if (closed)
+            throw new IOException(sm.getString("inputBuffer.streamClosed"));
+
+        return cb.substract(cbuf, off, len);
+    }
+
+
+    @Override
+    public long skip(long n)
+        throws IOException {
+
+
+        if (closed)
+            throw new IOException(sm.getString("inputBuffer.streamClosed"));
+
+        if (n < 0) {
+            throw new IllegalArgumentException();
+        }
+
+        long nRead = 0;
+        while (nRead < n) {
+            if (cb.getLength() >= n) {
+                cb.setOffset(cb.getStart() + (int) n);
+                nRead = n;
+            } else {
+                nRead += cb.getLength();
+                cb.setOffset(cb.getEnd());
+                int toRead = 0;
+                if (cb.getChars().length < (n - nRead)) {
+                    toRead = cb.getChars().length;
+                } else {
+                    toRead = (int) (n - nRead);
+                }
+                int nb = realReadChars(cb.getChars(), 0, toRead);
+                if (nb < 0)
+                    break;
+            }
+        }
+
+        return nRead;
+
+    }
+
+
+    @Override
+    public boolean ready()
+        throws IOException {
+
+        if (closed)
+            throw new IOException(sm.getString("inputBuffer.streamClosed"));
+
+        return (available() > 0);
+    }
+
+
+    @Override
+    public boolean markSupported() {
+        return true;
+    }
+
+
+    @Override
+    public void mark(int readAheadLimit)
+        throws IOException {
+
+        if (closed)
+            throw new IOException(sm.getString("inputBuffer.streamClosed"));
+
+        if (cb.getLength() <= 0) {
+            cb.setOffset(0);
+            cb.setEnd(0);
+        } else {
+            if ((cb.getBuffer().length > (2 * size)) 
+                && (cb.getLength()) < (cb.getStart())) {
+                System.arraycopy(cb.getBuffer(), cb.getStart(), 
+                                 cb.getBuffer(), 0, cb.getLength());
+                cb.setEnd(cb.getLength());
+                cb.setOffset(0);
+            }
+        }
+        cb.setLimit(cb.getStart() + readAheadLimit + size);
+        markPos = cb.getStart();
+    }
+
+
+    @Override
+    public void reset()
+        throws IOException {
+
+        if (closed)
+            throw new IOException(sm.getString("inputBuffer.streamClosed"));
+
+        if (state == CHAR_STATE) {
+            if (markPos < 0) {
+                cb.recycle();
+                markPos = -1;
+                throw new IOException();
+            } else {
+                cb.setOffset(markPos);
+            }
+        } else {
+            bb.recycle();
+        }
+    }
+
+
+    public void checkConverter() 
+        throws IOException {
+
+        if (!gotEnc)
+            setConverter();
+
+    }
+
+
+    protected void setConverter()
+        throws IOException {
+
+        if (coyoteRequest != null)
+            enc = coyoteRequest.getCharacterEncoding();
+
+        gotEnc = true;
+        if (enc == null)
+            enc = DEFAULT_ENCODING;
+        conv = encoders.get(enc);
+        if (conv == null) {
+            if (SecurityUtil.isPackageProtectionEnabled()){
+                try{
+                    conv = AccessController.doPrivileged(
+                            new PrivilegedExceptionAction<B2CConverter>(){
+
+                                @Override
+                                public B2CConverter run() throws IOException {
+                                    return new B2CConverter(enc);
+                                }
+
+                            }
+                    );              
+                }catch(PrivilegedActionException ex){
+                    Exception e = ex.getException();
+                    if (e instanceof IOException)
+                        throw (IOException)e; 
+                }
+            } else {
+                conv = new B2CConverter(enc);
+            }
+            encoders.put(enc, conv);
+        }
+
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/connector/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/LocalStrings.properties
new file mode 100644
index 0000000..a0d38fa
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/LocalStrings.properties
@@ -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.
+
+
+#
+# CoyoteConnector
+#
+coyoteConnector.cannotRegisterProtocol=Cannot register MBean for the Protocol
+coyoteConnector.protocolHandlerDestroyFailed=Protocol handler destroy failed
+coyoteConnector.protocolHandlerInitializationFailed=Protocol handler initialization failed
+coyoteConnector.protocolHandlerInstantiationFailed=Protocol handler instantiation failed
+coyoteConnector.protocolHandlerStartFailed=Protocol handler start failed
+coyoteConnector.protocolRegistrationFailed=Protocol JMX registration failed
+coyoteConnector.protocolHandlerPauseFailed=Protocol handler pause failed
+coyoteConnector.protocolHandlerResumeFailed=Protocol handler resume failed
+coyoteConnector.MapperRegistration=register Mapper: {0}
+coyoteConnector.protocolUnregistrationFailed=Protocol handler stop failed
+coyoteConnector.parseBodyMethodNoTrace=TRACE method MUST NOT include an entity (see RFC 2616 Section 9.6)
+
+#
+# CoyoteAdapter
+#
+coyoteAdapter.service=An exception or error occurred in the container during the request processing
+coyoteAdapter.read=The servlet did not read all available bytes during the processing of the read event
+coyoteAdapter.parsePathParam=Unable to parse the path parameters using encoding [{0}]. The path parameters in the URL will be ignored.
+coyoteAdapter.debug=The variable [{0}] has value [{1}]
+coyoteAdapter.accesslogFail=Exception while attempting to add an entry to the access log
+
+#
+# CoyoteResponse
+#
+coyoteResponse.getOutputStream.ise=getWriter() has already been called for this response
+coyoteResponse.getWriter.ise=getOutputStream() has already been called for this response
+coyoteResponse.resetBuffer.ise=Cannot reset buffer after response has been committed
+coyoteResponse.sendError.ise=Cannot call sendError() after the response has been committed
+coyoteResponse.sendRedirect.ise=Cannot call sendRedirect() after the response has been committed
+coyoteResponse.setBufferSize.ise=Cannot change buffer size after data has been written
+
+#
+# CoyoteRequest
+#
+coyoteRequest.getInputStream.ise=getReader() has already been called for this request
+coyoteRequest.getReader.ise=getInputStream() has already been called for this request
+coyoteRequest.sessionCreateCommitted=Cannot create a session after the response has been committed
+coyoteRequest.setAttribute.namenull=Cannot call setAttribute with a null name
+coyoteRequest.listenerStart=Exception sending context initialized event to listener instance of class {0}
+coyoteRequest.listenerStop=Exception sending context destroyed event to listener instance of class {0}
+coyoteRequest.attributeEvent=Exception thrown by attributes event listener
+coyoteRequest.parseParameters=Exception thrown whilst processing POSTed parameters
+coyoteRequest.postTooLarge=Parameters were not parsed because the size of the posted data was too big. Use the maxPostSize attribute of the connector to resolve this if the application should accept large POSTs.
+coyoteRequest.chunkedPostTooLarge=Parameters were not parsed because the size of the posted data was too big. Because this request was a chunked request, it could not be processed further. Use the maxPostSize attribute of the connector to resolve this if the application should accept large POSTs.
+coyoteRequest.alreadyAuthenticated=This is request has already been authenticated
+coyoteRequest.noLoginConfig=No authentication mechanism has been configured for this context
+coyoteRequest.authenticate.ise=Cannot call authenticate() after the reponse has been committed
+coyoteRequest.uploadLocationInvalid=The temporary upload location [{0}] is not valid
+coyoteRequest.sessionEndAccessFail=Exception triggered ending access to session while recycling request
+
+requestFacade.nullRequest=The request object has been recycled and is no longer associated with this facade
+
+responseFacade.nullResponse=The response object has been recycled and is no longer associated with this facade
+
+cometEvent.nullRequest=The event object has been recycled and is no longer associated with a request
+
+#
+# MapperListener
+#
+mapperListener.unknownDefaultHost=Unknown default host [{0}] for connector [{1}]
+mapperListener.registerHost=Register host [{0}] at domain [{1}] for connector [{2}]
+mapperListener.unregisterHost=Unregister host [{0}] at domain [{1}] for connector [{2}]
+mapperListener.registerContext=Register Context [{0}] for connector [{1}]
+mapperListener.unregisterContext=Unregister Context [{0}] for connector [{1}]
+mapperListener.registerWrapper=Register Wrapper [{0}] in Context [{1}] for connector [{2}]
+mapperListener.unregisterWrapper=Unregister Wrapper [{0}] in Context [{1}] for connector [{2}]
+mapperListener.addMBeanListenerFail=Failed to add MBean notification listener for connector [{0}] in domain [{1}]. Adding Hosts, Contexts and Wrappers will not be visible to the connector.
+mapperListener.removeMBeanListenerFail=Failed to remove MBean notification listener for connector [{0}] in domain [{1}]. This may result in a memory leak.
+mapperListener.lifecycleListenerFail=Failed to add Lifecycle listener to object [{0}]. Changes in the object state may not be correctly reflected in the mapper for connector [{1}] in domain [{2}].
+
+inputBuffer.streamClosed=Stream closed
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/connector/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/LocalStrings_es.properties
new file mode 100644
index 0000000..9d2af17
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/LocalStrings_es.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.
+#
+# CoyoteConnector
+coyoteConnector.cannotRegisterProtocol = No puedo registrar MBean para el Protocolo
+coyoteConnector.protocolHandlerDestroyFailed = Fall\u00F3 la destrucci\u00F3n del manejador de protocolo\: {0}
+coyoteConnector.protocolHandlerInitializationFailed = Fall\u00F3 la inicializaci\u00F3n del manejador de protocolo\: {0}
+coyoteConnector.protocolHandlerInstantiationFailed = Fall\u00F3 la instanciaci\u00F3n del manejador de protocolo\: {0}
+coyoteConnector.protocolHandlerStartFailed = Fall\u00F3 el arranque del manejador de protocolo\: {0}
+coyoteConnector.protocolRegistrationFailed = Fall\u00F3 el registro de JMX
+coyoteConnector.protocolHandlerPauseFailed = Ha fallado la pausa del manejador de protocolo
+coyoteConnector.protocolHandlerResumeFailed = Ha fallado el rearranque del manejador de protocolo
+coyoteConnector.MapperRegistration = Mapeador de registro\: {0}
+coyoteConnector.protocolUnregistrationFailed = Ha fallado la parada del manejador de protocolo
+#
+# CoyoteAdapter
+coyoteAdapter.service = Ha tenido lugar una excepci\u00F3n o error en el contenedor durante el procesamiento del requerimiento
+coyoteAdapter.read = El servlet no ley\u00F3 todos los bytes disponibles durante el procesamiento del evento de lectura
+#
+# CoyoteResponse
+coyoteResponse.getOutputStream.ise = getWriter() ya ha sido llamado para esta respuesta
+coyoteResponse.getWriter.ise = getOutputStream() ya ha sido llamado para esta respuesta
+coyoteResponse.resetBuffer.ise = No puedo limpiar el b\u00FAfer despu\u00E9s de que la repuesta ha sido llevada a cabo
+coyoteResponse.sendError.ise = No puedo llamar a sendError() tras llevar a cabo la respuesta
+coyoteResponse.sendRedirect.ise = No puedo llamar a sendRedirect() tras llevar a cabo la respuesta
+coyoteResponse.setBufferSize.ise = No puedo cambiar la medida del b\u00FAfer tras escribir los datos
+#
+# CoyoteRequest
+coyoteRequest.getInputStream.ise = getReader() ya ha sido llamado para este requerimiento
+coyoteRequest.getReader.ise = getInputStream() ya ha sido llamado para este requerimiento
+coyoteRequest.sessionCreateCommitted = No puedo crear una sesi\u00F3n despu\u00E9s de llevar a cabo la respueta
+coyoteRequest.setAttribute.namenull = No pudeo llamar a setAttribute con un nombre nulo
+coyoteRequest.listenerStart = Excepci\u00F3n enviando evento inicializado de contexto a instancia de escuchador de clase {0}
+coyoteRequest.listenerStop = Excepci\u00F3n enviando evento destru\u00EDdo de contexto a instancia de escuchador de clase {0}
+coyoteRequest.attributeEvent = Excepci\u00F3n lanzada mediante el escuchador de eventos de atributos
+coyoteRequest.parseParameters = Excepci\u00F3n lanzada al procesar par\u00E1metros POST
+coyoteRequest.postTooLarge = No se analizaron los par\u00E1metros porque la medida de los datos enviados era demasiado grande. Usa el atributo maxPostSize del conector para resolver esto en caso de que la aplicaci\u00F3n debiera de aceptar POSTs m\u00E1s grandes.
+requestFacade.nullRequest = El objeto de requerimiento ha sido reciclado y ya no est\u00E1 asociado con esta fachada
+responseFacade.nullResponse = El objeto de respuesta ha sido reciclado y ya no est\u00E1 asociado con esta fachada
+cometEvent.nullRequest = El objeto de evento ha sido reciclado y ya no est\u00E1 asociado con este requerimiento
+mapperListener.unknownDefaultHost = M\u00E1quina por defecto desconocida\: {0}
+mapperListener.registerHost = Registrar m\u00E1quina {0} en dominio {1}
+mapperListener.unregisterHost = Desregistrar m\u00E1quina {0} en dominio {1}
+#
+# MapperListener
+mapperListener.registerContext = Registrar Contexto {0}
+mapperListener.unregisterContext = Desregistrar Contexto {0}
+mapperListener.registerWrapper = Registrar Arropador (Wrapper) {0} en Contexto {1}
+inputBuffer.streamClosed = Flujo cerrado
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/connector/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/LocalStrings_fr.properties
new file mode 100644
index 0000000..93d3b35
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/LocalStrings_fr.properties
@@ -0,0 +1,70 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+#
+# CoyoteConnector
+#
+
+coyoteConnector.cannotRegisterProtocol=Impossible d''enregistrer le MBean pour le Protocol
+coyoteConnector.protocolHandlerDestroyFailed=La destruction du gestionnaire de protocole a \u00e9chou\u00e9: {0}
+coyoteConnector.protocolHandlerInitializationFailed=L''initialisation du gestionnaire de protocole a \u00e9chou\u00e9: {0}
+coyoteConnector.protocolHandlerInstantiationFailed=L''instantiation du gestionnaire de protocole a \u00e9chou\u00e9: {0}
+coyoteConnector.protocolHandlerStartFailed=Le d\u00e9marrage du gestionnaire de protocole a \u00e9chou\u00e9: {0}
+coyoteConnector.protocolRegistrationFailed=L''enregistrement du protocol JMX a \u00e9chou\u00e9
+coyoteConnector.protocolHandlerPauseFailed=La suspension du gestionnaire de protocole a \u00e9chou\u00e9e
+coyoteConnector.protocolHandlerResumeFailed=Le red\u00e9marrage du gestionnaire de protocole a \u00e9chou\u00e9
+
+#
+# CoyoteAdapter
+#
+
+coyoteAdapter.service=Une exception ou une erreur s''est produite dans le conteneur durant le traitement de la requ\u00eate
+
+#
+# CoyoteResponse
+#
+
+coyoteResponse.getOutputStream.ise="getWriter()" a d\u00e9j\u00e0 \u00e9t\u00e9 appel\u00e9 pour cette r\u00e9ponse
+coyoteResponse.getWriter.ise="getOutputStream()" a d\u00e9j\u00e0 \u00e9t\u00e9 appel\u00e9 pour cette r\u00e9ponse
+coyoteResponse.resetBuffer.ise=Impossible de remettre \u00e0 z\u00e9ro le tampon apr\u00e8s que la r\u00e9ponse ait \u00e9t\u00e9 envoy\u00e9e
+coyoteResponse.sendError.ise=Impossible d''appeler "sendError()" apr\u00e8s que la r\u00e9ponse ait \u00e9t\u00e9 envoy\u00e9e
+coyoteResponse.sendRedirect.ise=Impossible d''appeler "sendRedirect()" apr\u00e8s que la r\u00e9ponse ait \u00e9t\u00e9 envoy\u00e9e
+coyoteResponse.setBufferSize.ise=Impossible de changer la taille du tampon apr\u00e8s que les donn\u00e9es aient \u00e9t\u00e9 \u00e9crites
+
+#
+# CoyoteRequest
+#
+
+coyoteRequest.getInputStream.ise="getReader()" a d\u00e9j\u00e0 \u00e9t\u00e9 appel\u00e9 pour cette requ\u00eate
+coyoteRequest.getReader.ise="getInputStream()" a d\u00e9j\u00e0 \u00e9t\u00e9 appel\u00e9 pour cette requ\u00eate
+coyoteRequest.sessionCreateCommitted=Impossible de cr\u00e9er une session apr\u00e8s que la r\u00e9ponse ait \u00e9t\u00e9 envoy\u00e9e
+coyoteRequest.setAttribute.namenull=Impossible d''appeler "setAttribute" avec un nom nul
+coyoteRequest.listenerStart=Une exception s''est produite lors de l''envoi de l''\u00e9v\u00e8nement contexte initialis\u00e9 \u00e0 l''instance de classe d''\u00e9coute {0}
+coyoteRequest.listenerStop=Une exception s''est produite lors de l''envoi de l''\u00e9v\u00e8nement contexte d\u00e9truit \u00e0 l''instance de classe d''\u00e9coute {0}
+coyoteRequest.attributeEvent=Une exception a \u00e9t\u00e9 lanc\u00e9e par l''instance d''\u00e9coute pour l''\u00e9v\u00e8nement attributs (attributes)
+coyoteRequest.postTooLarge=Les param\u00e8tres n''ont pas \u00e9t\u00e9 \u00e9valu\u00e9s car la taille des donn\u00e9es post\u00e9es est trop important. Utilisez l''attribut maxPostSize du connecteur pour corriger ce probl\u00e8me si votre application doit accepter des POSTs importants.
+
+
+#
+# MapperListener
+#
+
+mapperListener.registerContext=Enregistrement du contexte {0}
+mapperListener.unregisterContext=D\u00e9senregistrement du contexte {0}
+mapperListener.registerWrapper=Enregistrement de l''enrobeur (wrapper) {0} dans le contexte {1}
+
+
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/connector/LocalStrings_ja.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/LocalStrings_ja.properties
new file mode 100644
index 0000000..30c7a75
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/LocalStrings_ja.properties
@@ -0,0 +1,70 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+#
+# CoyoteConnector
+#
+
+coyoteConnector.cannotRegisterProtocol=\u305d\u306e\u30d7\u30ed\u30c8\u30b3\u30eb\u306bMBean\u3092\u767b\u9332\u3067\u304d\u307e\u305b\u3093
+coyoteConnector.protocolHandlerDestroyFailed=\u30d7\u30ed\u30c8\u30b3\u30eb\u30cf\u30f3\u30c9\u30e9\u306e\u5ec3\u68c4\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {0}
+coyoteConnector.protocolHandlerInitializationFailed=\u30d7\u30ed\u30c8\u30b3\u30eb\u30cf\u30f3\u30c9\u30e9\u306e\u521d\u671f\u5316\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {0}
+coyoteConnector.protocolHandlerInstantiationFailed=\u30d7\u30ed\u30c8\u30b3\u30eb\u30cf\u30f3\u30c9\u30e9\u306e\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u5316\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {0}
+coyoteConnector.protocolHandlerStartFailed=\u30d7\u30ed\u30c8\u30b3\u30eb\u30cf\u30f3\u30c9\u30e9\u306e\u8d77\u52d5\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {0}
+coyoteConnector.protocolRegistrationFailed=\u30d7\u30ed\u30c8\u30b3\u30ebJMX\u306e\u767b\u9332\u306b\u5931\u6557\u3057\u307e\u3057\u305f
+coyoteConnector.protocolHandlerPauseFailed=\u30d7\u30ed\u30c8\u30b3\u30eb\u30cf\u30f3\u30c9\u30e9\u306e\u4e00\u6642\u505c\u6b62\u306b\u5931\u6557\u3057\u307e\u3057\u305f
+coyoteConnector.protocolHandlerResumeFailed=\u30d7\u30ed\u30c8\u30b3\u30eb\u30cf\u30f3\u30c9\u30e9\u306e\u518d\u958b\u306b\u5931\u6557\u3057\u307e\u3057\u305f
+
+#
+# CoyoteAdapter
+#
+
+coyoteAdapter.service=\u30ea\u30af\u30a8\u30b9\u30c8\u306e\u51e6\u7406\u4e2d\u306b\u30b3\u30cd\u30af\u30bf\u3067\u4f8b\u5916\u307e\u305f\u306f\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
+
+#
+# CoyoteResponse
+#
+
+coyoteResponse.getOutputStream.ise=getWriter()\u306f\u3053\u306e\u30ec\u30b9\u30dd\u30f3\u30b9\u306b\u5bfe\u3057\u3066\u65e2\u306b\u547c\u3073\u51fa\u3055\u308c\u3066\u3044\u307e\u3059
+coyoteResponse.getWriter.ise=getOutputStream()\u306f\u3053\u306e\u30ec\u30b9\u30dd\u30f3\u30b9\u306b\u5bfe\u3057\u3066\u65e2\u306b\u547c\u3073\u51fa\u3055\u308c\u3066\u3044\u307e\u3059
+coyoteResponse.resetBuffer.ise=\u30ec\u30b9\u30dd\u30f3\u30b9\u304c\u30b3\u30df\u30c3\u30c8\u3055\u308c\u305f\u5f8c\u3067\u30d0\u30c3\u30d5\u30a1\u3092\u30ea\u30bb\u30c3\u30c8\u3059\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093
+coyoteResponse.sendError.ise=\u30ec\u30b9\u30dd\u30f3\u30b9\u304c\u30b3\u30df\u30c3\u30c8\u3055\u308c\u305f\u5f8c\u3067sendError()\u3092\u547c\u3073\u51fa\u3059\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093
+coyoteResponse.sendRedirect.ise=\u30ec\u30b9\u30dd\u30f3\u30b9\u304c\u30b3\u30df\u30c3\u30c8\u3055\u308c\u305f\u5f8c\u3067sendRedirect()\u3092\u547c\u3073\u51fa\u3059\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093
+coyoteResponse.setBufferSize.ise=\u30c7\u30fc\u30bf\u304c\u65e2\u306b\u66f8\u304d\u8fbc\u307e\u308c\u305f\u5f8c\u3067\u30d0\u30c3\u30d5\u30a1\u30b5\u30a4\u30ba\u3092\u5909\u66f4\u3059\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093
+
+#
+# CoyoteRequest
+#
+
+coyoteRequest.getInputStream.ise=getReader()\u306f\u3053\u306e\u30ea\u30af\u30a8\u30b9\u30c8\u306b\u5bfe\u3057\u3066\u65e2\u306b\u547c\u3073\u51fa\u3055\u308c\u3066\u3044\u307e\u3059
+coyoteRequest.getReader.ise=getInputStream()\u306f\u3053\u306e\u30ea\u30af\u30a8\u30b9\u30c8\u306b\u5bfe\u3057\u3066\u65e2\u306b\u547c\u3073\u51fa\u3055\u308c\u3066\u3044\u307e\u3059
+coyoteRequest.sessionCreateCommitted=\u30ec\u30b9\u30dd\u30f3\u30b9\u3092\u30b3\u30df\u30c3\u30c8\u3057\u305f\u5f8c\u3067\u30bb\u30c3\u30b7\u30e7\u30f3\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093
+coyoteRequest.setAttribute.namenull=setAttribute\u3092\u540d\u524d\u3092\u6307\u5b9a\u305b\u305a\u306b\u547c\u3073\u51fa\u3059\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093
+coyoteRequest.listenerStart=\u30af\u30e9\u30b9 {0} \u306e\u30ea\u30b9\u30ca\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u306b\u521d\u671f\u5316\u30a4\u30d9\u30f3\u30c8\u3092\u9001\u4fe1\u4e2d\u306b\u4f8b\u5916\u304c\u6295\u3052\u3089\u308c\u307e\u3057\u305f
+coyoteRequest.listenerStop=\u30af\u30e9\u30b9 {0} \u306e\u30ea\u30b9\u30ca\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u306b\u5ec3\u68c4\u30a4\u30d9\u30f3\u30c8\u3092\u9001\u4fe1\u4e2d\u306b\u4f8b\u5916\u304c\u6295\u3052\u3089\u308c\u307e\u3057\u305f
+coyoteRequest.attributeEvent=\u5c5e\u6027\u30a4\u30d9\u30f3\u30c8\u30ea\u30b9\u30ca\u306b\u3088\u3063\u3066\u4f8b\u5916\u304c\u6295\u3052\u3089\u308c\u307e\u3057\u305f
+coyoteRequest.postTooLarge=POST\u3055\u308c\u305f\u30c7\u30fc\u30bf\u304c\u5927\u304d\u3059\u304e\u305f\u306e\u3067\u3001\u30d1\u30e9\u30e1\u30fc\u30bf\u304c\u69cb\u6587\u89e3\u6790\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u305d\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u304c\u5de8\u5927\u306aPOST\u3092\u53d7\u3051\u4ed8\u3051\u306d\u3070\u306a\u3089\u306a\u3044\u5834\u5408\u306b\u306f\u3001\u3053\u308c\u3092\u89e3\u6c7a\u3059\u308b\u305f\u3081\u306b\u30b3\u30cd\u30af\u30bf\u306emaxPostSize\u5c5e\u6027\u3092\u4f7f\u7528\u3057\u3066\u304f\u3060\u3055\u3044\u3002
+
+
+#
+# MapperListener
+#
+
+mapperListener.registerContext=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8 {0}\u3000\u3092\u767b\u9332\u3057\u307e\u3059
+mapperListener.unregisterContext=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8 {0} \u306e\u767b\u9332\u3092\u62b9\u6d88\u3057\u307e\u3059
+mapperListener.registerWrapper=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8 {1} \u306b\u30e9\u30c3\u30d1 {0} \u3092\u767b\u9332\u3057\u307e\u3059
+
+
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/connector/MapperListener.java b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/MapperListener.java
new file mode 100644
index 0000000..a716a69
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/MapperListener.java
@@ -0,0 +1,479 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.connector;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.ContainerEvent;
+import org.apache.catalina.ContainerListener;
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Host;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.util.LifecycleMBeanBase;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.http.mapper.Mapper;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Mapper listener.
+ *
+ * @author Remy Maucherat
+ * @author Costin Manolache
+ */
+public class MapperListener extends LifecycleMBeanBase
+        implements ContainerListener, LifecycleListener {
+
+
+    private static final Log log = LogFactory.getLog(MapperListener.class);
+
+
+    // ----------------------------------------------------- Instance Variables
+    /**
+     * Associated mapper.
+     */
+    private Mapper mapper = null;
+    
+    /**
+     * Associated connector
+     */
+    private Connector connector = null;
+
+
+    /**
+     * The string manager for this package.
+     */
+    private static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    /**
+     * The domain (effectively the engine) this mapper is associated with
+     */
+    private String domain = null;
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Create mapper listener.
+     */
+    public MapperListener(Mapper mapper, Connector connector) {
+        this.mapper = mapper;
+        this.connector = connector;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+    public String getConnectorName() {
+        return this.connector.toString();
+    }
+
+    
+    // ------------------------------------------------------- Lifecycle Methods
+
+    @Override
+    public void startInternal() throws LifecycleException {
+
+        setState(LifecycleState.STARTING);
+
+        // Find any components that have already been initialized since the
+        // MBean listener won't be notified as those components will have
+        // already registered their MBeans
+        findDefaultHost();
+        
+        Engine engine = (Engine) connector.getService().getContainer();
+        addListeners(engine);
+        
+        Container[] conHosts = engine.findChildren();
+        for (Container conHost : conHosts) {
+            Host host = (Host) conHost;
+            if (!LifecycleState.NEW.equals(host.getState())) {
+                // Registering the host will register the context and wrappers
+                registerHost(host);
+            }
+        }
+    }
+        
+
+    @Override
+    public void stopInternal() throws LifecycleException {
+        setState(LifecycleState.STOPPING);
+    }
+
+
+    @Override
+    protected String getDomainInternal() {
+        // Should be the same as the connector
+        return connector.getDomainInternal();
+    }
+
+
+    @Override
+    protected String getObjectNameKeyProperties() {
+        // Same as connector but Mapper rather than Connector
+        return connector.createObjectNameKeyProperties("Mapper");
+    }
+
+    // --------------------------------------------- Container Listener methods
+
+    @Override
+    public void containerEvent(ContainerEvent event) {
+
+        if (event.getType() == Container.ADD_CHILD_EVENT) {
+            Container child = (Container) event.getData();
+            addListeners(child);
+            // If child is started then it is too late for life-cycle listener
+            // to register the child so register it here
+            if (child.getState().isAvailable()) {
+                if (child instanceof Host) {
+                    registerHost((Host) child);
+                } else if (child instanceof Context) {
+                    registerContext((Context) child);
+                } else if (child instanceof Wrapper) {
+                    registerWrapper((Wrapper) child);
+                }
+            }
+        } else if (event.getType() == Container.REMOVE_CHILD_EVENT) {
+            Container child = (Container) event.getData();
+            removeListeners(child);
+            // No need to unregister - life-cycle listener will handle this when
+            // the child stops
+        } else if (event.getType() == Host.ADD_ALIAS_EVENT) {
+            // Handle dynamically adding host aliases
+            mapper.addHostAlias(((Host) event.getSource()).getName(),
+                    event.getData().toString());
+        } else if (event.getType() == Host.REMOVE_ALIAS_EVENT) {
+            // Handle dynamically removing host aliases
+            mapper.removeHostAlias(event.getData().toString());
+        } else if (event.getType() == Wrapper.ADD_MAPPING_EVENT) {
+            // Handle dynamically adding wrappers
+            Wrapper wrapper = (Wrapper) event.getSource();
+            Context context = (Context) wrapper.getParent();
+            String contextPath = context.getPath();
+            if ("/".equals(contextPath)) {
+                contextPath = "";
+            }
+            String version = ((Context) wrapper.getParent()).getWebappVersion();
+            String hostName = context.getParent().getName();
+            String wrapperName = wrapper.getName();
+            String mapping = (String) event.getData();
+            boolean jspWildCard = ("jsp".equals(wrapperName)
+                    && mapping.endsWith("/*"));
+            mapper.addWrapper(hostName, contextPath, version, mapping, wrapper,
+                    jspWildCard, context.isResourceOnlyServlet(wrapperName));
+        } else if (event.getType() == Wrapper.REMOVE_MAPPING_EVENT) {
+            // Handle dynamically removing wrappers
+            Wrapper wrapper = (Wrapper) event.getSource();
+
+            String contextPath = ((Context) wrapper.getParent()).getPath();
+            if ("/".equals(contextPath)) {
+                contextPath = "";
+            }
+            String version = ((Context) wrapper.getParent()).getWebappVersion();
+            String hostName = wrapper.getParent().getParent().getName();
+
+            String mapping = (String) event.getData();
+            
+            mapper.removeWrapper(hostName, contextPath, version, mapping);
+        } else if (event.getType() == Context.ADD_WELCOME_FILE_EVENT) {
+            // Handle dynamically adding welcome files
+            Context context = (Context) event.getSource();
+            
+            String hostName = context.getParent().getName();
+
+            String contextPath = context.getPath();
+            if ("/".equals(contextPath)) {
+                contextPath = "";
+            }
+            
+            String welcomeFile = (String) event.getData();
+            
+            mapper.addWelcomeFile(hostName, contextPath,
+                    context.getWebappVersion(), welcomeFile);
+        } else if (event.getType() == Context.REMOVE_WELCOME_FILE_EVENT) {
+            // Handle dynamically removing welcome files
+            Context context = (Context) event.getSource();
+            
+            String hostName = context.getParent().getName();
+
+            String contextPath = context.getPath();
+            if ("/".equals(contextPath)) {
+                contextPath = "";
+            }
+            
+            String welcomeFile = (String) event.getData();
+            
+            mapper.removeWelcomeFile(hostName, contextPath,
+                    context.getWebappVersion(), welcomeFile);
+        } else if (event.getType() == Context.CLEAR_WELCOME_FILES_EVENT) {
+            // Handle dynamically clearing welcome files
+            Context context = (Context) event.getSource();
+            
+            String hostName = context.getParent().getName();
+
+            String contextPath = context.getPath();
+            if ("/".equals(contextPath)) {
+                contextPath = "";
+            }
+            
+            mapper.clearWelcomeFiles(hostName, contextPath,
+                    context.getWebappVersion());
+        }
+    }
+
+    
+    // ------------------------------------------------------ Protected Methods
+
+    private void findDefaultHost() {
+
+        Engine engine = (Engine) connector.getService().getContainer();
+        String defaultHost = engine.getDefaultHost();
+
+        boolean found = false;
+
+        if (defaultHost != null && defaultHost.length() >0) {
+            Container[] containers = engine.findChildren();
+            
+            for (Container container : containers) {
+                Host host = (Host) container;
+                if (defaultHost.equalsIgnoreCase(host.getName())) {
+                    found = true;
+                    break;
+                }
+                
+                String[] aliases = host.findAliases();
+                for (String alias : aliases) {
+                    if (defaultHost.equalsIgnoreCase(alias)) {
+                        found = true;
+                        break;
+                    }
+                }
+            }
+        }
+
+        if(found) {
+            mapper.setDefaultHostName(defaultHost);
+        } else {
+            log.warn(sm.getString("mapperListener.unknownDefaultHost",
+                    defaultHost, connector));
+        }
+    }
+
+    
+    /**
+     * Register host.
+     */
+    private void registerHost(Host host) {
+        
+        String[] aliases = host.findAliases();
+        mapper.addHost(host.getName(), aliases, host);
+        
+        for (Container container : host.findChildren()) {
+            if (container.getState().isAvailable()) {
+                registerContext((Context) container);
+            }
+        }
+        if(log.isDebugEnabled()) {
+            log.debug(sm.getString("mapperListener.registerHost",
+                    host.getName(), domain, connector));
+        }
+    }
+
+
+    /**
+     * Unregister host.
+     */
+    private void unregisterHost(Host host) {
+
+        String hostname = host.getName();
+        
+        mapper.removeHost(hostname);
+
+        if(log.isDebugEnabled())
+            log.debug(sm.getString("mapperListener.unregisterHost", hostname,
+                    domain, connector));
+    }
+
+    
+    /**
+     * Unregister wrapper.
+     */
+    private void unregisterWrapper(Wrapper wrapper) {
+
+        String contextPath = ((Context) wrapper.getParent()).getPath();
+        String wrapperName = wrapper.getName();
+
+        if ("/".equals(contextPath)) {
+            contextPath = "";
+        }
+        String version = ((Context) wrapper.getParent()).getWebappVersion();
+        String hostName = wrapper.getParent().getParent().getName();
+
+        String[] mappings = wrapper.findMappings();
+        
+        for (String mapping : mappings) {
+            mapper.removeWrapper(hostName, contextPath, version,  mapping);
+        }
+        
+        if(log.isDebugEnabled()) {
+            log.debug(sm.getString("mapperListener.unregisterWrapper",
+                    wrapperName, contextPath, connector));
+        }
+    }
+
+    
+    /**
+     * Register context.
+     */
+    private void registerContext(Context context) {
+
+        String contextPath = context.getPath();
+        if ("/".equals(contextPath)) {
+            contextPath = "";
+        }
+        Container host = context.getParent();
+        
+        javax.naming.Context resources = context.getResources();
+        String[] welcomeFiles = context.findWelcomeFiles();
+
+        mapper.addContextVersion(host.getName(), host, contextPath,
+                context.getWebappVersion(), context, welcomeFiles, resources);
+
+        for (Container container : context.findChildren()) {
+            registerWrapper((Wrapper) container);
+        }
+
+        if(log.isDebugEnabled()) {
+            log.debug(sm.getString("mapperListener.registerContext",
+                    contextPath, connector));
+        }
+    }
+
+
+    /**
+     * Unregister context.
+     */
+    private void unregisterContext(Context context) {
+
+        // Don't un-map a context that is paused
+        if (context.getPaused()){
+            return;
+        }
+
+        String contextPath = context.getPath();
+        if ("/".equals(contextPath)) {
+            contextPath = "";
+        }
+        String hostName = context.getParent().getName();
+
+        if(log.isDebugEnabled())
+            log.debug(sm.getString("mapperListener.unregisterContext",
+                    contextPath, connector));
+
+        mapper.removeContextVersion(hostName, contextPath,
+                context.getWebappVersion());
+    }
+
+
+    /**
+     * Register wrapper.
+     */
+    private void registerWrapper(Wrapper wrapper) {
+
+        String wrapperName = wrapper.getName();
+        Context context = (Context) wrapper.getParent();
+        String contextPath = context.getPath();
+        if ("/".equals(contextPath)) {
+            contextPath = "";
+        }
+        String version = ((Context) wrapper.getParent()).getWebappVersion();
+        String hostName = context.getParent().getName();
+        
+        String[] mappings = wrapper.findMappings();
+
+        for (String mapping : mappings) {
+            boolean jspWildCard = (wrapperName.equals("jsp")
+                                   && mapping.endsWith("/*"));
+            mapper.addWrapper(hostName, contextPath, version, mapping, wrapper,
+                              jspWildCard,
+                              context.isResourceOnlyServlet(wrapperName));
+        }
+
+        if(log.isDebugEnabled()) {
+            log.debug(sm.getString("mapperListener.registerWrapper",
+                    wrapperName, contextPath, connector));
+        }
+    }
+
+    @Override
+    public void lifecycleEvent(LifecycleEvent event) {
+        if (event.getType() == Lifecycle.AFTER_START_EVENT) {
+            Object obj = event.getSource();
+            if (obj instanceof Wrapper) {
+                registerWrapper((Wrapper) obj);
+            } else if (obj instanceof Context) {
+                registerContext((Context) obj);
+            } else if (obj instanceof Host) {
+                registerHost((Host) obj);
+            }
+        } else if (event.getType() == Lifecycle.BEFORE_STOP_EVENT) {
+            Object obj = event.getSource();
+            if (obj instanceof Wrapper) {
+                unregisterWrapper((Wrapper) obj);
+            } else if (obj instanceof Context) {
+                unregisterContext((Context) obj);
+            } else if (obj instanceof Host) {
+                unregisterHost((Host) obj);
+            }
+        }
+    }
+
+
+    /**
+     * Add this mapper to the container and all child containers
+     * 
+     * @param container
+     */
+    private void addListeners(Container container) {
+        container.addContainerListener(this);
+        container.addLifecycleListener(this);
+        for (Container child : container.findChildren()) {
+            addListeners(child);
+        }
+    }
+
+
+    /**
+     * Remove this mapper from the container and all child containers
+     * 
+     * @param container
+     */
+    private void removeListeners(Container container) {
+        container.removeContainerListener(this);
+        container.removeLifecycleListener(this);
+        for (Container child : container.findChildren()) {
+            removeListeners(child);
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/connector/OutputBuffer.java b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/OutputBuffer.java
new file mode 100644
index 0000000..d58bfd5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/OutputBuffer.java
@@ -0,0 +1,586 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.connector;
+
+
+import java.io.IOException;
+import java.io.Writer;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.HashMap;
+
+import org.apache.catalina.Globals;
+import org.apache.coyote.ActionCode;
+import org.apache.coyote.Response;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.C2BConverter;
+
+
+/**
+ * The buffer used by Tomcat response. This is a derivative of the Tomcat 3.3
+ * OutputBuffer, with the removal of some of the state handling (which in 
+ * Coyote is mostly the Processor's responsibility).
+ *
+ * @author Costin Manolache
+ * @author Remy Maucherat
+ */
+public class OutputBuffer extends Writer
+    implements ByteChunk.ByteOutputChannel {
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    public static final String DEFAULT_ENCODING = 
+        org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING;
+    public static final int DEFAULT_BUFFER_SIZE = 8*1024;
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The byte buffer.
+     */
+    private ByteChunk bb;
+
+
+    /**
+     * State of the output buffer.
+     */
+    private boolean initial = true;
+
+
+    /**
+     * Number of bytes written.
+     */
+    private long bytesWritten = 0;
+
+
+    /**
+     * Number of chars written.
+     */
+    private long charsWritten = 0;
+
+
+    /**
+     * Flag which indicates if the output buffer is closed.
+     */
+    private boolean closed = false;
+
+
+    /**
+     * Do a flush on the next operation.
+     */
+    private boolean doFlush = false;
+
+
+    /**
+     * Byte chunk used to output bytes.
+     */
+    private ByteChunk outputChunk = new ByteChunk();
+
+
+    /**
+     * Encoding to use.
+     */
+    private String enc;
+
+
+    /**
+     * Encoder is set.
+     */
+    private boolean gotEnc = false;
+
+
+    /**
+     * List of encoders.
+     */
+    protected HashMap<String, C2BConverter> encoders =
+        new HashMap<String, C2BConverter>();
+
+
+    /**
+     * Current char to byte converter.
+     */
+    protected C2BConverter conv;
+
+
+    /**
+     * Associated Coyote response.
+     */
+    private Response coyoteResponse;
+
+
+    /**
+     * Suspended flag. All output bytes will be swallowed if this is true.
+     */
+    private boolean suspended = false;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Default constructor. Allocate the buffer with the default buffer size.
+     */
+    public OutputBuffer() {
+
+        this(DEFAULT_BUFFER_SIZE);
+
+    }
+
+
+    /**
+     * Alternate constructor which allows specifying the initial buffer size.
+     * 
+     * @param size Buffer size to use
+     */
+    public OutputBuffer(int size) {
+
+        bb = new ByteChunk(size);
+        bb.setLimit(size);
+        bb.setByteOutputChannel(this);
+
+    }
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Associated Coyote response.
+     * 
+     * @param coyoteResponse Associated Coyote response
+     */
+    public void setResponse(Response coyoteResponse) {
+        this.coyoteResponse = coyoteResponse;
+    }
+
+
+    /**
+     * Get associated Coyote response.
+     * 
+     * @return the associated Coyote response
+     */
+    public Response getResponse() {
+        return this.coyoteResponse;
+    }
+
+
+    /**
+     * Is the response output suspended ?
+     * 
+     * @return suspended flag value
+     */
+    public boolean isSuspended() {
+        return this.suspended;
+    }
+
+
+    /**
+     * Set the suspended flag.
+     * 
+     * @param suspended New suspended flag value
+     */
+    public void setSuspended(boolean suspended) {
+        this.suspended = suspended;
+    }
+
+
+    /**
+     * Is the response output closed ?
+     * 
+     * @return closed flag value
+     */
+    public boolean isClosed() {
+        return this.closed;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Recycle the output buffer.
+     */
+    public void recycle() {
+        
+        initial = true;
+        bytesWritten = 0;
+        charsWritten = 0;
+        
+        bb.recycle(); 
+        closed = false;
+        suspended = false;
+        
+        if (conv!= null) {
+            conv.recycle();
+        }
+        
+        gotEnc = false;
+        enc = null;
+        
+    }
+
+
+    /**
+     * Clear cached encoders (to save memory for Comet requests).
+     */
+    public void clearEncoders() {
+        encoders.clear();
+    }
+    
+    
+    /**
+     * Close the output buffer. This tries to calculate the response size if 
+     * the response has not been committed yet.
+     * 
+     * @throws IOException An underlying IOException occurred
+     */
+    @Override
+    public void close()
+        throws IOException {
+
+        if (closed)
+            return;
+        if (suspended)
+            return;
+
+        if ((!coyoteResponse.isCommitted()) 
+            && (coyoteResponse.getContentLengthLong() == -1)) {
+            // If this didn't cause a commit of the response, the final content
+            // length can be calculated
+            if (!coyoteResponse.isCommitted()) {
+                coyoteResponse.setContentLength(bb.getLength());
+            }
+        }
+
+        doFlush(false);
+        closed = true;
+
+        // The request should have been completely read by the time the response
+        // is closed. Further reads of the input a) are pointless and b) really
+        // confuse AJP (bug 50189) so close the input buffer to prevent them.
+        Request req = (Request) coyoteResponse.getRequest().getNote(
+                CoyoteAdapter.ADAPTER_NOTES);
+        req.inputBuffer.close();
+        
+        coyoteResponse.finish();
+
+    }
+
+
+    /**
+     * Flush bytes or chars contained in the buffer.
+     * 
+     * @throws IOException An underlying IOException occurred
+     */
+    @Override
+    public void flush()
+        throws IOException {
+        doFlush(true);
+    }
+
+
+    /**
+     * Flush bytes or chars contained in the buffer.
+     * 
+     * @throws IOException An underlying IOException occurred
+     */
+    protected void doFlush(boolean realFlush)
+        throws IOException {
+
+        if (suspended)
+            return;
+
+        doFlush = true;
+        if (initial) {
+            coyoteResponse.sendHeaders();
+            initial = false;
+        }
+        if (bb.getLength() > 0) {
+            bb.flushBuffer();
+        }
+        doFlush = false;
+
+        if (realFlush) {
+            coyoteResponse.action(ActionCode.CLIENT_FLUSH, 
+                                  coyoteResponse);
+            // If some exception occurred earlier, or if some IOE occurred
+            // here, notify the servlet with an IOE
+            if (coyoteResponse.isExceptionPresent()) {
+                throw new ClientAbortException
+                    (coyoteResponse.getErrorException());
+            }
+        }
+
+    }
+
+
+    // ------------------------------------------------- Bytes Handling Methods
+
+
+    /** 
+     * Sends the buffer data to the client output, checking the
+     * state of Response and calling the right interceptors.
+     * 
+     * @param buf Byte buffer to be written to the response
+     * @param off Offset
+     * @param cnt Length
+     * 
+     * @throws IOException An underlying IOException occurred
+     */
+    @Override
+    public void realWriteBytes(byte buf[], int off, int cnt)
+            throws IOException {
+
+        if (closed)
+            return;
+        if (coyoteResponse == null)
+            return;
+
+        // If we really have something to write
+        if (cnt > 0) {
+            // real write to the adapter
+            outputChunk.setBytes(buf, off, cnt);
+            try {
+                coyoteResponse.doWrite(outputChunk);
+            } catch (IOException e) {
+                // An IOException on a write is almost always due to
+                // the remote client aborting the request.  Wrap this
+                // so that it can be handled better by the error dispatcher.
+                throw new ClientAbortException(e);
+            }
+        }
+
+    }
+
+
+    public void write(byte b[], int off, int len) throws IOException {
+
+        if (suspended)
+            return;
+
+        writeBytes(b, off, len);
+
+    }
+
+
+    private void writeBytes(byte b[], int off, int len) 
+        throws IOException {
+
+        if (closed)
+            return;
+
+        bb.append(b, off, len);
+        bytesWritten += len;
+
+        // if called from within flush(), then immediately flush
+        // remaining bytes
+        if (doFlush) {
+            bb.flushBuffer();
+        }
+
+    }
+
+
+    public void writeByte(int b)
+        throws IOException {
+
+        if (suspended)
+            return;
+
+        bb.append((byte) b);
+        bytesWritten++;
+
+    }
+
+
+    // ------------------------------------------------- Chars Handling Methods
+
+
+    @Override
+    public void write(int c)
+        throws IOException {
+
+        if (suspended)
+            return;
+
+        conv.convert((char) c);
+        conv.flushBuffer();
+        charsWritten++;
+        
+    }
+
+
+    @Override
+    public void write(char c[])
+        throws IOException {
+
+        if (suspended)
+            return;
+
+        write(c, 0, c.length);
+
+    }
+
+
+    @Override
+    public void write(char c[], int off, int len)
+        throws IOException {
+
+        if (suspended)
+            return;
+
+        conv.convert(c, off, len);
+        conv.flushBuffer();
+        charsWritten += len;
+
+    }
+
+
+    /**
+     * Append a string to the buffer
+     */
+    @Override
+    public void write(String s, int off, int len)
+        throws IOException {
+
+        if (suspended)
+            return;
+
+        charsWritten += len;
+        if (s == null)
+            s = "null";
+        conv.convert(s, off, len);
+        conv.flushBuffer();
+
+    }
+
+
+    @Override
+    public void write(String s)
+        throws IOException {
+
+        if (suspended)
+            return;
+
+        if (s == null)
+            s = "null";
+        conv.convert(s);
+        conv.flushBuffer();
+
+    } 
+
+
+    public void setEncoding(String s) {
+        enc = s;
+    }
+
+
+    public void checkConverter() 
+        throws IOException {
+
+        if (!gotEnc)
+            setConverter();
+
+    }
+
+
+    protected void setConverter() 
+        throws IOException {
+
+        if (coyoteResponse != null)
+            enc = coyoteResponse.getCharacterEncoding();
+
+        gotEnc = true;
+        if (enc == null)
+            enc = DEFAULT_ENCODING;
+        conv = encoders.get(enc);
+        if (conv == null) {
+            
+            if (Globals.IS_SECURITY_ENABLED){
+                try{
+                    conv = AccessController.doPrivileged(
+                            new PrivilegedExceptionAction<C2BConverter>(){
+
+                                @Override
+                                public C2BConverter run() throws IOException{
+                                    return new C2BConverter(bb, enc);
+                                }
+
+                            }
+                    );              
+                }catch(PrivilegedActionException ex){
+                    Exception e = ex.getException();
+                    if (e instanceof IOException)
+                        throw (IOException)e; 
+                }
+            } else {
+                conv = new C2BConverter(bb, enc);
+            }
+            
+            encoders.put(enc, conv);
+
+        }
+    }
+
+    
+    // --------------------  BufferedOutputStream compatibility
+
+
+    public long getContentWritten() {
+        return bytesWritten + charsWritten;
+    }
+    
+    /** 
+     * True if this buffer hasn't been used ( since recycle() ) -
+     * i.e. no chars or bytes have been added to the buffer.  
+     */
+    public boolean isNew() {
+        return (bytesWritten == 0) && (charsWritten == 0);
+    }
+
+
+    public void setBufferSize(int size) {
+        if (size > bb.getLimit()) {// ??????
+            bb.setLimit(size);
+        }
+    }
+
+
+    public void reset() {
+
+        bb.recycle();
+        bytesWritten = 0;
+        charsWritten = 0;
+        gotEnc = false;
+        enc = null;
+        initial = true;
+        
+    }
+
+
+    public int getBufferSize() {
+        return bb.getLimit();
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/connector/Request.java b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/Request.java
new file mode 100644
index 0000000..0abdd5f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/Request.java
@@ -0,0 +1,3120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.connector;
+
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.Principal;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.security.auth.Subject;
+import javax.servlet.AsyncContext;
+import javax.servlet.DispatcherType;
+import javax.servlet.FilterChain;
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestAttributeEvent;
+import javax.servlet.ServletRequestAttributeListener;
+import javax.servlet.ServletResponse;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.Part;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Globals;
+import org.apache.catalina.Host;
+import org.apache.catalina.Manager;
+import org.apache.catalina.Realm;
+import org.apache.catalina.Session;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.core.ApplicationPart;
+import org.apache.catalina.core.ApplicationSessionCookieConfig;
+import org.apache.catalina.core.AsyncContextImpl;
+import org.apache.catalina.deploy.LoginConfig;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.apache.catalina.util.Enumerator;
+import org.apache.catalina.util.ParameterMap;
+import org.apache.catalina.util.StringParser;
+import org.apache.coyote.ActionCode;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.buf.B2CConverter;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.Cookies;
+import org.apache.tomcat.util.http.FastHttpDateFormat;
+import org.apache.tomcat.util.http.Parameters;
+import org.apache.tomcat.util.http.ServerCookie;
+import org.apache.tomcat.util.http.fileupload.FileItem;
+import org.apache.tomcat.util.http.fileupload.FileUploadBase;
+import org.apache.tomcat.util.http.fileupload.FileUploadBase.InvalidContentTypeException;
+import org.apache.tomcat.util.http.fileupload.FileUploadException;
+import org.apache.tomcat.util.http.fileupload.disk.DiskFileItemFactory;
+import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
+import org.apache.tomcat.util.http.mapper.MappingData;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Wrapper object for the Coyote request.
+ *
+ * @author Remy Maucherat
+ * @author Craig R. McClanahan
+ * @version $Id: Request.java,v 1.1 2011/06/28 21:08:19 rherrmann Exp $
+ */
+
+public class Request
+    implements HttpServletRequest {
+
+    private static final Log log = LogFactory.getLog(Request.class);
+    
+    // ----------------------------------------------------------- Constructors
+
+
+    public Request() {
+
+        formats[0].setTimeZone(GMT_ZONE);
+        formats[1].setTimeZone(GMT_ZONE);
+        formats[2].setTimeZone(GMT_ZONE);
+
+    }
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Coyote request.
+     */
+    protected org.apache.coyote.Request coyoteRequest;
+
+    /**
+     * Set the Coyote request.
+     * 
+     * @param coyoteRequest The Coyote request
+     */
+    public void setCoyoteRequest(org.apache.coyote.Request coyoteRequest) {
+        this.coyoteRequest = coyoteRequest;
+        inputBuffer.setRequest(coyoteRequest);
+    }
+
+    /**
+     * Get the Coyote request.
+     */
+    public org.apache.coyote.Request getCoyoteRequest() {
+        return (this.coyoteRequest);
+    }
+
+
+    // ----------------------------------------------------- Variables
+
+
+    protected static final TimeZone GMT_ZONE = TimeZone.getTimeZone("GMT");
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
+     * The set of cookies associated with this Request.
+     */
+    protected Cookie[] cookies = null;
+
+
+    /**
+     * The set of SimpleDateFormat formats to use in getDateHeader().
+     *
+     * Notice that because SimpleDateFormat is not thread-safe, we can't
+     * declare formats[] as a static variable.
+     */
+    protected 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)
+    };
+
+
+    /**
+     * The default Locale if none are specified.
+     */
+    protected static Locale defaultLocale = Locale.getDefault();
+
+
+    /**
+     * The attributes associated with this Request, keyed by attribute name.
+     */
+    protected HashMap<String, Object> attributes =
+        new HashMap<String, Object>();
+
+
+    /**
+     * Flag that indicates if SSL attributes have been parsed to improve
+     * performance for applications (usually frameworks) that make multiple
+     * calls to {@link Request#getAttributeNames()}.
+     */
+    protected boolean sslAttributesParsed = false;
+
+    /**
+     * List of read only attributes for this Request.
+     */
+    private HashMap<String,Object> readOnlyAttributes =
+        new HashMap<String,Object>();
+
+
+    /**
+     * The preferred Locales associated with this Request.
+     */
+    protected ArrayList<Locale> locales = new ArrayList<Locale>();
+
+
+    /**
+     * Internal notes associated with this request by Catalina components
+     * and event listeners.
+     */
+    private transient HashMap<String, Object> notes = new HashMap<String, Object>();
+
+
+    /**
+     * Authentication type.
+     */
+    protected String authType = null;
+
+    
+    /**
+     * Associated event.
+     */
+    protected CometEventImpl event = null;
+    
+
+    /**
+     * Comet state
+     */
+    protected boolean comet = false;
+    
+    
+    /**
+     * The current dispatcher type.
+     */
+    protected DispatcherType internalDispatcherType = null;
+
+
+    /**
+     * The associated input buffer.
+     */
+    protected InputBuffer inputBuffer = new InputBuffer();
+
+
+    /**
+     * ServletInputStream.
+     */
+    protected CoyoteInputStream inputStream = 
+        new CoyoteInputStream(inputBuffer);
+
+
+    /**
+     * Reader.
+     */
+    protected CoyoteReader reader = new CoyoteReader(inputBuffer);
+
+
+    /**
+     * Using stream flag.
+     */
+    protected boolean usingInputStream = false;
+
+
+    /**
+     * Using writer flag.
+     */
+    protected boolean usingReader = false;
+
+
+    /**
+     * User principal.
+     */
+    protected Principal userPrincipal = null;
+
+
+    /**
+     * Session parsed flag.
+     */
+    protected boolean sessionParsed = false;
+
+
+    /**
+     * Request parameters parsed flag.
+     */
+    protected boolean parametersParsed = false;
+
+
+    /**
+     * Cookies parsed flag.
+     */
+    protected boolean cookiesParsed = false;
+
+
+    /**
+     * Secure flag.
+     */
+    protected boolean secure = false;
+
+    
+    /**
+     * The Subject associated with the current AccessControllerContext
+     */
+    protected transient Subject subject = null;
+
+
+    /**
+     * Post data buffer.
+     */
+    protected static int CACHED_POST_LEN = 8192;
+    protected byte[] postData = null;
+
+
+    /**
+     * Hash map used in the getParametersMap method.
+     */
+    protected ParameterMap<String, String[]> parameterMap = new ParameterMap<String, String[]>();
+
+
+    /**
+     * The parts, if any, uploaded with this request.
+     */
+    protected Collection<Part> parts = null;
+    
+    
+    /**
+     * The exception thrown, if any when parsing the parts.
+     */
+    protected Exception partsParseException = null;
+    
+    
+    /**
+     * The currently active session for this request.
+     */
+    protected Session session = null;
+
+
+    /**
+     * The current request dispatcher path.
+     */
+    protected Object requestDispatcherPath = null;
+
+
+    /**
+     * Was the requested session ID received in a cookie?
+     */
+    protected boolean requestedSessionCookie = false;
+
+
+    /**
+     * The requested session ID (if any) for this request.
+     */
+    protected String requestedSessionId = null;
+
+
+    /**
+     * Was the requested session ID received in a URL?
+     */
+    protected boolean requestedSessionURL = false;
+
+
+    /**
+     * Was the requested session ID obtained from the SSL session?
+     */
+    protected boolean requestedSessionSSL = false;
+
+
+    /**
+     * Parse locales.
+     */
+    protected boolean localesParsed = false;
+
+
+    /**
+     * The string parser we will use for parsing request lines.
+     */
+    private StringParser parser = new StringParser();
+
+
+    /**
+     * Local port
+     */
+    protected int localPort = -1;
+
+    /**
+     * Remote address.
+     */
+    protected String remoteAddr = null;
+
+
+    /**
+     * Remote host.
+     */
+    protected String remoteHost = null;
+
+    
+    /**
+     * Remote port
+     */
+    protected int remotePort = -1;
+    
+    /**
+     * Local address
+     */
+    protected String localAddr = null;
+
+    
+    /**
+     * Local address
+     */
+    protected String localName = null;
+    
+    /**
+     * AsyncContext 
+     */
+    protected volatile AsyncContextImpl asyncContext = null;
+    
+    protected Boolean asyncSupported = null;
+    
+    
+    /**
+     * Path parameters
+     */
+    protected Map<String,String> pathParameters = new HashMap<String, String>();
+
+    // --------------------------------------------------------- Public Methods
+
+    
+    protected void addPathParameter(String name, String value) {
+        pathParameters.put(name, value);
+    }
+
+    protected String getPathParameter(String name) {
+        return pathParameters.get(name);
+    }
+
+    public void setAsyncSupported(boolean asyncSupported) {
+        this.asyncSupported = Boolean.valueOf(asyncSupported);
+    }
+
+    /**
+     * Release all object references, and initialize instance variables, in
+     * preparation for reuse of this object.
+     */
+    public void recycle() {
+
+        context = null;
+        wrapper = null;
+
+        internalDispatcherType = null;
+        requestDispatcherPath = null;
+
+        comet = false;
+        if (event != null) {
+            event.clear();
+            event = null;
+        }
+        
+        authType = null;
+        inputBuffer.recycle();
+        usingInputStream = false;
+        usingReader = false;
+        userPrincipal = null;
+        subject = null;
+        sessionParsed = false;
+        parametersParsed = false;
+        parts = null;
+        partsParseException = null;
+        cookiesParsed = false;
+        locales.clear();
+        localesParsed = false;
+        secure = false;
+        remoteAddr = null;
+        remoteHost = null;
+        remotePort = -1;
+        localPort = -1;
+        localAddr = null;
+        localName = null;
+
+        attributes.clear();
+        sslAttributesParsed = false;
+        notes.clear();
+        cookies = null;
+
+        if (session != null) {
+            try {
+                session.endAccess();
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                log.warn(sm.getString("coyoteRequest.sessionEndAccessFail"), t);
+            }
+        }
+        session = null;
+        requestedSessionCookie = false;
+        requestedSessionId = null;
+        requestedSessionURL = false;
+
+        if (Globals.IS_SECURITY_ENABLED || Connector.RECYCLE_FACADES) {
+            parameterMap = new ParameterMap<String, String[]>();
+        } else {
+            parameterMap.setLocked(false);
+            parameterMap.clear();
+        }
+
+        mappingData.recycle();
+
+        if (Globals.IS_SECURITY_ENABLED || Connector.RECYCLE_FACADES) {
+            if (facade != null) {
+                facade.clear();
+                facade = null;
+            }
+            if (inputStream != null) {
+                inputStream.clear();
+                inputStream = null;
+            }
+            if (reader != null) {
+                reader.clear();
+                reader = null;
+            }
+        }
+        
+        asyncSupported = null;
+        if (asyncContext!=null) asyncContext.recycle();
+        asyncContext = null;
+
+        pathParameters.clear();
+    }
+
+    protected boolean isProcessing() {
+        return coyoteRequest.isProcessing();
+    }
+    /**
+     * Clear cached encoders (to save memory for Comet requests).
+     */
+    public void clearEncoders() {
+        inputBuffer.clearEncoders();
+    }
+    
+
+    /**
+     * Clear cached encoders (to save memory for Comet requests).
+     */
+    public boolean read()
+        throws IOException {
+        return (inputBuffer.realReadBytes(null, 0, 0) > 0);
+    }
+    
+
+    // -------------------------------------------------------- Request Methods
+
+
+    /**
+     * Associated Catalina connector.
+     */
+    protected Connector connector;
+
+    /**
+     * Return the Connector through which this Request was received.
+     */
+    public Connector getConnector() {
+        return this.connector;
+    }
+
+    /**
+     * Set the Connector through which this Request was received.
+     *
+     * @param connector The new connector
+     */
+    public void setConnector(Connector connector) {
+        this.connector = connector;
+    }
+
+
+    /**
+     * Associated context.
+     */
+    protected Context context = null;
+
+    /**
+     * Return the Context within which this Request is being processed.
+     */
+    public Context getContext() {
+        return this.context;
+    }
+
+
+    /**
+     * Set the Context within which this Request is being processed.  This
+     * must be called as soon as the appropriate Context is identified, because
+     * it identifies the value to be returned by <code>getContextPath()</code>,
+     * and thus enables parsing of the request URI.
+     *
+     * @param context The newly associated Context
+     */
+    public void setContext(Context context) {
+        this.context = context;
+    }
+
+
+    /**
+     * Filter chain associated with the request.
+     */
+    protected FilterChain filterChain = null;
+
+    /**
+     * Get filter chain associated with the request.
+     */
+    public FilterChain getFilterChain() {
+        return this.filterChain;
+    }
+
+    /**
+     * Set filter chain associated with the request.
+     * 
+     * @param filterChain new filter chain
+     */
+    public void setFilterChain(FilterChain filterChain) {
+        this.filterChain = filterChain;
+    }
+
+
+    /**
+     * Return the Host within which this Request is being processed.
+     */
+    public Host getHost() {
+        return ((Host) mappingData.host);
+    }
+
+
+    /**
+     * Set the Host within which this Request is being processed.  This
+     * must be called as soon as the appropriate Host is identified, and
+     * before the Request is passed to a context.
+     *
+     * @param host The newly associated Host
+     */
+    public void setHost(Host host) {
+        mappingData.host = host;
+    }
+
+
+    /**
+     * Descriptive information about this Request implementation.
+     */
+    protected static final String info =
+        "org.apache.coyote.catalina.CoyoteRequest/1.0";
+
+    /**
+     * Return descriptive information about this Request implementation and
+     * the corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    public String getInfo() {
+        return info;
+    }
+
+
+    /**
+     * Mapping data.
+     */
+    protected MappingData mappingData = new MappingData();
+
+    /**
+     * Return mapping data.
+     */
+    public MappingData getMappingData() {
+        return mappingData;
+    }
+
+
+    /**
+     * The facade associated with this request.
+     */
+    protected RequestFacade facade = null;
+
+    /**
+     * Return the <code>ServletRequest</code> for which this object
+     * is the facade.  This method must be implemented by a subclass.
+     */
+    public HttpServletRequest getRequest() {
+        if (facade == null) {
+            facade = new RequestFacade(this);
+        } 
+        return facade;
+    }
+
+
+    /**
+     * The response with which this request is associated.
+     */
+    protected org.apache.catalina.connector.Response response = null;
+
+    /**
+     * Return the Response with which this Request is associated.
+     */
+    public org.apache.catalina.connector.Response getResponse() {
+        return this.response;
+    }
+
+    /**
+     * Set the Response with which this Request is associated.
+     *
+     * @param response The new associated response
+     */
+    public void setResponse(org.apache.catalina.connector.Response response) {
+        this.response = response;
+    }
+
+    /**
+     * Return the input stream associated with this Request.
+     */
+    public InputStream getStream() {
+        if (inputStream == null) {
+            inputStream = new CoyoteInputStream(inputBuffer);
+        }
+        return inputStream;
+    }
+
+    /**
+     * URI byte to char converter (not recycled).
+     */
+    protected B2CConverter URIConverter = null;
+
+    /**
+     * Return the URI converter.
+     */
+    protected B2CConverter getURIConverter() {
+        return URIConverter;
+    }
+
+    /**
+     * Set the URI converter.
+     * 
+     * @param URIConverter the new URI converter
+     */
+    protected void setURIConverter(B2CConverter URIConverter) {
+        this.URIConverter = URIConverter;
+    }
+
+
+    /**
+     * Associated wrapper.
+     */
+    protected Wrapper wrapper = null;
+
+    /**
+     * Return the Wrapper within which this Request is being processed.
+     */
+    public Wrapper getWrapper() {
+        return this.wrapper;
+    }
+
+
+    /**
+     * Set the Wrapper within which this Request is being processed.  This
+     * must be called as soon as the appropriate Wrapper is identified, and
+     * before the Request is ultimately passed to an application servlet.
+     * @param wrapper The newly associated Wrapper
+     */
+    public void setWrapper(Wrapper wrapper) {
+        this.wrapper = wrapper;
+    }
+
+
+    // ------------------------------------------------- Request Public Methods
+
+
+    /**
+     * Create and return a ServletInputStream to read the content
+     * associated with this Request.
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public ServletInputStream createInputStream() 
+        throws IOException {
+        if (inputStream == null) {
+            inputStream = new CoyoteInputStream(inputBuffer);
+        }
+        return inputStream;
+    }
+
+
+    /**
+     * Perform whatever actions are required to flush and close the input
+     * stream or reader, in a single operation.
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public void finishRequest() throws IOException {
+        // Optionally disable swallowing of additional request data.
+        Context context = getContext();
+        if (context != null &&
+                response.getStatus() == HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE &&
+                !context.getSwallowAbortedUploads()) {
+            coyoteRequest.action(ActionCode.DISABLE_SWALLOW_INPUT, null);
+        }
+    }
+
+
+    /**
+     * Return the object bound with the specified name to the internal notes
+     * for this request, or <code>null</code> if no such binding exists.
+     *
+     * @param name Name of the note to be returned
+     */
+    public Object getNote(String name) {
+        return notes.get(name);
+    }
+
+
+    /**
+     * Return an Iterator containing the String names of all notes bindings
+     * that exist for this request.
+     */
+    public Iterator<String> getNoteNames() {
+        return notes.keySet().iterator();
+    }
+
+
+    /**
+     * Remove any object bound to the specified name in the internal notes
+     * for this request.
+     *
+     * @param name Name of the note to be removed
+     */
+    public void removeNote(String name) {
+        notes.remove(name);
+    }
+
+
+    /**
+     * Bind an object to a specified name in the internal notes associated
+     * with this request, replacing any existing binding for this name.
+     *
+     * @param name Name to which the object should be bound
+     * @param value Object to be bound to the specified name
+     */
+    public void setNote(String name, Object value) {
+        notes.put(name, value);
+    }
+
+
+    /**
+     * Set the IP address of the remote client associated with this Request.
+     *
+     * @param remoteAddr The remote IP address
+     */
+    public void setRemoteAddr(String remoteAddr) {
+        this.remoteAddr = remoteAddr;
+    }
+
+
+    /**
+     * Set the fully qualified name of the remote client associated with this
+     * Request.
+     *
+     * @param remoteHost The remote host name
+     */
+    public void setRemoteHost(String remoteHost) {
+        this.remoteHost = remoteHost;
+    }
+
+
+    /**
+     * Set the value to be returned by <code>isSecure()</code>
+     * for this Request.
+     *
+     * @param secure The new isSecure value
+     */
+    public void setSecure(boolean secure) {
+        this.secure = secure;
+    }
+
+
+    /**
+     * Set the name of the server (virtual host) to process this request.
+     *
+     * @param name The server name
+     */
+    public void setServerName(String name) {
+        coyoteRequest.serverName().setString(name);
+    }
+
+
+    /**
+     * Set the port number of the server to process this request.
+     *
+     * @param port The server port
+     */
+    public void setServerPort(int port) {
+        coyoteRequest.setServerPort(port);
+    }
+
+
+    // ------------------------------------------------- ServletRequest Methods
+
+
+    /**
+     * Return the specified request attribute if it exists; otherwise, return
+     * <code>null</code>.
+     *
+     * @param name Name of the request attribute to return
+     */
+    @Override
+    public Object getAttribute(String name) {
+
+        if (name.equals(Globals.DISPATCHER_TYPE_ATTR)) {
+            return (internalDispatcherType == null) 
+                ? DispatcherType.REQUEST
+                : internalDispatcherType;
+        } else if (name.equals(Globals.DISPATCHER_REQUEST_PATH_ATTR)) {
+            return (requestDispatcherPath == null) 
+                ? getRequestPathMB().toString()
+                : requestDispatcherPath.toString();
+        }
+        
+        if (name.equals(Globals.ASYNC_SUPPORTED_ATTR)) {
+            return asyncSupported;
+        }
+
+        if (name.equals(Globals.GSS_CREDENTIAL_ATTR)) {
+            if (userPrincipal instanceof GenericPrincipal) {
+                return ((GenericPrincipal) userPrincipal).getGssCredential();
+            }
+            return null;
+        }
+
+        Object attr=attributes.get(name);
+
+        if(attr!=null)
+            return(attr);
+
+        attr =  coyoteRequest.getAttribute(name);
+        if(attr != null)
+            return attr;
+        if( isSSLAttribute(name) ) {
+            coyoteRequest.action(ActionCode.REQ_SSL_ATTRIBUTE, 
+                                 coyoteRequest);
+            attr = coyoteRequest.getAttribute(Globals.CERTIFICATES_ATTR);
+            if( attr != null) {
+                attributes.put(Globals.CERTIFICATES_ATTR, attr);
+            }
+            attr = coyoteRequest.getAttribute(Globals.CIPHER_SUITE_ATTR);
+            if(attr != null) {
+                attributes.put(Globals.CIPHER_SUITE_ATTR, attr);
+            }
+            attr = coyoteRequest.getAttribute(Globals.KEY_SIZE_ATTR);
+            if(attr != null) {
+                attributes.put(Globals.KEY_SIZE_ATTR, attr);
+            }
+            attr = coyoteRequest.getAttribute(Globals.SSL_SESSION_ID_ATTR);
+            if(attr != null) {
+                attributes.put(Globals.SSL_SESSION_ID_ATTR, attr);
+            }
+            attr = coyoteRequest.getAttribute(Globals.SSL_SESSION_MGR_ATTR);
+            if(attr != null) {
+                attributes.put(Globals.SSL_SESSION_MGR_ATTR, attr);
+            }
+            attr = attributes.get(name);
+            sslAttributesParsed = true;
+        }
+        return attr;
+    }
+
+
+    /**
+     * Test if a given name is one of the special Servlet-spec SSL attributes.
+     */
+    static boolean isSSLAttribute(String name) {
+        return Globals.CERTIFICATES_ATTR.equals(name) ||
+            Globals.CIPHER_SUITE_ATTR.equals(name) ||
+            Globals.KEY_SIZE_ATTR.equals(name)  ||
+            Globals.SSL_SESSION_ID_ATTR.equals(name) ||
+            Globals.SSL_SESSION_MGR_ATTR.equals(name);
+    }
+
+    /**
+     * Return the names of all request attributes for this Request, or an
+     * empty <code>Enumeration</code> if there are none. Note that the attribute
+     * names returned will only be those for the attributes set via
+     * {@link #setAttribute(String, Object)}. Tomcat internal attributes will
+     * not be included although they are accessible via
+     * {@link #getAttribute(String)}. The Tomcat internal attributes include:
+     * <ul>
+     * <li>{@link Globals#DISPATCHER_TYPE_ATTR}</li>
+     * <li>{@link Globals#DISPATCHER_REQUEST_PATH_ATTR}</li>
+     * <li>{@link Globals#ASYNC_SUPPORTED_ATTR}</li>
+     * <li>{@link Globals#CERTIFICATES_ATTR} (SSL connections only)</li>
+     * <li>{@link Globals#CIPHER_SUITE_ATTR} (SSL connections only)</li>
+     * <li>{@link Globals#KEY_SIZE_ATTR} (SSL connections only)</li>
+     * <li>{@link Globals#SSL_SESSION_ID_ATTR} (SSL connections only)</li>
+     * <li>{@link Globals#SSL_SESSION_MGR_ATTR} (SSL connections only)</li>
+     * </ul>
+     * The underlying connector may also expose request attributes. These all
+     * have names starting with "org.apache.tomcat" and include:
+     * <ul>
+     * <li>org.apache.tomcat.sendfile.support</li>
+     * <li>org.apache.tomcat.comet.support</li>
+     * <li>org.apache.tomcat.comet.timeout.support</li>
+     * </ul>
+     * Connector implementations may return some, all or none of these
+     * attributes and may also support additional attributes.
+     */
+    @Override
+    public Enumeration<String> getAttributeNames() {
+        if (isSecure() && !sslAttributesParsed) {
+            getAttribute(Globals.CERTIFICATES_ATTR);
+        }
+        return new Enumerator<String>(attributes.keySet(), true);
+    }
+
+
+    /**
+     * Return the character encoding for this Request.
+     */
+    @Override
+    public String getCharacterEncoding() {
+      return coyoteRequest.getCharacterEncoding();
+    }
+
+
+    /**
+     * Return the content length for this Request.
+     */
+    @Override
+    public int getContentLength() {
+        return coyoteRequest.getContentLength();
+    }
+
+
+    /**
+     * Return the content type for this Request.
+     */
+    @Override
+    public String getContentType() {
+        return coyoteRequest.getContentType();
+    }
+
+
+    /**
+     * Return the servlet input stream for this Request.  The default
+     * implementation returns a servlet input stream created by
+     * <code>createInputStream()</code>.
+     *
+     * @exception IllegalStateException if <code>getReader()</code> has
+     *  already been called for this request
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public ServletInputStream getInputStream() throws IOException {
+
+        if (usingReader)
+            throw new IllegalStateException
+                (sm.getString("coyoteRequest.getInputStream.ise"));
+
+        usingInputStream = true;
+        if (inputStream == null) {
+            inputStream = new CoyoteInputStream(inputBuffer);
+        }
+        return inputStream;
+
+    }
+
+
+    /**
+     * Return the preferred Locale that the client will accept content in,
+     * based on the value for the first <code>Accept-Language</code> header
+     * that was encountered.  If the request did not specify a preferred
+     * language, the server's default Locale is returned.
+     */
+    @Override
+    public Locale getLocale() {
+
+        if (!localesParsed)
+            parseLocales();
+
+        if (locales.size() > 0) {
+            return locales.get(0);
+        }
+
+        return defaultLocale;
+    }
+
+
+    /**
+     * Return the set of preferred Locales that the client will accept
+     * content in, based on the values for any <code>Accept-Language</code>
+     * headers that were encountered.  If the request did not specify a
+     * preferred language, the server's default Locale is returned.
+     */
+    @Override
+    public Enumeration<Locale> getLocales() {
+
+        if (!localesParsed)
+            parseLocales();
+
+        if (locales.size() > 0)
+            return (new Enumerator<Locale>(locales));
+        ArrayList<Locale> results = new ArrayList<Locale>();
+        results.add(defaultLocale);
+        return new Enumerator<Locale>(results);
+
+    }
+
+
+    /**
+     * Return the value of the specified request parameter, if any; otherwise,
+     * return <code>null</code>.  If there is more than one value defined,
+     * return only the first one.
+     *
+     * @param name Name of the desired request parameter
+     */
+    @Override
+    public String getParameter(String name) {
+
+        if (!parametersParsed)
+            parseParameters();
+
+        return coyoteRequest.getParameters().getParameter(name);
+
+    }
+
+
+
+    /**
+     * Returns a <code>Map</code> of the parameters of this request.
+     * Request parameters are extra information sent with the request.
+     * For HTTP servlets, parameters are contained in the query string
+     * or posted form data.
+     *
+     * @return A <code>Map</code> containing parameter names as keys
+     *  and parameter values as map values.
+     */
+    @Override
+    public Map<String, String[]> getParameterMap() {
+
+        if (parameterMap.isLocked())
+            return parameterMap;
+
+        Enumeration<String> enumeration = getParameterNames();
+        while (enumeration.hasMoreElements()) {
+            String name = enumeration.nextElement();
+            String[] values = getParameterValues(name);
+            parameterMap.put(name, values);
+        }
+
+        parameterMap.setLocked(true);
+
+        return parameterMap;
+
+    }
+
+
+    /**
+     * Return the names of all defined request parameters for this request.
+     */
+    @Override
+    public Enumeration<String> getParameterNames() {
+
+        if (!parametersParsed)
+            parseParameters();
+
+        return coyoteRequest.getParameters().getParameterNames();
+
+    }
+
+
+    /**
+     * Return the defined values for the specified request parameter, if any;
+     * otherwise, return <code>null</code>.
+     *
+     * @param name Name of the desired request parameter
+     */
+    @Override
+    public String[] getParameterValues(String name) {
+
+        if (!parametersParsed)
+            parseParameters();
+
+        return coyoteRequest.getParameters().getParameterValues(name);
+
+    }
+
+
+    /**
+     * Return the protocol and version used to make this Request.
+     */
+    @Override
+    public String getProtocol() {
+        return coyoteRequest.protocol().toString();
+    }
+
+
+    /**
+     * Read the Reader wrapping the input stream for this Request.  The
+     * default implementation wraps a <code>BufferedReader</code> around the
+     * servlet input stream returned by <code>createInputStream()</code>.
+     *
+     * @exception IllegalStateException if <code>getInputStream()</code>
+     *  has already been called for this request
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public BufferedReader getReader() throws IOException {
+
+        if (usingInputStream)
+            throw new IllegalStateException
+                (sm.getString("coyoteRequest.getReader.ise"));
+
+        usingReader = true;
+        inputBuffer.checkConverter();
+        if (reader == null) {
+            reader = new CoyoteReader(inputBuffer);
+        }
+        return reader;
+
+    }
+
+
+    /**
+     * Return the real path of the specified virtual path.
+     *
+     * @param path Path to be translated
+     *
+     * @deprecated As of version 2.1 of the Java Servlet API, use
+     *  <code>ServletContext.getRealPath()</code>.
+     */
+    @Override
+    @Deprecated
+    public String getRealPath(String path) {
+
+        if (context == null)
+            return null;
+        ServletContext servletContext = context.getServletContext();
+        if (servletContext == null) {
+            return null;
+        }
+
+        try {
+            return (servletContext.getRealPath(path));
+        } catch (IllegalArgumentException e) {
+            return null;
+        }
+    }
+
+
+    /**
+     * Return the remote IP address making this Request.
+     */
+    @Override
+    public String getRemoteAddr() {
+        if (remoteAddr == null) {
+            coyoteRequest.action
+                (ActionCode.REQ_HOST_ADDR_ATTRIBUTE, coyoteRequest);
+            remoteAddr = coyoteRequest.remoteAddr().toString();
+        }
+        return remoteAddr;
+    }
+
+
+    /**
+     * Return the remote host name making this Request.
+     */
+    @Override
+    public String getRemoteHost() {
+        if (remoteHost == null) {
+            if (!connector.getEnableLookups()) {
+                remoteHost = getRemoteAddr();
+            } else {
+                coyoteRequest.action
+                    (ActionCode.REQ_HOST_ATTRIBUTE, coyoteRequest);
+                remoteHost = coyoteRequest.remoteHost().toString();
+            }
+        }
+        return remoteHost;
+    }
+
+    /**
+     * Returns the Internet Protocol (IP) source port of the client
+     * or last proxy that sent the request.
+     */    
+    @Override
+    public int getRemotePort(){
+        if (remotePort == -1) {
+            coyoteRequest.action
+                (ActionCode.REQ_REMOTEPORT_ATTRIBUTE, coyoteRequest);
+            remotePort = coyoteRequest.getRemotePort();
+        }
+        return remotePort;    
+    }
+
+    /**
+     * Returns the host name of the Internet Protocol (IP) interface on
+     * which the request was received.
+     */
+    @Override
+    public String getLocalName(){
+        if (localName == null) {
+            coyoteRequest.action
+                (ActionCode.REQ_LOCAL_NAME_ATTRIBUTE, coyoteRequest);
+            localName = coyoteRequest.localName().toString();
+        }
+        return localName;
+    }
+
+    /**
+     * Returns the Internet Protocol (IP) address of the interface on
+     * which the request  was received.
+     */       
+    @Override
+    public String getLocalAddr(){
+        if (localAddr == null) {
+            coyoteRequest.action
+                (ActionCode.REQ_LOCAL_ADDR_ATTRIBUTE, coyoteRequest);
+            localAddr = coyoteRequest.localAddr().toString();
+        }
+        return localAddr;    
+    }
+
+
+    /**
+     * Returns the Internet Protocol (IP) port number of the interface
+     * on which the request was received.
+     */
+    @Override
+    public int getLocalPort(){
+        if (localPort == -1){
+            coyoteRequest.action
+                (ActionCode.REQ_LOCALPORT_ATTRIBUTE, coyoteRequest);
+            localPort = coyoteRequest.getLocalPort();
+        }
+        return localPort;
+    }
+    
+    /**
+     * Return a RequestDispatcher that wraps the resource at the specified
+     * path, which may be interpreted as relative to the current request path.
+     *
+     * @param path Path of the resource to be wrapped
+     */
+    @Override
+    public RequestDispatcher getRequestDispatcher(String path) {
+
+        if (context == null)
+            return null;
+
+        // If the path is already context-relative, just pass it through
+        if (path == null)
+            return null;
+        else if (path.startsWith("/"))
+            return (context.getServletContext().getRequestDispatcher(path));
+
+        // Convert a request-relative path to a context-relative one
+        String servletPath = (String) getAttribute(
+                RequestDispatcher.INCLUDE_SERVLET_PATH);
+        if (servletPath == null)
+            servletPath = getServletPath();
+
+        // Add the path info, if there is any
+        String pathInfo = getPathInfo();
+        String requestPath = null;
+
+        if (pathInfo == null) {
+            requestPath = servletPath;
+        } else {
+            requestPath = servletPath + pathInfo;
+        }
+
+        int pos = requestPath.lastIndexOf('/');
+        String relative = null;
+        if (pos >= 0) {
+            relative = requestPath.substring(0, pos + 1) + path;
+        } else {
+            relative = requestPath + path;
+        }
+
+        return context.getServletContext().getRequestDispatcher(relative);
+
+    }
+
+
+    /**
+     * Return the scheme used to make this Request.
+     */
+    @Override
+    public String getScheme() {
+        return coyoteRequest.scheme().toString();
+    }
+
+
+    /**
+     * Return the server name responding to this Request.
+     */
+    @Override
+    public String getServerName() {
+        return coyoteRequest.serverName().toString();
+    }
+
+
+    /**
+     * Return the server port responding to this Request.
+     */
+    @Override
+    public int getServerPort() {
+        return coyoteRequest.getServerPort();
+    }
+
+
+    /**
+     * Was this request received on a secure connection?
+     */
+    @Override
+    public boolean isSecure() {
+        return secure;
+    }
+
+
+    /**
+     * Remove the specified request attribute if it exists.
+     *
+     * @param name Name of the request attribute to remove
+     */
+    @Override
+    public void removeAttribute(String name) {
+        Object value = null;
+        boolean found = false;
+
+        // Remove the specified attribute
+        // Check for read only attribute
+        // requests are per thread so synchronization unnecessary
+        if (readOnlyAttributes.containsKey(name)) {
+            return;
+        }
+
+        // Pass special attributes to the native layer
+        if (name.startsWith("org.apache.tomcat.")) {
+            coyoteRequest.getAttributes().remove(name);
+        }
+
+        found = attributes.containsKey(name);
+        if (found) {
+            value = attributes.get(name);
+            attributes.remove(name);
+        } else {
+            return;
+        }
+        
+        // Notify interested application event listeners
+        Object listeners[] = context.getApplicationEventListeners();
+        if ((listeners == null) || (listeners.length == 0))
+            return;
+        ServletRequestAttributeEvent event =
+          new ServletRequestAttributeEvent(context.getServletContext(),
+                                           getRequest(), name, value);
+        for (int i = 0; i < listeners.length; i++) {
+            if (!(listeners[i] instanceof ServletRequestAttributeListener))
+                continue;
+            ServletRequestAttributeListener listener =
+                (ServletRequestAttributeListener) listeners[i];
+            try {
+                listener.attributeRemoved(event);
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                context.getLogger().error(sm.getString("coyoteRequest.attributeEvent"), t);
+                // Error valve will pick this exception up and display it to user
+                attributes.put(RequestDispatcher.ERROR_EXCEPTION, t);
+            }
+        }
+    }
+
+
+    /**
+     * Set the specified request attribute to the specified value.
+     *
+     * @param name Name of the request attribute to set
+     * @param value The associated value
+     */
+    @Override
+    public void setAttribute(String name, Object value) {
+
+        // Name cannot be null
+        if (name == null)
+            throw new IllegalArgumentException
+                (sm.getString("coyoteRequest.setAttribute.namenull"));
+
+        // Null value is the same as removeAttribute()
+        if (value == null) {
+            removeAttribute(name);
+            return;
+        }
+
+        if (name.equals(Globals.DISPATCHER_TYPE_ATTR)) {
+            internalDispatcherType = (DispatcherType)value;
+            return;
+        } else if (name.equals(Globals.DISPATCHER_REQUEST_PATH_ATTR)) {
+            requestDispatcherPath = value;
+            return;
+        }
+        
+        if (name.equals(Globals.ASYNC_SUPPORTED_ATTR)) {
+            this.asyncSupported = (Boolean)value;
+        }
+
+        Object oldValue = null;
+        boolean replaced = false;
+
+        // Add or replace the specified attribute
+        // Check for read only attribute
+        // requests are per thread so synchronization unnecessary
+        if (readOnlyAttributes.containsKey(name)) {
+            return;
+        }
+
+        oldValue = attributes.put(name, value);
+        if (oldValue != null) {
+            replaced = true;
+        }
+
+        // Pass special attributes to the native layer
+        if (name.startsWith("org.apache.tomcat.")) {
+            coyoteRequest.setAttribute(name, value);
+        }
+        
+        // Notify interested application event listeners
+        Object listeners[] = context.getApplicationEventListeners();
+        if ((listeners == null) || (listeners.length == 0))
+            return;
+        ServletRequestAttributeEvent event = null;
+        if (replaced)
+            event =
+                new ServletRequestAttributeEvent(context.getServletContext(),
+                                                 getRequest(), name, oldValue);
+        else
+            event =
+                new ServletRequestAttributeEvent(context.getServletContext(),
+                                                 getRequest(), name, value);
+
+        for (int i = 0; i < listeners.length; i++) {
+            if (!(listeners[i] instanceof ServletRequestAttributeListener))
+                continue;
+            ServletRequestAttributeListener listener =
+                (ServletRequestAttributeListener) listeners[i];
+            try {
+                if (replaced) {
+                    listener.attributeReplaced(event);
+                } else {
+                    listener.attributeAdded(event);
+                }
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                context.getLogger().error(sm.getString("coyoteRequest.attributeEvent"), t);
+                // Error valve will pick this exception up and display it to user
+                attributes.put(RequestDispatcher.ERROR_EXCEPTION, t );
+            }
+        }
+    }
+
+
+    /**
+     * Overrides the name of the character encoding used in the body of
+     * this request.  This method must be called prior to reading request
+     * parameters or reading input using <code>getReader()</code>.
+     *
+     * @param enc The character encoding to be used
+     *
+     * @exception UnsupportedEncodingException if the specified encoding
+     *  is not supported
+     *
+     * @since Servlet 2.3
+     */
+    @Override
+    public void setCharacterEncoding(String enc)
+        throws UnsupportedEncodingException {
+
+        if (usingReader)
+            return;
+        
+        // Ensure that the specified encoding is valid
+        byte buffer[] = new byte[1];
+        buffer[0] = (byte) 'a';
+        @SuppressWarnings("unused")
+        String s = new String(buffer, enc);
+
+        // Save the validated encoding
+        coyoteRequest.setCharacterEncoding(enc);
+
+    }
+
+
+    @Override
+    public ServletContext getServletContext() {
+        return context.getServletContext();
+     }
+
+    @Override
+    public AsyncContext startAsync() {
+        return startAsync(getRequest(),response.getResponse());
+    }
+
+    @Override
+    public AsyncContext startAsync(ServletRequest request,
+            ServletResponse response) {
+        if (!isAsyncSupported()) {
+            throw new IllegalStateException("Not supported.");
+        }
+        
+        if (asyncContext == null) {
+            asyncContext = new AsyncContextImpl(this);
+        }
+        
+        asyncContext.setStarted(getContext(), request, response,
+                request==getRequest() && response==getResponse().getResponse());
+        asyncContext.setTimeout(getConnector().getAsyncTimeout());
+        
+        return asyncContext;
+    }
+
+    @Override
+    public boolean isAsyncStarted() {
+        if (asyncContext == null) {
+            return false;
+        }
+        
+        return asyncContext.isStarted();
+    }
+
+    public boolean isAsyncDispatching() {
+        if (asyncContext == null) {
+            return false;
+        }
+
+        AtomicBoolean result = new AtomicBoolean(false);
+        coyoteRequest.action(ActionCode.ASYNC_IS_DISPATCHING, result);
+        return result.get();
+    }
+
+    public boolean isAsync() {
+        if (asyncContext == null) {
+            return false;
+        }
+
+        AtomicBoolean result = new AtomicBoolean(false);
+        coyoteRequest.action(ActionCode.ASYNC_IS_ASYNC, result);
+        return result.get();
+    }
+
+    @Override
+    public boolean isAsyncSupported() {
+        if (this.asyncSupported == null) { 
+            return true;
+        }
+
+        return asyncSupported.booleanValue();
+    }
+
+    @Override
+    public AsyncContext getAsyncContext() {
+        return this.asyncContext;
+    }
+
+    @Override
+    public DispatcherType getDispatcherType() {
+        if (internalDispatcherType == null) { 
+            return DispatcherType.REQUEST;
+        }
+
+        return this.internalDispatcherType;
+    }
+
+    // ---------------------------------------------------- HttpRequest Methods
+
+
+    /**
+     * Add a Cookie to the set of Cookies associated with this Request.
+     *
+     * @param cookie The new cookie
+     */
+    public void addCookie(Cookie cookie) {
+
+        if (!cookiesParsed)
+            parseCookies();
+
+        int size = 0;
+        if (cookies != null) {
+            size = cookies.length;
+        }
+
+        Cookie[] newCookies = new Cookie[size + 1];
+        for (int i = 0; i < size; i++) {
+            newCookies[i] = cookies[i];
+        }
+        newCookies[size] = cookie;
+
+        cookies = newCookies;
+
+    }
+
+
+    /**
+     * Add a Locale to the set of preferred Locales for this Request.  The
+     * first added Locale will be the first one returned by getLocales().
+     *
+     * @param locale The new preferred Locale
+     */
+    public void addLocale(Locale locale) {
+        locales.add(locale);
+    }
+
+
+    /**
+     * Add a parameter name and corresponding set of values to this Request.
+     * (This is used when restoring the original request on a form based
+     * login).
+     *
+     * @param name Name of this request parameter
+     * @param values Corresponding values for this request parameter
+     */
+    public void addParameter(String name, String values[]) {
+        coyoteRequest.getParameters().addParameterValues(name, values);
+    }
+
+
+    /**
+     * Clear the collection of Cookies associated with this Request.
+     */
+    public void clearCookies() {
+        cookiesParsed = true;
+        cookies = null;
+    }
+
+
+    /**
+     * Clear the collection of Headers associated with this Request.
+     */
+    public void clearHeaders() {
+        // Not used
+    }
+
+
+    /**
+     * Clear the collection of Locales associated with this Request.
+     */
+    public void clearLocales() {
+        locales.clear();
+    }
+
+
+    /**
+     * Clear the collection of parameters associated with this Request.
+     */
+    public void clearParameters() {
+        // Not used
+    }
+
+
+    /**
+     * Set the authentication type used for this request, if any; otherwise
+     * set the type to <code>null</code>.  Typical values are "BASIC",
+     * "DIGEST", or "SSL".
+     *
+     * @param type The authentication type used
+     */
+    public void setAuthType(String type) {
+        this.authType = type;
+    }
+
+
+    /**
+     * Set the context path for this Request.  This will normally be called
+     * when the associated Context is mapping the Request to a particular
+     * Wrapper.
+     *
+     * @param path The context path
+     */
+    public void setContextPath(String path) {
+
+        if (path == null) {
+            mappingData.contextPath.setString("");
+        } else {
+            mappingData.contextPath.setString(path);
+        }
+
+    }
+
+
+    /**
+     * Set the path information for this Request.  This will normally be called
+     * when the associated Context is mapping the Request to a particular
+     * Wrapper.
+     *
+     * @param path The path information
+     */
+    public void setPathInfo(String path) {
+        mappingData.pathInfo.setString(path);
+    }
+
+
+    /**
+     * Set a flag indicating whether or not the requested session ID for this
+     * request came in through a cookie.  This is normally called by the
+     * HTTP Connector, when it parses the request headers.
+     *
+     * @param flag The new flag
+     */
+    public void setRequestedSessionCookie(boolean flag) {
+
+        this.requestedSessionCookie = flag;
+
+    }
+
+
+    /**
+     * Set the requested session ID for this request.  This is normally called
+     * by the HTTP Connector, when it parses the request headers.
+     *
+     * @param id The new session id
+     */
+    public void setRequestedSessionId(String id) {
+
+        this.requestedSessionId = id;
+
+    }
+
+
+    /**
+     * Set a flag indicating whether or not the requested session ID for this
+     * request came in through a URL.  This is normally called by the
+     * HTTP Connector, when it parses the request headers.
+     *
+     * @param flag The new flag
+     */
+    public void setRequestedSessionURL(boolean flag) {
+
+        this.requestedSessionURL = flag;
+
+    }
+
+
+    /**
+     * Set a flag indicating whether or not the requested session ID for this
+     * request came in through SSL.  This is normally called by the
+     * HTTP Connector, when it parses the request headers.
+     *
+     * @param flag The new flag
+     */
+    public void setRequestedSessionSSL(boolean flag) {
+
+        this.requestedSessionSSL = flag;
+
+    }
+
+
+    /**
+     * Get the decoded request URI.
+     * 
+     * @return the URL decoded request URI
+     */
+    public String getDecodedRequestURI() {
+        return coyoteRequest.decodedURI().toString();
+    }
+
+
+    /**
+     * Get the decoded request URI.
+     * 
+     * @return the URL decoded request URI
+     */
+    public MessageBytes getDecodedRequestURIMB() {
+        return coyoteRequest.decodedURI();
+    }
+
+
+    /**
+     * Set the servlet path for this Request.  This will normally be called
+     * when the associated Context is mapping the Request to a particular
+     * Wrapper.
+     *
+     * @param path The servlet path
+     */
+    public void setServletPath(String path) {
+        if (path != null)
+            mappingData.wrapperPath.setString(path);
+    }
+
+
+    /**
+     * Set the Principal who has been authenticated for this Request.  This
+     * value is also used to calculate the value to be returned by the
+     * <code>getRemoteUser()</code> method.
+     *
+     * @param principal The user Principal
+     */
+    public void setUserPrincipal(Principal principal) {
+
+        if (Globals.IS_SECURITY_ENABLED){
+            HttpSession session = getSession(false);
+            if ( (subject != null) && 
+                 (!subject.getPrincipals().contains(principal)) ){
+                subject.getPrincipals().add(principal);         
+            } else if (session != null &&
+                        session.getAttribute(Globals.SUBJECT_ATTR) == null) {
+                subject = new Subject();
+                subject.getPrincipals().add(principal);         
+            }
+            if (session != null){
+                session.setAttribute(Globals.SUBJECT_ATTR, subject);
+            }
+        } 
+
+        this.userPrincipal = principal;
+    }
+
+
+    // --------------------------------------------- HttpServletRequest Methods
+
+
+    /**
+     * Return the authentication type used for this Request.
+     */
+    @Override
+    public String getAuthType() {
+        return authType;
+    }
+
+
+    /**
+     * Return the portion of the request URI used to select the Context
+     * of the Request.
+     */
+    @Override
+    public String getContextPath() {
+        return mappingData.contextPath.toString();
+    }
+
+
+    /**
+     * Get the context path.
+     * 
+     * @return the context path
+     */
+    public MessageBytes getContextPathMB() {
+        return mappingData.contextPath;
+    }
+
+
+    /**
+     * Return the set of Cookies received with this Request.
+     */
+    @Override
+    public Cookie[] getCookies() {
+
+        if (!cookiesParsed)
+            parseCookies();
+
+        return cookies;
+
+    }
+
+
+    /**
+     * Set the set of cookies received with this Request.
+     */
+    public void setCookies(Cookie[] cookies) {
+
+        this.cookies = cookies;
+
+    }
+
+
+    /**
+     * Return the value of the specified date header, if any; otherwise
+     * return -1.
+     *
+     * @param name Name of the requested date header
+     *
+     * @exception IllegalArgumentException if the specified header value
+     *  cannot be converted to a date
+     */
+    @Override
+    public long getDateHeader(String name) {
+
+        String value = getHeader(name);
+        if (value == null)
+            return (-1L);
+
+        // Attempt to convert the date header in a variety of formats
+        long result = FastHttpDateFormat.parseDate(value, formats);
+        if (result != (-1L)) {
+            return result;
+        }
+        throw new IllegalArgumentException(value);
+
+    }
+
+
+    /**
+     * Return the first value of the specified header, if any; otherwise,
+     * return <code>null</code>
+     *
+     * @param name Name of the requested header
+     */
+    @Override
+    public String getHeader(String name) {
+        return coyoteRequest.getHeader(name);
+    }
+
+
+    /**
+     * Return all of the values of the specified header, if any; otherwise,
+     * return an empty enumeration.
+     *
+     * @param name Name of the requested header
+     */
+    @Override
+    public Enumeration<String> getHeaders(String name) {
+        return coyoteRequest.getMimeHeaders().values(name);
+    }
+
+
+    /**
+     * Return the names of all headers received with this request.
+     */
+    @Override
+    public Enumeration<String> getHeaderNames() {
+        return coyoteRequest.getMimeHeaders().names();
+    }
+
+
+    /**
+     * Return the value of the specified header as an integer, or -1 if there
+     * is no such header for this request.
+     *
+     * @param name Name of the requested header
+     *
+     * @exception IllegalArgumentException if the specified header value
+     *  cannot be converted to an integer
+     */
+    @Override
+    public int getIntHeader(String name) {
+
+        String value = getHeader(name);
+        if (value == null) {
+            return (-1);
+        }
+
+        return Integer.parseInt(value);
+    }
+
+
+    /**
+     * Return the HTTP request method used in this Request.
+     */
+    @Override
+    public String getMethod() {
+        return coyoteRequest.method().toString();
+    }
+
+
+    /**
+     * Return the path information associated with this Request.
+     */
+    @Override
+    public String getPathInfo() {
+        return mappingData.pathInfo.toString();
+    }
+
+
+    /**
+     * Get the path info.
+     * 
+     * @return the path info
+     */
+    public MessageBytes getPathInfoMB() {
+        return mappingData.pathInfo;
+    }
+
+
+    /**
+     * Return the extra path information for this request, translated
+     * to a real path.
+     */
+    @Override
+    public String getPathTranslated() {
+
+        if (context == null)
+            return null;
+
+        if (getPathInfo() == null) {
+            return null;
+        }
+
+        return context.getServletContext().getRealPath(getPathInfo());
+    }
+
+
+    /**
+     * Return the query string associated with this request.
+     */
+    @Override
+    public String getQueryString() {
+        return coyoteRequest.queryString().toString();
+    }
+
+
+    /**
+     * Return the name of the remote user that has been authenticated
+     * for this Request.
+     */
+    @Override
+    public String getRemoteUser() {
+
+        if (userPrincipal == null) {
+            return null;
+        }
+     
+        return userPrincipal.getName();
+    }
+
+
+    /**
+     * Get the request path.
+     * 
+     * @return the request path
+     */
+    public MessageBytes getRequestPathMB() {
+        return mappingData.requestPath;
+    }
+
+
+    /**
+     * Return the session identifier included in this request, if any.
+     */
+    @Override
+    public String getRequestedSessionId() {
+        return requestedSessionId;
+    }
+
+
+    /**
+     * Return the request URI for this request.
+     */
+    @Override
+    public String getRequestURI() {
+        return coyoteRequest.requestURI().toString();
+    }
+
+
+    /**
+     * 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 <code>String</code>, 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
+     */
+    @Override
+    public StringBuffer getRequestURL() {
+
+        StringBuffer url = new StringBuffer();
+        String scheme = getScheme();
+        int port = getServerPort();
+        if (port < 0)
+            port = 80; // Work around java.net.URL bug
+
+        url.append(scheme);
+        url.append("://");
+        url.append(getServerName());
+        if ((scheme.equals("http") && (port != 80))
+            || (scheme.equals("https") && (port != 443))) {
+            url.append(':');
+            url.append(port);
+        }
+        url.append(getRequestURI());
+
+        return url;
+    }
+
+
+    /**
+     * Return the portion of the request URI used to select the servlet
+     * that will process this request.
+     */
+    @Override
+    public String getServletPath() {
+        return (mappingData.wrapperPath.toString());
+    }
+
+
+    /**
+     * Get the servlet path.
+     * 
+     * @return the servlet path
+     */
+    public MessageBytes getServletPathMB() {
+        return mappingData.wrapperPath;
+    }
+
+
+    /**
+     * Return the session associated with this Request, creating one
+     * if necessary.
+     */
+    @Override
+    public HttpSession getSession() {
+        Session session = doGetSession(true);
+        if (session == null) {
+            return null;
+        }
+     
+        return session.getSession();
+    }
+
+
+    /**
+     * Return the session associated with this Request, creating one
+     * if necessary and requested.
+     *
+     * @param create Create a new session if one does not exist
+     */
+    @Override
+    public HttpSession getSession(boolean create) {
+        Session session = doGetSession(create);
+        if (session == null) {
+            return null;
+        }
+        
+        return session.getSession();
+    }
+
+
+    /**
+     * Return <code>true</code> if the session identifier included in this
+     * request came from a cookie.
+     */
+    @Override
+    public boolean isRequestedSessionIdFromCookie() {
+
+        if (requestedSessionId == null) {
+            return false;
+        }
+            
+        return requestedSessionCookie;
+    }
+
+
+    /**
+     * Return <code>true</code> if the session identifier included in this
+     * request came from the request URI.
+     */
+    @Override
+    public boolean isRequestedSessionIdFromURL() {
+
+        if (requestedSessionId == null) {
+            return false;
+        }
+        
+        return requestedSessionURL;
+    }
+
+
+    /**
+     * Return <code>true</code> if the session identifier included in this
+     * request came from the request URI.
+     *
+     * @deprecated As of Version 2.1 of the Java Servlet API, use
+     *  <code>isRequestedSessionIdFromURL()</code> instead.
+     */
+    @Override
+    @Deprecated
+    public boolean isRequestedSessionIdFromUrl() {
+        return (isRequestedSessionIdFromURL());
+    }
+
+
+    /**
+     * Return <code>true</code> if the session identifier included in this
+     * request identifies a valid session.
+     */
+    @Override
+    public boolean isRequestedSessionIdValid() {
+
+        if (requestedSessionId == null) {
+            return false;
+        }
+        
+        if (context == null) {
+            return false;
+        }
+        
+        Manager manager = context.getManager();
+        if (manager == null) {
+            return false;
+        }
+        
+        Session session = null;
+        try {
+            session = manager.findSession(requestedSessionId);
+        } catch (IOException e) {
+            // Can't find the session 
+        }
+        
+        if ((session == null) || !session.isValid()) {
+            // Check for parallel deployment contexts
+            if (getMappingData().contexts == null) {
+                return false;
+            } else {
+                for (int i = (getMappingData().contexts.length); i > 0; i--) {
+                    Context ctxt = (Context) getMappingData().contexts[i - 1];
+                    try {
+                        if (ctxt.getManager().findSession(requestedSessionId) !=
+                                null) {
+                            return true;
+                        }
+                    } catch (IOException e) {
+                        // Ignore
+                    }
+                }
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Return <code>true</code> if the authenticated user principal
+     * possesses the specified role name.
+     *
+     * @param role Role name to be validated
+     */
+    @Override
+    public boolean isUserInRole(String role) {
+
+        // Have we got an authenticated principal at all?
+        if (userPrincipal == null)
+            return false;
+
+        // Identify the Realm we will use for checking role assignments
+        if (context == null)
+            return false;
+        
+        Realm realm = context.getRealm();
+        if (realm == null)
+            return false;
+
+        // Check for a role defined directly as a <security-role>
+        return (realm.hasRole(wrapper, userPrincipal, role));
+    }
+
+
+    /**
+     * Return the principal that has been authenticated for this Request.
+     */
+    public Principal getPrincipal() {
+        return userPrincipal;
+    }
+
+
+    /**
+     * Return the principal that has been authenticated for this Request.
+     */
+    @Override
+    public Principal getUserPrincipal() {
+        if (userPrincipal instanceof GenericPrincipal) {
+            return ((GenericPrincipal) userPrincipal).getUserPrincipal();
+        }
+
+        return userPrincipal;
+    }
+
+
+    /**
+     * Return the session associated with this Request, creating one
+     * if necessary.
+     */
+    public Session getSessionInternal() {
+        return doGetSession(true);
+    }
+
+
+    /**
+     * Change the ID of the session that this request is associated with. There
+     * are several things that may trigger an ID change. These include moving
+     * between nodes in a cluster and session fixation prevention during the
+     * authentication process.
+     * 
+     * @param newSessionId   The session to change the session ID for
+     */
+    public void changeSessionId(String newSessionId) {
+        // This should only ever be called if there was an old session ID but
+        // double check to be sure
+        if (requestedSessionId != null && requestedSessionId.length() > 0) {
+            requestedSessionId = newSessionId;
+        }
+        
+        if (context != null && !context.getServletContext()
+                .getEffectiveSessionTrackingModes().contains(
+                        SessionTrackingMode.COOKIE))
+            return;
+        
+        if (response != null) {
+            Cookie newCookie =
+                ApplicationSessionCookieConfig.createSessionCookie(context,
+                        newSessionId, secure);
+            response.addSessionCookieInternal(newCookie);
+        }
+    }
+
+    
+    /**
+     * Return the session associated with this Request, creating one
+     * if necessary and requested.
+     *
+     * @param create Create a new session if one does not exist
+     */
+    public Session getSessionInternal(boolean create) {
+        return doGetSession(create);
+    }
+
+
+    /**
+     * Get the event associated with the request.
+     * @return the event
+     */
+    public CometEventImpl getEvent() {
+        if (event == null) {
+            event = new CometEventImpl(this, response);
+        }
+        return event;
+    }
+    
+    
+    /**
+     * Return true if the current request is handling Comet traffic.
+     */
+    public boolean isComet() {
+        return comet;
+    }
+
+    
+    /**
+     * Set comet state.
+     */
+    public void setComet(boolean comet) {
+        this.comet = comet;
+    }
+
+    /**
+     * return true if we have parsed parameters
+     */
+    public boolean isParametersParsed() {
+        return parametersParsed;
+    }    
+    
+    /**
+     * Return true if bytes are available.
+     */
+    public boolean getAvailable() {
+        return (inputBuffer.available() > 0);
+    }
+
+    /**
+     * Disable swallowing of remaining input if configured
+     */
+    protected void checkSwallowInput() {
+        Context context = getContext();
+        if (context != null && !context.getSwallowAbortedUploads()) {
+            coyoteRequest.action(ActionCode.DISABLE_SWALLOW_INPUT, null);
+        }
+    }
+    
+    public void cometClose() {
+        coyoteRequest.action(ActionCode.COMET_CLOSE,getEvent());
+    }
+    
+    public void setCometTimeout(long timeout) {
+        coyoteRequest.action(ActionCode.COMET_SETTIMEOUT, Long.valueOf(timeout));
+    }
+    
+    /**
+     * Not part of Servlet 3 spec but probably should be.
+     * @return true if the requested session ID was obtained from the SSL session
+     */
+    public boolean isRequestedSessionIdFromSSL() {
+        return requestedSessionSSL;
+    }
+    
+    /**
+     * @throws IOException If an I/O error occurs
+     * @throws IllegalStateException If the response has been committed
+     * @throws ServletException If the caller is responsible for handling the
+     *         error and the container has NOT set the HTTP response code etc.
+     */
+    @Override
+    public boolean authenticate(HttpServletResponse response) 
+    throws IOException, ServletException {
+        if (response.isCommitted()) {
+            throw new IllegalStateException(
+                    sm.getString("coyoteRequest.authenticate.ise"));
+        }
+
+        LoginConfig config = context.getLoginConfig();
+        
+        if (config == null) {
+            throw new ServletException(
+                    sm.getString("coyoteRequest.noLoginConfig"));
+        }
+        return context.getAuthenticator().authenticate(this, response, config);
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void login(String username, String password)
+    throws ServletException {
+        if (getAuthType() != null || getRemoteUser() != null ||
+                getUserPrincipal() != null) {
+            throw new ServletException(
+                    sm.getString("coyoteRequest.alreadyAuthenticated"));
+        }
+        
+        if (context.getAuthenticator() == null) {
+            throw new ServletException("no authenticator");
+        }
+        
+        context.getAuthenticator().login(username, password, this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void logout() throws ServletException {
+        context.getAuthenticator().logout(this);
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Collection<Part> getParts() throws IOException, IllegalStateException,
+            ServletException {
+        
+        parseParts();
+        
+        if (partsParseException != null) {
+            if (partsParseException instanceof IOException) { 
+                throw (IOException) partsParseException;
+            } else if (partsParseException instanceof IllegalStateException) {
+                throw (IllegalStateException) partsParseException;
+            } else if (partsParseException instanceof ServletException) {
+                throw (ServletException) partsParseException;
+            }
+        }
+        
+        return parts;
+    }
+
+    private void parseParts() {
+
+        // Return immediately if the parts have already been parsed
+        if (parts != null || partsParseException != null)
+            return;
+
+        MultipartConfigElement mce = getWrapper().getMultipartConfigElement();
+
+        if (mce == null) {
+            if(getContext().getAllowCasualMultipartParsing()) {
+                mce = new MultipartConfigElement(null,
+                                                 connector.getMaxPostSize(),
+                                                 connector.getMaxPostSize(),
+                                                 connector.getMaxPostSize());
+            } else {
+                parts = Collections.emptyList();
+                return;
+            }
+        }
+        
+        Parameters parameters = coyoteRequest.getParameters();
+
+        File location;
+        String locationStr = mce.getLocation();
+        if (locationStr == null || locationStr.length() == 0) {
+            location = ((File) context.getServletContext().getAttribute(
+                    ServletContext.TEMPDIR));
+        } else {
+            location = new File(locationStr);
+        }
+        
+        if (!location.isAbsolute() || !location.isDirectory()) {
+            partsParseException = new IOException(
+                    sm.getString("coyoteRequest.uploadLocationInvalid",
+                            location));
+            return;
+        }
+        
+        // Create a new file upload handler
+        DiskFileItemFactory factory = new DiskFileItemFactory();
+        try {
+            factory.setRepository(location.getCanonicalFile());
+        } catch (IOException ioe) {
+            partsParseException = ioe;
+            return;
+        }
+        factory.setSizeThreshold(mce.getFileSizeThreshold());
+        
+        ServletFileUpload upload = new ServletFileUpload();
+        upload.setFileItemFactory(factory);
+        upload.setFileSizeMax(mce.getMaxFileSize());
+        upload.setSizeMax(mce.getMaxRequestSize());
+
+        parts = new ArrayList<Part>();
+        try {
+            List<FileItem> items = upload.parseRequest(this);
+            for (FileItem item : items) {
+                ApplicationPart part = new ApplicationPart(item, mce);
+                parts.add(part);
+                if (part.getFilename() == null) {
+                    try {
+                        String encoding = parameters.getEncoding();
+                        if (encoding == null) {
+                            encoding = Parameters.DEFAULT_ENCODING;
+                        }
+                        parameters.addParameterValues(part.getName(),
+                                new String[] { part.getString(encoding) });
+                    } catch (UnsupportedEncodingException uee) {
+                        try {
+                            parameters.addParameterValues(part.getName(),
+                                    new String[] {part.getString(
+                                            Parameters.DEFAULT_ENCODING)});
+                        } catch (UnsupportedEncodingException e) {
+                            // Should not be possible
+                        }
+                    }
+                }
+            }
+
+        } catch (InvalidContentTypeException e) {
+            partsParseException = new ServletException(e);
+        } catch (FileUploadBase.SizeException e) {
+            checkSwallowInput();
+            partsParseException = new IllegalStateException(e);
+        } catch (FileUploadException e) {
+            partsParseException = new IOException(e);
+        }
+        
+        return;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Part getPart(String name) throws IOException, IllegalStateException,
+            ServletException {
+        Collection<Part> c = getParts();
+        Iterator<Part> iterator = c.iterator();
+        while (iterator.hasNext()) {
+            Part part = iterator.next();
+            if (name.equals(part.getName())) {
+                return part;
+            }
+        }
+        return null;
+    }
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    protected Session doGetSession(boolean create) {
+
+        // There cannot be a session if no context has been assigned yet
+        if (context == null)
+            return (null);
+
+        // Return the current session if it exists and is valid
+        if ((session != null) && !session.isValid())
+            session = null;
+        if (session != null)
+            return (session);
+
+        // Return the requested session if it exists and is valid
+        Manager manager = null;
+        if (context != null)
+            manager = context.getManager();
+        if (manager == null)
+            return (null);      // Sessions are not supported
+        if (requestedSessionId != null) {
+            try {
+                session = manager.findSession(requestedSessionId);
+            } catch (IOException e) {
+                session = null;
+            }
+            if ((session != null) && !session.isValid())
+                session = null;
+            if (session != null) {
+                session.access();
+                return (session);
+            }
+        }
+
+        // Create a new session if requested and the response is not committed
+        if (!create)
+            return (null);
+        if ((context != null) && (response != null) &&
+            context.getServletContext().getEffectiveSessionTrackingModes().
+                    contains(SessionTrackingMode.COOKIE) &&
+            response.getResponse().isCommitted()) {
+            throw new IllegalStateException
+              (sm.getString("coyoteRequest.sessionCreateCommitted"));
+        }
+
+        // Attempt to reuse session id if one was submitted in a cookie
+        // Do not reuse the session id if it is from a URL, to prevent possible
+        // phishing attacks
+        // Use the SSL session ID if one is present. 
+        if (("/".equals(context.getSessionCookiePath()) 
+                && isRequestedSessionIdFromCookie()) || requestedSessionSSL ) {
+            session = manager.createSession(getRequestedSessionId());
+        } else {
+            session = manager.createSession(null);
+        }
+
+        // Creating a new session cookie based on that session
+        if ((session != null) && (getContext() != null)
+               && getContext().getServletContext().
+                       getEffectiveSessionTrackingModes().contains(
+                               SessionTrackingMode.COOKIE)) {
+            Cookie cookie =
+                ApplicationSessionCookieConfig.createSessionCookie(
+                        context, session.getIdInternal(), isSecure());
+            
+            response.addSessionCookieInternal(cookie);
+        }
+
+        if (session == null) {
+            return null;
+        }
+        
+        session.access();
+        return session;
+    }
+
+    protected String unescape(String s) {
+        if (s==null) return null;
+        if (s.indexOf('\\') == -1) return s;
+        StringBuilder buf = new StringBuilder();
+        for (int i=0; i<s.length(); i++) {
+            char c = s.charAt(i);
+            if (c!='\\') buf.append(c);
+            else {
+                if (++i >= s.length()) throw new IllegalArgumentException();//invalid escape, hence invalid cookie
+                c = s.charAt(i);
+                buf.append(c);
+            }
+        }
+        return buf.toString();
+    }
+
+    /**
+     * Parse cookies.
+     */
+    protected void parseCookies() {
+
+        cookiesParsed = true;
+
+        Cookies serverCookies = coyoteRequest.getCookies();
+        int count = serverCookies.getCookieCount();
+        if (count <= 0)
+            return;
+
+        cookies = new Cookie[count];
+
+        int idx=0;
+        for (int i = 0; i < count; i++) {
+            ServerCookie scookie = serverCookies.getCookie(i);
+            try {
+                /*
+                we must unescape the '\\' escape character
+                */
+                Cookie cookie = new Cookie(scookie.getName().toString(),null);
+                int version = scookie.getVersion();
+                cookie.setVersion(version);
+                cookie.setValue(unescape(scookie.getValue().toString()));
+                cookie.setPath(unescape(scookie.getPath().toString()));
+                String domain = scookie.getDomain().toString();
+                if (domain!=null) cookie.setDomain(unescape(domain));//avoid NPE
+                String comment = scookie.getComment().toString();
+                cookie.setComment(version==1?unescape(comment):null);
+                cookies[idx++] = cookie;
+            } catch(IllegalArgumentException e) {
+                // Ignore bad cookie
+            }
+        }
+        if( idx < count ) {
+            Cookie [] ncookies = new Cookie[idx];
+            System.arraycopy(cookies, 0, ncookies, 0, idx);
+            cookies = ncookies;
+        }
+
+    }
+
+    /**
+     * Parse request parameters.
+     */
+    protected void parseParameters() {
+
+        parametersParsed = true;
+
+        Parameters parameters = coyoteRequest.getParameters();
+
+        // getCharacterEncoding() may have been overridden to search for
+        // hidden form field containing request encoding
+        String enc = getCharacterEncoding();
+
+        boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI();
+        if (enc != null) {
+            parameters.setEncoding(enc);
+            if (useBodyEncodingForURI) {
+                parameters.setQueryStringEncoding(enc);
+            }
+        } else {
+            parameters.setEncoding
+                (org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING);
+            if (useBodyEncodingForURI) {
+                parameters.setQueryStringEncoding
+                    (org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING);
+            }
+        }
+
+        parameters.handleQueryParameters();
+
+        if (usingInputStream || usingReader)
+            return;
+
+        if( !getConnector().isParseBodyMethod(getMethod()) )
+            return;
+
+        String contentType = getContentType();
+        if (contentType == null)
+            contentType = "";
+        int semicolon = contentType.indexOf(';');
+        if (semicolon >= 0) {
+            contentType = contentType.substring(0, semicolon).trim();
+        } else {
+            contentType = contentType.trim();
+        }
+        
+        if ("multipart/form-data".equals(contentType)) {
+            parseParts();
+            return;
+        }
+        
+        if (!("application/x-www-form-urlencoded".equals(contentType)))
+            return;
+
+        int len = getContentLength();
+
+        if (len > 0) {
+            int maxPostSize = connector.getMaxPostSize();
+            if ((maxPostSize > 0) && (len > maxPostSize)) {
+                if (context.getLogger().isDebugEnabled()) {
+                    context.getLogger().debug(
+                            sm.getString("coyoteRequest.postTooLarge"));
+                }
+                checkSwallowInput();
+                return;
+            }
+            byte[] formData = null;
+            if (len < CACHED_POST_LEN) {
+                if (postData == null)
+                    postData = new byte[CACHED_POST_LEN];
+                formData = postData;
+            } else {
+                formData = new byte[len];
+            }
+            try {
+                if (readPostBody(formData, len) != len) {
+                    return;
+                }
+            } catch (IOException e) {
+                // Client disconnect
+                if (context.getLogger().isDebugEnabled()) {
+                    context.getLogger().debug(
+                            sm.getString("coyoteRequest.parseParameters"), e);
+                }
+                return;
+            }
+            parameters.processParameters(formData, 0, len);
+        } else if ("chunked".equalsIgnoreCase(
+                coyoteRequest.getHeader("transfer-encoding"))) {
+            byte[] formData = null;
+            try {
+                formData = readChunkedPostBody();
+            } catch (IOException e) {
+                // Client disconnect
+                if (context.getLogger().isDebugEnabled()) {
+                    context.getLogger().debug(
+                            sm.getString("coyoteRequest.parseParameters"), e);
+                }
+                return;
+            }
+            if (formData != null) {
+                parameters.processParameters(formData, 0, formData.length);
+            }
+        }
+
+    }
+
+
+    /**
+     * Read post body in an array.
+     */
+    protected int readPostBody(byte body[], int len)
+        throws IOException {
+
+        int offset = 0;
+        do {
+            int inputLen = getStream().read(body, offset, len - offset);
+            if (inputLen <= 0) {
+                return offset;
+            }
+            offset += inputLen;
+        } while ((len - offset) > 0);
+        return len;
+
+    }
+
+
+    /**
+     * Read chunked post body.
+     */
+    protected byte[] readChunkedPostBody() throws IOException {
+        ByteChunk body = new ByteChunk();
+        
+        byte[] buffer = new byte[CACHED_POST_LEN];
+        
+        int len = 0;
+        while (len > -1) {
+            len = getStream().read(buffer, 0, CACHED_POST_LEN);
+            if (connector.getMaxPostSize() > 0 &&
+                    (body.getLength() + len) > connector.getMaxPostSize()) {
+                // Too much data
+                checkSwallowInput();
+                throw new IllegalArgumentException(
+                        sm.getString("coyoteRequest.chunkedPostTooLarge"));
+            }
+            if (len > 0) {
+                body.append(buffer, 0, len);
+            }
+        }
+        if (body.getLength() == 0) {
+            return null;
+        }
+        if (body.getLength() < body.getBuffer().length) {
+            int length = body.getLength();
+            byte[] result = new byte[length];
+            System.arraycopy(body.getBuffer(), 0, result, 0, length);
+            return result;
+        }
+        
+        return body.getBuffer();
+    }
+    
+    
+    /**
+     * Parse request locales.
+     */
+    protected void parseLocales() {
+
+        localesParsed = true;
+
+        Enumeration<String> values = getHeaders("accept-language");
+
+        while (values.hasMoreElements()) {
+            String value = values.nextElement();
+            parseLocalesHeader(value);
+        }
+
+    }
+
+
+    /**
+     * Parse accept-language header value.
+     */
+    protected void parseLocalesHeader(String value) {
+
+        // Store the accumulated languages that have been requested in
+        // a local collection, sorted by the quality value (so we can
+        // add Locales in descending order).  The values will be ArrayLists
+        // containing the corresponding Locales to be added
+        TreeMap<Double, ArrayList<Locale>> locales = new TreeMap<Double, ArrayList<Locale>>();
+
+        // Preprocess the value to remove all whitespace
+        int white = value.indexOf(' ');
+        if (white < 0)
+            white = value.indexOf('\t');
+        if (white >= 0) {
+            StringBuilder sb = new StringBuilder();
+            int len = value.length();
+            for (int i = 0; i < len; i++) {
+                char ch = value.charAt(i);
+                if ((ch != ' ') && (ch != '\t'))
+                    sb.append(ch);
+            }
+            parser.setString(sb.toString());
+        } else {
+            parser.setString(value);
+        }
+
+        // Process each comma-delimited language specification
+        int length = parser.getLength();
+        while (true) {
+
+            // Extract the next comma-delimited entry
+            int start = parser.getIndex();
+            if (start >= length)
+                break;
+            int end = parser.findChar(',');
+            String entry = parser.extract(start, end).trim();
+            parser.advance();   // For the following entry
+
+            // Extract the quality factor for this entry
+            double quality = 1.0;
+            int semi = entry.indexOf(";q=");
+            if (semi >= 0) {
+                try {
+                    String strQuality = entry.substring(semi + 3);
+                    if (strQuality.length() <= 5) {
+                        quality = Double.parseDouble(strQuality);
+                    } else {
+                        quality = 0.0;
+                    }
+                } catch (NumberFormatException e) {
+                    quality = 0.0;
+                }
+                entry = entry.substring(0, semi);
+            }
+
+            // Skip entries we are not going to keep track of
+            if (quality < 0.00005)
+                continue;       // Zero (or effectively zero) quality factors
+            if ("*".equals(entry))
+                continue;       // FIXME - "*" entries are not handled
+
+            // Extract the language and country for this entry
+            String language = null;
+            String country = null;
+            String variant = null;
+            int dash = entry.indexOf('-');
+            if (dash < 0) {
+                language = entry;
+                country = "";
+                variant = "";
+            } else {
+                language = entry.substring(0, dash);
+                country = entry.substring(dash + 1);
+                int vDash = country.indexOf('-');
+                if (vDash > 0) {
+                    String cTemp = country.substring(0, vDash);
+                    variant = country.substring(vDash + 1);
+                    country = cTemp;
+                } else {
+                    variant = "";
+                }
+            }
+            if (!isAlpha(language) || !isAlpha(country) || !isAlpha(variant)) {
+                continue;
+            }
+
+            // Add a new Locale to the list of Locales for this quality level
+            Locale locale = new Locale(language, country, variant);
+            Double key = new Double(-quality);  // Reverse the order
+            ArrayList<Locale> values = locales.get(key);
+            if (values == null) {
+                values = new ArrayList<Locale>();
+                locales.put(key, values);
+            }
+            values.add(locale);
+
+        }
+
+        // Process the quality values in highest->lowest order (due to
+        // negating the Double value when creating the key)
+        for (ArrayList<Locale> list : locales.values()) {
+            for (Locale locale : list) {
+                addLocale(locale);
+            }
+        }
+
+    }
+
+    
+    protected static final boolean isAlpha(String value) {
+        for (int i = 0; i < value.length(); i++) {
+            char c = value.charAt(i);
+            if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/connector/RequestFacade.java b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/RequestFacade.java
new file mode 100644
index 0000000..4215ee1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/RequestFacade.java
@@ -0,0 +1,1090 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.connector;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.Part;
+
+import org.apache.catalina.Globals;
+import org.apache.catalina.security.SecurityUtil;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Facade class that wraps a Coyote request object.  
+ * All methods are delegated to the wrapped request.
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @author Jean-Francois Arcand
+ * @version $Id: RequestFacade.java,v 1.1 2011/06/28 21:08:19 rherrmann Exp $
+ */
+
+@SuppressWarnings("deprecation")
+public class RequestFacade implements HttpServletRequest {
+        
+        
+    // ----------------------------------------------------------- DoPrivileged
+    
+    private final class GetAttributePrivilegedAction
+            implements PrivilegedAction<Enumeration<String>> {
+        
+        @Override
+        public Enumeration<String> run() {
+            return request.getAttributeNames();
+        }            
+    }
+     
+    
+    private final class GetParameterMapPrivilegedAction
+            implements PrivilegedAction<Map<String,String[]>> {
+        
+        @Override
+        public Map<String,String[]> run() {
+            return request.getParameterMap();
+        }        
+    }    
+    
+    
+    private final class GetRequestDispatcherPrivilegedAction
+            implements PrivilegedAction<RequestDispatcher> {
+
+        private String path;
+
+        public GetRequestDispatcherPrivilegedAction(String path){
+            this.path = path;
+        }
+        
+        @Override
+        public RequestDispatcher run() {   
+            return request.getRequestDispatcher(path);
+        }           
+    }    
+    
+    
+    private final class GetParameterPrivilegedAction
+            implements PrivilegedAction<String> {
+
+        public String name;
+
+        public GetParameterPrivilegedAction(String name){
+            this.name = name;
+        }
+
+        @Override
+        public String run() {       
+            return request.getParameter(name);
+        }           
+    }    
+    
+     
+    private final class GetParameterNamesPrivilegedAction
+            implements PrivilegedAction<Enumeration<String>> {
+        
+        @Override
+        public Enumeration<String> run() {          
+            return request.getParameterNames();
+        }           
+    } 
+    
+    
+    private final class GetParameterValuePrivilegedAction
+            implements PrivilegedAction<String[]> {
+
+        public String name;
+
+        public GetParameterValuePrivilegedAction(String name){
+            this.name = name;
+        }
+
+        @Override
+        public String[] run() {       
+            return request.getParameterValues(name);
+        }           
+    }    
+  
+    
+    private final class GetCookiesPrivilegedAction
+            implements PrivilegedAction<Cookie[]> {
+        
+        @Override
+        public Cookie[] run() {       
+            return request.getCookies();
+        }           
+    }      
+    
+    
+    private final class GetCharacterEncodingPrivilegedAction
+            implements PrivilegedAction<String> {
+        
+        @Override
+        public String run() {       
+            return request.getCharacterEncoding();
+        }           
+    }   
+        
+    
+    private final class GetHeadersPrivilegedAction
+            implements PrivilegedAction<Enumeration<String>> {
+
+        private String name;
+
+        public GetHeadersPrivilegedAction(String name){
+            this.name = name;
+        }
+        
+        @Override
+        public Enumeration<String> run() {       
+            return request.getHeaders(name);
+        }           
+    }    
+        
+    
+    private final class GetHeaderNamesPrivilegedAction
+            implements PrivilegedAction<Enumeration<String>> {
+
+        @Override
+        public Enumeration<String> run() {       
+            return request.getHeaderNames();
+        }           
+    }  
+            
+    
+    private final class GetLocalePrivilegedAction
+            implements PrivilegedAction<Locale> {
+
+        @Override
+        public Locale run() {       
+            return request.getLocale();
+        }           
+    }    
+            
+    
+    private final class GetLocalesPrivilegedAction
+            implements PrivilegedAction<Enumeration<Locale>> {
+
+        @Override
+        public Enumeration<Locale> run() {       
+            return request.getLocales();
+        }           
+    }    
+    
+    private final class GetSessionPrivilegedAction
+            implements PrivilegedAction<HttpSession> {
+
+        private boolean create;
+        
+        public GetSessionPrivilegedAction(boolean create){
+            this.create = create;
+        }
+                
+        @Override
+        public HttpSession run() {  
+            return request.getSession(create);
+        }           
+    }
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a wrapper for the specified request.
+     *
+     * @param request The request to be wrapped
+     */
+    public RequestFacade(Request request) {
+
+        this.request = request;
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The wrapped request.
+     */
+    protected Request request = null;
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Clear facade.
+     */
+    public void clear() {
+        request = null;
+    }
+
+    
+    /**
+     * Prevent cloning the facade.
+     */
+    @Override
+    protected Object clone()
+        throws CloneNotSupportedException {
+        throw new CloneNotSupportedException();
+    }
+
+
+    // ------------------------------------------------- ServletRequest Methods
+
+
+    @Override
+    public Object getAttribute(String name) {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getAttribute(name);
+    }
+
+
+    @Override
+    public Enumeration<String> getAttributeNames() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        if (Globals.IS_SECURITY_ENABLED){
+            return AccessController.doPrivileged(
+                new GetAttributePrivilegedAction());        
+        } else {
+            return request.getAttributeNames();
+        }
+    }
+
+
+    @Override
+    public String getCharacterEncoding() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        if (Globals.IS_SECURITY_ENABLED){
+            return AccessController.doPrivileged(
+                new GetCharacterEncodingPrivilegedAction());
+        } else {
+            return request.getCharacterEncoding();
+        }         
+    }
+
+
+    @Override
+    public void setCharacterEncoding(String env)
+            throws java.io.UnsupportedEncodingException {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        request.setCharacterEncoding(env);
+    }
+
+
+    @Override
+    public int getContentLength() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getContentLength();
+    }
+
+
+    @Override
+    public String getContentType() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getContentType();
+    }
+
+
+    @Override
+    public ServletInputStream getInputStream() throws IOException {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getInputStream();
+    }
+
+
+    @Override
+    public String getParameter(String name) {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        if (Globals.IS_SECURITY_ENABLED){
+            return AccessController.doPrivileged(
+                new GetParameterPrivilegedAction(name));
+        } else {
+            return request.getParameter(name);
+        }
+    }
+
+
+    @Override
+    public Enumeration<String> getParameterNames() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        if (Globals.IS_SECURITY_ENABLED){
+            return AccessController.doPrivileged(
+                new GetParameterNamesPrivilegedAction());
+        } else {
+            return request.getParameterNames();
+        }
+    }
+
+
+    @Override
+    public String[] getParameterValues(String name) {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        String[] ret = null;
+
+        /*
+         * Clone the returned array only if there is a security manager
+         * in place, so that performance won't suffer in the non-secure case
+         */
+        if (SecurityUtil.isPackageProtectionEnabled()){
+            ret = AccessController.doPrivileged(
+                new GetParameterValuePrivilegedAction(name));
+            if (ret != null) {
+                ret = ret.clone();
+            }
+        } else {
+            ret = request.getParameterValues(name);
+        }
+
+        return ret;
+    }
+
+
+    @Override
+    public Map<String,String[]> getParameterMap() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        if (Globals.IS_SECURITY_ENABLED){
+            return AccessController.doPrivileged(
+                new GetParameterMapPrivilegedAction());        
+        } else {
+            return request.getParameterMap();
+        }
+    }
+
+
+    @Override
+    public String getProtocol() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getProtocol();
+    }
+
+
+    @Override
+    public String getScheme() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getScheme();
+    }
+
+
+    @Override
+    public String getServerName() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getServerName();
+    }
+
+
+    @Override
+    public int getServerPort() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getServerPort();
+    }
+
+
+    @Override
+    public BufferedReader getReader() throws IOException {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getReader();
+    }
+
+
+    @Override
+    public String getRemoteAddr() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getRemoteAddr();
+    }
+
+
+    @Override
+    public String getRemoteHost() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getRemoteHost();
+    }
+
+
+    @Override
+    public void setAttribute(String name, Object o) {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        request.setAttribute(name, o);
+    }
+
+
+    @Override
+    public void removeAttribute(String name) {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        request.removeAttribute(name);
+    }
+
+
+    @Override
+    public Locale getLocale() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        if (Globals.IS_SECURITY_ENABLED){
+            return AccessController.doPrivileged(
+                new GetLocalePrivilegedAction());
+        } else {
+            return request.getLocale();
+        }        
+    }
+
+
+    @Override
+    public Enumeration<Locale> getLocales() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        if (Globals.IS_SECURITY_ENABLED){
+            return AccessController.doPrivileged(
+                new GetLocalesPrivilegedAction());
+        } else {
+            return request.getLocales();
+        }        
+    }
+
+
+    @Override
+    public boolean isSecure() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.isSecure();
+    }
+
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(String path) {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        if (Globals.IS_SECURITY_ENABLED){
+            return AccessController.doPrivileged(
+                new GetRequestDispatcherPrivilegedAction(path));
+        } else {
+            return request.getRequestDispatcher(path);
+        }
+    }
+
+    @Override
+    public String getRealPath(String path) {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getRealPath(path);
+    }
+
+
+    @Override
+    public String getAuthType() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getAuthType();
+    }
+
+
+    @Override
+    public Cookie[] getCookies() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        Cookie[] ret = null;
+
+        /*
+         * Clone the returned array only if there is a security manager
+         * in place, so that performance won't suffer in the non-secure case
+         */
+        if (SecurityUtil.isPackageProtectionEnabled()){
+            ret = AccessController.doPrivileged(
+                new GetCookiesPrivilegedAction());
+            if (ret != null) {
+                ret = ret.clone();
+            }
+        } else {
+            ret = request.getCookies();
+        }
+
+        return ret;
+    }
+
+
+    @Override
+    public long getDateHeader(String name) {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getDateHeader(name);
+    }
+
+
+    @Override
+    public String getHeader(String name) {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getHeader(name);
+    }
+
+
+    @Override
+    public Enumeration<String> getHeaders(String name) {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        if (Globals.IS_SECURITY_ENABLED){
+            return AccessController.doPrivileged(
+                new GetHeadersPrivilegedAction(name));
+        } else {
+            return request.getHeaders(name);
+        }         
+    }
+
+
+    @Override
+    public Enumeration<String> getHeaderNames() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        if (Globals.IS_SECURITY_ENABLED){
+            return AccessController.doPrivileged(
+                new GetHeaderNamesPrivilegedAction());
+        } else {
+            return request.getHeaderNames();
+        }             
+    }
+
+
+    @Override
+    public int getIntHeader(String name) {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getIntHeader(name);
+    }
+
+
+    @Override
+    public String getMethod() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getMethod();
+    }
+
+
+    @Override
+    public String getPathInfo() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getPathInfo();
+    }
+
+
+    @Override
+    public String getPathTranslated() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getPathTranslated();
+    }
+
+
+    @Override
+    public String getContextPath() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getContextPath();
+    }
+
+
+    @Override
+    public String getQueryString() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getQueryString();
+    }
+
+
+    @Override
+    public String getRemoteUser() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getRemoteUser();
+    }
+
+
+    @Override
+    public boolean isUserInRole(String role) {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.isUserInRole(role);
+    }
+
+
+    @Override
+    public java.security.Principal getUserPrincipal() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getUserPrincipal();
+    }
+
+
+    @Override
+    public String getRequestedSessionId() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getRequestedSessionId();
+    }
+
+
+    @Override
+    public String getRequestURI() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getRequestURI();
+    }
+
+
+    @Override
+    public StringBuffer getRequestURL() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getRequestURL();
+    }
+
+
+    @Override
+    public String getServletPath() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getServletPath();
+    }
+
+
+    @Override
+    public HttpSession getSession(boolean create) {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        if (SecurityUtil.isPackageProtectionEnabled()){
+            return AccessController.
+                doPrivileged(new GetSessionPrivilegedAction(create));
+        } else {
+            return request.getSession(create);
+        }
+    }
+
+    @Override
+    public HttpSession getSession() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return getSession(true);
+    }
+
+
+    @Override
+    public boolean isRequestedSessionIdValid() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.isRequestedSessionIdValid();
+    }
+
+
+    @Override
+    public boolean isRequestedSessionIdFromCookie() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.isRequestedSessionIdFromCookie();
+    }
+
+
+    @Override
+    public boolean isRequestedSessionIdFromURL() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.isRequestedSessionIdFromURL();
+    }
+
+
+    @Override
+    public boolean isRequestedSessionIdFromUrl() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.isRequestedSessionIdFromURL();
+    }
+
+
+    @Override
+    public String getLocalAddr() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getLocalAddr();
+    }
+
+
+    @Override
+    public String getLocalName() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getLocalName();
+    }
+
+
+    @Override
+    public int getLocalPort() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getLocalPort();
+    }
+
+
+    @Override
+    public int getRemotePort() {
+
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getRemotePort();
+    }
+
+
+    @Override
+    public ServletContext getServletContext() {
+        if (request == null) {
+            throw new IllegalStateException(
+                            sm.getString("requestFacade.nullRequest"));
+        }
+
+        return request.getServletContext();
+    }
+
+
+    @Override
+    public AsyncContext startAsync() throws IllegalStateException {
+        return request.startAsync();
+    }
+
+
+    @Override
+    public AsyncContext startAsync(ServletRequest request, ServletResponse response)
+    throws IllegalStateException {
+        return this.request.startAsync(request, response);
+    }
+
+
+    @Override
+    public boolean isAsyncStarted() {
+        return request.isAsyncStarted();
+    }
+
+
+    @Override
+    public boolean isAsyncSupported() {
+        return request.isAsyncSupported();
+    }
+
+    
+    @Override
+    public AsyncContext getAsyncContext() {
+        return request.getAsyncContext();
+    }
+
+    @Override
+    public DispatcherType getDispatcherType() {
+        return request.getDispatcherType();
+    }
+    
+    @Override
+    public boolean authenticate(HttpServletResponse response)
+    throws IOException, ServletException {
+        return request.authenticate(response);
+    }
+
+    @Override
+    public void login(String username, String password)
+    throws ServletException {
+        request.login(username, password);
+    }
+    
+    @Override
+    public void logout() throws ServletException {
+        request.logout();
+    }
+    
+    @Override
+    public Collection<Part> getParts() throws IllegalStateException,
+            IOException, ServletException {
+        return request.getParts();
+    }
+    
+    @Override
+    public Part getPart(String name) throws IllegalStateException, IOException,
+            ServletException {
+        return request.getPart(name);
+    }
+
+    public boolean getAllowTrace() {
+        return request.getConnector().getAllowTrace();
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/connector/Response.java b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/Response.java
new file mode 100644
index 0000000..99feaad
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/Response.java
@@ -0,0 +1,1707 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.connector;
+
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.Vector;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Globals;
+import org.apache.catalina.Session;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.core.ApplicationSessionCookieConfig;
+import org.apache.catalina.security.SecurityUtil;
+import org.apache.catalina.util.CharsetMapper;
+import org.apache.catalina.util.DateTool;
+import org.apache.tomcat.util.buf.CharChunk;
+import org.apache.tomcat.util.buf.UEncoder;
+import org.apache.tomcat.util.http.FastHttpDateFormat;
+import org.apache.tomcat.util.http.MimeHeaders;
+import org.apache.tomcat.util.http.ServerCookie;
+import org.apache.tomcat.util.net.URL;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Wrapper object for the Coyote response.
+ *
+ * @author Remy Maucherat
+ * @author Craig R. McClanahan
+ * @version $Id: Response.java,v 1.1 2011/06/28 21:08:19 rherrmann Exp $
+ */
+
+public class Response
+    implements HttpServletResponse {
+
+
+    // ----------------------------------------------------------- Constructors
+
+    /**
+     * Compliance with SRV.15.2.22.1. A call to Response.getWriter() if no
+     * character encoding has been specified will result in subsequent calls to
+     * Response.getCharacterEncoding() returning ISO-8859-1 and the Content-Type
+     * response header will include a charset=ISO-8859-1 component.
+     */
+    private static final boolean ENFORCE_ENCODING_IN_GET_WRITER;
+
+    static {
+        // Ensure that URL is loaded for SM
+        URL.isSchemeChar('c');
+
+        ENFORCE_ENCODING_IN_GET_WRITER = Boolean.valueOf(
+                System.getProperty("org.apache.catalina.connector.Response.ENFORCE_ENCODING_IN_GET_WRITER",
+                        "true")).booleanValue();
+    }
+
+    public Response() {
+        urlEncoder.addSafeCharacter('/');
+    }
+
+
+    // ----------------------------------------------------- Class Variables
+
+
+    /**
+     * Descriptive information about this Response implementation.
+     */
+    protected static final String info =
+        "org.apache.coyote.catalina.CoyoteResponse/1.0";
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * The date format we will use for creating date headers.
+     */
+    protected SimpleDateFormat format = null;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Associated Catalina connector.
+     */
+    protected Connector connector;
+
+    /**
+     * Return the Connector through which this Request was received.
+     */
+    public Connector getConnector() {
+        return (this.connector);
+    }
+
+    /**
+     * Set the Connector through which this Request was received.
+     *
+     * @param connector The new connector
+     */
+    public void setConnector(Connector connector) {
+        this.connector = connector;
+        if("AJP/1.3".equals(connector.getProtocol())) {
+            // default size to size of one ajp-packet
+            outputBuffer = new OutputBuffer(8184);
+        } else {
+            outputBuffer = new OutputBuffer();
+        }
+        outputStream = new CoyoteOutputStream(outputBuffer);
+        writer = new CoyoteWriter(outputBuffer);
+    }
+
+
+    /**
+     * Coyote response.
+     */
+    protected org.apache.coyote.Response coyoteResponse;
+
+    /**
+     * Set the Coyote response.
+     * 
+     * @param coyoteResponse The Coyote response
+     */
+    public void setCoyoteResponse(org.apache.coyote.Response coyoteResponse) {
+        this.coyoteResponse = coyoteResponse;
+        outputBuffer.setResponse(coyoteResponse);
+    }
+
+    /**
+     * Get the Coyote response.
+     */
+    public org.apache.coyote.Response getCoyoteResponse() {
+        return (coyoteResponse);
+    }
+
+
+    /**
+     * Return the Context within which this Request is being processed.
+     */
+    public Context getContext() {
+        return (request.getContext());
+    }
+
+    /**
+     * Set the Context within which this Request is being processed.  This
+     * must be called as soon as the appropriate Context is identified, because
+     * it identifies the value to be returned by <code>getContextPath()</code>,
+     * and thus enables parsing of the request URI.
+     *
+     * @param context The newly associated Context
+     */
+    public void setContext(Context context) {
+        request.setContext(context);
+    }
+
+
+    /**
+     * The associated output buffer.
+     */
+    protected OutputBuffer outputBuffer;
+
+
+    /**
+     * The associated output stream.
+     */
+    protected CoyoteOutputStream outputStream;
+
+
+    /**
+     * The associated writer.
+     */
+    protected CoyoteWriter writer;
+
+
+    /**
+     * The application commit flag.
+     */
+    protected boolean appCommitted = false;
+
+
+    /**
+     * The included flag.
+     */
+    protected boolean included = false;
+
+    
+    /**
+     * The characterEncoding flag
+     */
+    private boolean isCharacterEncodingSet = false;
+    
+    /**
+     * The error flag.
+     */
+    protected boolean error = false;
+
+
+    /**
+     * Using output stream flag.
+     */
+    protected boolean usingOutputStream = false;
+
+
+    /**
+     * Using writer flag.
+     */
+    protected boolean usingWriter = false;
+
+
+    /**
+     * URL encoder.
+     */
+    protected UEncoder urlEncoder = new UEncoder();
+
+
+    /**
+     * Recyclable buffer to hold the redirect URL.
+     */
+    protected CharChunk redirectURLCC = new CharChunk();
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Release all object references, and initialize instance variables, in
+     * preparation for reuse of this object.
+     */
+    public void recycle() {
+
+        outputBuffer.recycle();
+        usingOutputStream = false;
+        usingWriter = false;
+        appCommitted = false;
+        included = false;
+        error = false;
+        isCharacterEncodingSet = false;
+        
+        if (Globals.IS_SECURITY_ENABLED || Connector.RECYCLE_FACADES) {
+            if (facade != null) {
+                facade.clear();
+                facade = null;
+            }
+            if (outputStream != null) {
+                outputStream.clear();
+                outputStream = null;
+            }
+            if (writer != null) {
+                writer.clear();
+                writer = null;
+            }
+        } else {
+            writer.recycle();
+        }
+
+    }
+
+
+    /**
+     * Clear cached encoders (to save memory for Comet requests).
+     */
+    public void clearEncoders() {
+        outputBuffer.clearEncoders();
+    }
+    
+
+    // ------------------------------------------------------- Response Methods
+
+
+    /**
+     * Return the number of bytes the application has actually written to the
+     * output stream. This excludes chunking, compression, etc. as well as
+     * headers.
+     */
+    public long getContentWritten() {
+        return outputBuffer.getContentWritten();
+    }
+
+
+    /**
+     * Return the number of bytes the actually written to the socket. This
+     * includes chunking, compression, etc. but excludes headers.
+     */
+    public long getBytesWritten(boolean flush) {
+        if (flush) {
+            try {
+                outputBuffer.flush();
+            } catch (IOException ioe) {
+                // Ignore - the client has probably closed the connection
+            }
+        }
+        return coyoteResponse.getBytesWritten(flush);
+    }
+
+    /**
+     * Set the application commit flag.
+     * 
+     * @param appCommitted The new application committed flag value
+     */
+    public void setAppCommitted(boolean appCommitted) {
+        this.appCommitted = appCommitted;
+    }
+
+
+    /**
+     * Application commit flag accessor.
+     */
+    public boolean isAppCommitted() {
+        return (this.appCommitted || isCommitted() || isSuspended()
+                || ((getContentLength() > 0) 
+                    && (getContentWritten() >= getContentLength())));
+    }
+
+
+    /**
+     * Return the "processing inside an include" flag.
+     */
+    public boolean getIncluded() {
+        return included;
+    }
+
+
+    /**
+     * Set the "processing inside an include" flag.
+     *
+     * @param included <code>true</code> if we are currently inside a
+     *  RequestDispatcher.include(), else <code>false</code>
+     */
+    public void setIncluded(boolean included) {
+        this.included = included;
+    }
+
+
+    /**
+     * Return descriptive information about this Response implementation and
+     * the corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    public String getInfo() {
+        return (info);
+    }
+
+
+    /**
+     * The request with which this response is associated.
+     */
+    protected Request request = null;
+
+    /**
+     * Return the Request with which this Response is associated.
+     */
+    public org.apache.catalina.connector.Request getRequest() {
+        return (this.request);
+    }
+
+    /**
+     * Set the Request with which this Response is associated.
+     *
+     * @param request The new associated request
+     */
+    public void setRequest(org.apache.catalina.connector.Request request) {
+        this.request = request;
+    }
+
+
+    /**
+     * The facade associated with this response.
+     */
+    protected ResponseFacade facade = null;
+
+    /**
+     * Return the <code>ServletResponse</code> for which this object
+     * is the facade.
+     */
+    public HttpServletResponse getResponse() {
+        if (facade == null) {
+            facade = new ResponseFacade(this);
+        }
+        return (facade);
+    }
+
+
+    /**
+     * Return the output stream associated with this Response.
+     */
+    public OutputStream getStream() {
+        if (outputStream == null) {
+            outputStream = new CoyoteOutputStream(outputBuffer);
+        }
+        return outputStream;
+    }
+
+
+    /**
+     * Set the suspended flag.
+     * 
+     * @param suspended The new suspended flag value
+     */
+    public void setSuspended(boolean suspended) {
+        outputBuffer.setSuspended(suspended);
+    }
+
+
+    /**
+     * Suspended flag accessor.
+     */
+    public boolean isSuspended() {
+        return outputBuffer.isSuspended();
+    }
+
+
+    /**
+     * Closed flag accessor.
+     */
+    public boolean isClosed() {
+        return outputBuffer.isClosed();
+    }
+
+
+    /**
+     * Set the error flag.
+     */
+    public void setError() {
+        error = true;
+    }
+
+
+    /**
+     * Error flag accessor.
+     */
+    public boolean isError() {
+        return error;
+    }
+
+
+    /**
+     * Create and return a ServletOutputStream to write the content
+     * associated with this Response.
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public ServletOutputStream createOutputStream() 
+        throws IOException {
+        // Probably useless
+        if (outputStream == null) {
+            outputStream = new CoyoteOutputStream(outputBuffer);
+        }
+        return outputStream;
+    }
+
+
+    /**
+     * Perform whatever actions are required to flush and close the output
+     * stream or writer, in a single operation.
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public void finishResponse() 
+        throws IOException {
+        // Writing leftover bytes
+        outputBuffer.close();
+    }
+
+
+    /**
+     * Return the content length that was set or calculated for this Response.
+     */
+    public int getContentLength() {
+        return (coyoteResponse.getContentLength());
+    }
+
+
+    /**
+     * Return the content type that was set or calculated for this response,
+     * or <code>null</code> if no content type was set.
+     */
+    @Override
+    public String getContentType() {
+        return (coyoteResponse.getContentType());
+    }
+
+
+    /**
+     * Return a PrintWriter that can be used to render error messages,
+     * regardless of whether a stream or writer has already been acquired.
+     *
+     * @return Writer which can be used for error reports. If the response is
+     * not an error report returned using sendError or triggered by an
+     * unexpected exception thrown during the servlet processing
+     * (and only in that case), null will be returned if the response stream
+     * has already been used.
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public PrintWriter getReporter() throws IOException {
+        if (outputBuffer.isNew()) {
+            outputBuffer.checkConverter();
+            if (writer == null) {
+                writer = new CoyoteWriter(outputBuffer);
+            }
+            return writer;
+        } else {
+            return null;
+        }
+    }
+
+
+    // ------------------------------------------------ ServletResponse Methods
+
+
+    /**
+     * Flush the buffer and commit this response.
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public void flushBuffer() 
+        throws IOException {
+        outputBuffer.flush();
+    }
+
+
+    /**
+     * Return the actual buffer size used for this Response.
+     */
+    @Override
+    public int getBufferSize() {
+        return outputBuffer.getBufferSize();
+    }
+
+
+    /**
+     * Return the character encoding used for this Response.
+     */
+    @Override
+    public String getCharacterEncoding() {
+        return (coyoteResponse.getCharacterEncoding());
+    }
+
+
+    /**
+     * Return the servlet output stream associated with this Response.
+     *
+     * @exception IllegalStateException if <code>getWriter</code> has
+     *  already been called for this response
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public ServletOutputStream getOutputStream() 
+        throws IOException {
+
+        if (usingWriter)
+            throw new IllegalStateException
+                (sm.getString("coyoteResponse.getOutputStream.ise"));
+
+        usingOutputStream = true;
+        if (outputStream == null) {
+            outputStream = new CoyoteOutputStream(outputBuffer);
+        }
+        return outputStream;
+
+    }
+
+
+    /**
+     * Return the Locale assigned to this response.
+     */
+    @Override
+    public Locale getLocale() {
+        return (coyoteResponse.getLocale());
+    }
+
+
+    /**
+     * Return the writer associated with this Response.
+     *
+     * @exception IllegalStateException if <code>getOutputStream</code> has
+     *  already been called for this response
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public PrintWriter getWriter() 
+        throws IOException {
+
+        if (usingOutputStream)
+            throw new IllegalStateException
+                (sm.getString("coyoteResponse.getWriter.ise"));
+
+        if (ENFORCE_ENCODING_IN_GET_WRITER) {
+            /*
+             * If the response's character encoding has not been specified as
+             * described in <code>getCharacterEncoding</code> (i.e., the method
+             * just returns the default value <code>ISO-8859-1</code>),
+             * <code>getWriter</code> updates it to <code>ISO-8859-1</code>
+             * (with the effect that a subsequent call to getContentType() will
+             * include a charset=ISO-8859-1 component which will also be
+             * reflected in the Content-Type response header, thereby satisfying
+             * the Servlet spec requirement that containers must communicate the
+             * character encoding used for the servlet response's writer to the
+             * client).
+             */
+            setCharacterEncoding(getCharacterEncoding());
+        }
+
+        usingWriter = true;
+        outputBuffer.checkConverter();
+        if (writer == null) {
+            writer = new CoyoteWriter(outputBuffer);
+        }
+        return writer;
+
+    }
+
+
+    /**
+     * Has the output of this response already been committed?
+     */
+    @Override
+    public boolean isCommitted() {
+        return (coyoteResponse.isCommitted());
+    }
+
+
+    /**
+     * Clear any content written to the buffer.
+     *
+     * @exception IllegalStateException if this response has already
+     *  been committed
+     */
+    @Override
+    public void reset() {
+
+        if (included)
+            return;     // Ignore any call from an included servlet
+
+        coyoteResponse.reset();
+        outputBuffer.reset();
+        usingOutputStream = false;
+        usingWriter = false;
+        isCharacterEncodingSet = false;
+    }
+
+
+    /**
+     * Reset the data buffer but not any status or header information.
+     *
+     * @exception IllegalStateException if the response has already
+     *  been committed
+     */
+    @Override
+    public void resetBuffer() {
+        resetBuffer(false);
+    }
+
+    
+    /**
+     * Reset the data buffer and the using Writer/Stream flags but not any
+     * status or header information.
+     *
+     * @param resetWriterStreamFlags <code>true</code> if the internal
+     *        <code>usingWriter</code>, <code>usingOutputStream</code>,
+     *        <code>isCharacterEncodingSet</code> flags should also be reset
+     * 
+     * @exception IllegalStateException if the response has already
+     *  been committed
+     */
+    public void resetBuffer(boolean resetWriterStreamFlags) {
+
+        if (isCommitted())
+            throw new IllegalStateException
+                (sm.getString("coyoteResponse.resetBuffer.ise"));
+
+        outputBuffer.reset();
+        
+        if(resetWriterStreamFlags) {
+            usingOutputStream = false;
+            usingWriter = false;
+            isCharacterEncodingSet = false;
+        }
+
+    }
+
+    
+    /**
+     * Set the buffer size to be used for this Response.
+     *
+     * @param size The new buffer size
+     *
+     * @exception IllegalStateException if this method is called after
+     *  output has been committed for this response
+     */
+    @Override
+    public void setBufferSize(int size) {
+
+        if (isCommitted() || !outputBuffer.isNew())
+            throw new IllegalStateException
+                (sm.getString("coyoteResponse.setBufferSize.ise"));
+
+        outputBuffer.setBufferSize(size);
+
+    }
+
+
+    /**
+     * Set the content length (in bytes) for this Response.
+     *
+     * @param length The new content length
+     */
+    @Override
+    public void setContentLength(int length) {
+
+        if (isCommitted())
+            return;
+
+        // Ignore any call from an included servlet
+        if (included)
+            return;
+        
+        coyoteResponse.setContentLength(length);
+
+    }
+
+
+    /**
+     * Set the content type for this Response.
+     *
+     * @param type The new content type
+     */
+    @Override
+    @SuppressWarnings("deprecation") // isSpace (deprecated) cannot be replaced by isWhiteSpace
+    public void setContentType(String type) {
+
+        if (isCommitted())
+            return;
+
+        // Ignore any call from an included servlet
+        if (included)
+            return;
+
+        // Ignore charset if getWriter() has already been called
+        if (usingWriter) {
+            if (type != null) {
+                int index = type.indexOf(";");
+                if (index != -1) {
+                    type = type.substring(0, index);
+                }
+            }
+        }
+
+        coyoteResponse.setContentType(type);
+
+        // Check to see if content type contains charset
+        if (type != null) {
+            int index = type.indexOf(";");
+            if (index != -1) {
+                int len = type.length();
+                index++;
+                // N.B. isSpace (deprecated) cannot be replaced by isWhiteSpace
+                while (index < len && Character.isSpace(type.charAt(index))) {
+                    index++;
+                }
+                if (index+7 < len
+                        && type.charAt(index) == 'c'
+                        && type.charAt(index+1) == 'h'
+                        && type.charAt(index+2) == 'a'
+                        && type.charAt(index+3) == 'r'
+                        && type.charAt(index+4) == 's'
+                        && type.charAt(index+5) == 'e'
+                        && type.charAt(index+6) == 't'
+                        && type.charAt(index+7) == '=') {
+                    isCharacterEncodingSet = true;
+                }
+            }
+        }
+    }
+
+
+    /*
+     * Overrides the name of the character encoding used in the body
+     * of the request. This method must be called prior to reading
+     * request parameters or reading input using getReader().
+     *
+     * @param charset String containing the name of the character encoding.
+     */
+    @Override
+    public void setCharacterEncoding(String charset) {
+
+        if (isCommitted())
+            return;
+        
+        // Ignore any call from an included servlet
+        if (included)
+            return;     
+        
+        // Ignore any call made after the getWriter has been invoked
+        // The default should be used
+        if (usingWriter)
+            return;
+
+        coyoteResponse.setCharacterEncoding(charset);
+        isCharacterEncodingSet = true;
+    }
+
+
+    /**
+     * Set the Locale that is appropriate for this response, including
+     * setting the appropriate character encoding.
+     *
+     * @param locale The new locale
+     */
+    @Override
+    public void setLocale(Locale locale) {
+
+        if (isCommitted())
+            return;
+
+        // Ignore any call from an included servlet
+        if (included)
+            return;
+
+        coyoteResponse.setLocale(locale);
+
+        // Ignore any call made after the getWriter has been invoked.
+        // The default should be used
+        if (usingWriter)
+            return;
+
+        if (isCharacterEncodingSet) {
+            return;
+        }
+
+        CharsetMapper cm = getContext().getCharsetMapper();
+        String charset = cm.getCharset( locale );
+        if ( charset != null ){
+            coyoteResponse.setCharacterEncoding(charset);
+        }
+
+    }
+
+
+    // --------------------------------------------------- HttpResponse Methods
+
+
+    /**
+     * Return the value for the specified header, or <code>null</code> if this
+     * header has not been set.  If more than one value was added for this
+     * name, only the first is returned; use {@link #getHeaders(String)} to
+     * retrieve all of them.
+     *
+     * @param name Header name to look up
+     */
+    @Override
+    public String getHeader(String name) {
+        return coyoteResponse.getMimeHeaders().getHeader(name);
+    }
+
+
+    /**
+     * Return an Iterable of all the header names set for this response.
+     */
+    @Override
+    public Collection<String> getHeaderNames() {
+
+        MimeHeaders headers = coyoteResponse.getMimeHeaders();
+        int n = headers.size();
+        List<String> result = new ArrayList<String>(n);
+        for (int i = 0; i < n; i++) {
+            result.add(headers.getName(i).toString());
+        }
+        return result;
+
+    }
+
+
+    /**
+     * Return an Iterable of all the header values associated with the
+     * specified header name.
+     *
+     * @param name Header name to look up
+     */
+    @Override
+    public Collection<String> getHeaders(String name) {
+
+        Enumeration<String> enumeration =
+            coyoteResponse.getMimeHeaders().values(name);
+        Vector<String> result = new Vector<String>();
+        while (enumeration.hasMoreElements()) {
+            result.addElement(enumeration.nextElement());
+        }
+        return result;
+    }
+
+
+    /**
+     * Return the error message that was set with <code>sendError()</code>
+     * for this Response.
+     */
+    public String getMessage() {
+        return coyoteResponse.getMessage();
+    }
+
+
+    /**
+     * Return the HTTP status code associated with this Response.
+     */
+    @Override
+    public int getStatus() {
+        return coyoteResponse.getStatus();
+    }
+
+
+    /**
+     * Reset this response, and specify the values for the HTTP status code
+     * and corresponding message.
+     *
+     * @exception IllegalStateException if this response has already been
+     *  committed
+     */
+    public void reset(int status, String message) {
+        reset();
+        setStatus(status, message);
+    }
+
+
+    // -------------------------------------------- HttpServletResponse Methods
+
+
+    /**
+     * Add the specified Cookie to those that will be included with
+     * this Response.
+     *
+     * @param cookie Cookie to be added
+     */
+    @Override
+    public void addCookie(final Cookie cookie) {
+
+        // Ignore any call from an included servlet
+        if (included || isCommitted())
+            return;
+
+        final StringBuffer sb = generateCookieString(cookie);
+        //if we reached here, no exception, cookie is valid
+        // the header name is Set-Cookie for both "old" and v.1 ( RFC2109 )
+        // RFC2965 is not supported by browsers and the Servlet spec
+        // asks for 2109.
+        addHeader("Set-Cookie", sb.toString());
+    }
+
+    /**
+     * Special method for adding a session cookie as we should be overriding 
+     * any previous 
+     * @param cookie
+     */
+    public void addSessionCookieInternal(final Cookie cookie) {
+        if (isCommitted())
+            return;
+        
+        String name = cookie.getName();
+        final String headername = "Set-Cookie";
+        final String startsWith = name + "=";
+        final StringBuffer sb = generateCookieString(cookie);
+        boolean set = false;
+        MimeHeaders headers = coyoteResponse.getMimeHeaders();
+        int n = headers.size();
+        for (int i = 0; i < n; i++) {
+            if (headers.getName(i).toString().equals(headername)) {
+                if (headers.getValue(i).toString().startsWith(startsWith)) {
+                    headers.getValue(i).setString(sb.toString());
+                    set = true;
+                }
+            }
+        }
+        if (!set) {
+            addHeader(headername, sb.toString());
+        }
+        
+        
+    }
+
+    public StringBuffer generateCookieString(final Cookie cookie) {
+        final StringBuffer sb = new StringBuffer();
+        //web application code can receive a IllegalArgumentException 
+        //from the appendCookieValue invocation
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                @Override
+                public Void run(){
+                    ServerCookie.appendCookieValue
+                        (sb, cookie.getVersion(), cookie.getName(), 
+                         cookie.getValue(), cookie.getPath(), 
+                         cookie.getDomain(), cookie.getComment(), 
+                         cookie.getMaxAge(), cookie.getSecure(),
+                         cookie.isHttpOnly());
+                    return null;
+                }
+            });
+        } else {
+            ServerCookie.appendCookieValue
+                (sb, cookie.getVersion(), cookie.getName(), cookie.getValue(),
+                     cookie.getPath(), cookie.getDomain(), cookie.getComment(), 
+                     cookie.getMaxAge(), cookie.getSecure(),
+                     cookie.isHttpOnly());
+        }
+        return sb;
+    }
+
+
+    /**
+     * Add the specified date header to the specified value.
+     *
+     * @param name Name of the header to set
+     * @param value Date value to be set
+     */
+    @Override
+    public void addDateHeader(String name, long value) {
+
+        if (name == null || name.length() == 0) {
+            return;
+        }
+
+        if (isCommitted())
+            return;
+
+        // Ignore any call from an included servlet
+        if (included) {
+            return;
+        }
+
+        if (format == null) {
+            format = new SimpleDateFormat(DateTool.HTTP_RESPONSE_DATE_HEADER,
+                                          Locale.US);
+            format.setTimeZone(TimeZone.getTimeZone("GMT"));
+        }
+
+        addHeader(name, FastHttpDateFormat.formatDate(value, format));
+
+    }
+
+
+    /**
+     * Add the specified header to the specified value.
+     *
+     * @param name Name of the header to set
+     * @param value Value to be set
+     */
+    @Override
+    public void addHeader(String name, String value) {
+
+        if (name == null || name.length() == 0 || value == null) {
+            return;
+        }
+
+        if (isCommitted())
+            return;
+
+        // Ignore any call from an included servlet
+        if (included)
+            return;
+
+        coyoteResponse.addHeader(name, value);
+
+    }
+
+
+    /**
+     * Add the specified integer header to the specified value.
+     *
+     * @param name Name of the header to set
+     * @param value Integer value to be set
+     */
+    @Override
+    public void addIntHeader(String name, int value) {
+
+        if (name == null || name.length() == 0) {
+            return;
+        }
+
+        if (isCommitted())
+            return;
+
+        // Ignore any call from an included servlet
+        if (included)
+            return;
+
+        addHeader(name, "" + value);
+
+    }
+
+
+    /**
+     * Has the specified header been set already in this response?
+     *
+     * @param name Name of the header to check
+     */
+    @Override
+    public boolean containsHeader(String name) {
+        // Need special handling for Content-Type and Content-Length due to
+        // special handling of these in coyoteResponse
+        char cc=name.charAt(0);
+        if(cc=='C' || cc=='c') {
+            if(name.equalsIgnoreCase("Content-Type")) {
+                // Will return null if this has not been set
+                return (coyoteResponse.getContentType() != null);
+            }
+            if(name.equalsIgnoreCase("Content-Length")) {
+                // -1 means not known and is not sent to client
+                return (coyoteResponse.getContentLengthLong() != -1);
+            }
+        }
+
+        return coyoteResponse.containsHeader(name);
+    }
+
+
+    /**
+     * Encode the session identifier associated with this response
+     * into the specified redirect URL, if necessary.
+     *
+     * @param url URL to be encoded
+     */
+    @Override
+    public String encodeRedirectURL(String url) {
+
+        if (isEncodeable(toAbsolute(url))) {
+            return (toEncoded(url, request.getSessionInternal().getIdInternal()));
+        } else {
+            return (url);
+        }
+
+    }
+
+
+    /**
+     * Encode the session identifier associated with this response
+     * into the specified redirect URL, if necessary.
+     *
+     * @param url URL to be encoded
+     *
+     * @deprecated As of Version 2.1 of the Java Servlet API, use
+     *  <code>encodeRedirectURL()</code> instead.
+     */
+    @Override
+    @Deprecated
+    public String encodeRedirectUrl(String url) {
+        return (encodeRedirectURL(url));
+    }
+
+
+    /**
+     * Encode the session identifier associated with this response
+     * into the specified URL, if necessary.
+     *
+     * @param url URL to be encoded
+     */
+    @Override
+    public String encodeURL(String url) {
+        
+        String absolute = toAbsolute(url);
+        if (isEncodeable(absolute)) {
+            // W3c spec clearly said 
+            if (url.equalsIgnoreCase("")){
+                url = absolute;
+            }
+            return (toEncoded(url, request.getSessionInternal().getIdInternal()));
+        } else {
+            return (url);
+        }
+
+    }
+
+
+    /**
+     * Encode the session identifier associated with this response
+     * into the specified URL, if necessary.
+     *
+     * @param url URL to be encoded
+     *
+     * @deprecated As of Version 2.1 of the Java Servlet API, use
+     *  <code>encodeURL()</code> instead.
+     */
+    @Override
+    @Deprecated
+    public String encodeUrl(String url) {
+        return (encodeURL(url));
+    }
+
+
+    /**
+     * Send an acknowledgment of a request.
+     * 
+     * @exception IOException if an input/output error occurs
+     */
+    public void sendAcknowledgement()
+        throws IOException {
+
+        if (isCommitted())
+            return;
+
+        // Ignore any call from an included servlet
+        if (included)
+            return; 
+
+        coyoteResponse.acknowledge();
+
+    }
+
+
+    /**
+     * Send an error response with the specified status and a
+     * default message.
+     *
+     * @param status HTTP status code to send
+     *
+     * @exception IllegalStateException if this response has
+     *  already been committed
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public void sendError(int status) 
+        throws IOException {
+        sendError(status, null);
+    }
+
+
+    /**
+     * Send an error response with the specified status and message.
+     *
+     * @param status HTTP status code to send
+     * @param message Corresponding message to send
+     *
+     * @exception IllegalStateException if this response has
+     *  already been committed
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public void sendError(int status, String message) 
+        throws IOException {
+
+        if (isCommitted())
+            throw new IllegalStateException
+                (sm.getString("coyoteResponse.sendError.ise"));
+
+        // Ignore any call from an included servlet
+        if (included)
+            return; 
+
+        Wrapper wrapper = getRequest().getWrapper();
+        if (wrapper != null) {
+            wrapper.incrementErrorCount();
+        } 
+
+        setError();
+
+        coyoteResponse.setStatus(status);
+        coyoteResponse.setMessage(message);
+
+        // Clear any data content that has been buffered
+        resetBuffer();
+
+        // Cause the response to be finished (from the application perspective)
+        setSuspended(true);
+
+    }
+
+
+    /**
+     * Send a temporary redirect to the specified redirect location URL.
+     *
+     * @param location Location URL to redirect to
+     *
+     * @exception IllegalStateException if this response has
+     *  already been committed
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public void sendRedirect(String location) 
+        throws IOException {
+
+        if (isCommitted())
+            throw new IllegalStateException
+                (sm.getString("coyoteResponse.sendRedirect.ise"));
+
+        // Ignore any call from an included servlet
+        if (included)
+            return; 
+
+        // Clear any data content that has been buffered
+        resetBuffer();
+
+        // Generate a temporary redirect to the specified location
+        try {
+            String absolute = toAbsolute(location);
+            setStatus(SC_FOUND);
+            setHeader("Location", absolute);
+        } catch (IllegalArgumentException e) {
+            setStatus(SC_NOT_FOUND);
+        }
+
+        // Cause the response to be finished (from the application perspective)
+        setSuspended(true);
+
+    }
+
+
+    /**
+     * Set the specified date header to the specified value.
+     *
+     * @param name Name of the header to set
+     * @param value Date value to be set
+     */
+    @Override
+    public void setDateHeader(String name, long value) {
+
+        if (name == null || name.length() == 0) {
+            return;
+        }
+
+        if (isCommitted())
+            return;
+
+        // Ignore any call from an included servlet
+        if (included) {
+            return;
+        }
+
+        if (format == null) {
+            format = new SimpleDateFormat(DateTool.HTTP_RESPONSE_DATE_HEADER,
+                                          Locale.US);
+            format.setTimeZone(TimeZone.getTimeZone("GMT"));
+        }
+
+        setHeader(name, FastHttpDateFormat.formatDate(value, format));
+
+    }
+
+
+    /**
+     * Set the specified header to the specified value.
+     *
+     * @param name Name of the header to set
+     * @param value Value to be set
+     */
+    @Override
+    public void setHeader(String name, String value) {
+
+        if (name == null || name.length() == 0 || value == null) {
+            return;
+        }
+
+        if (isCommitted())
+            return;
+
+        // Ignore any call from an included servlet
+        if (included)
+            return;
+
+        coyoteResponse.setHeader(name, value);
+
+    }
+
+
+    /**
+     * Set the specified integer header to the specified value.
+     *
+     * @param name Name of the header to set
+     * @param value Integer value to be set
+     */
+    @Override
+    public void setIntHeader(String name, int value) {
+
+        if (name == null || name.length() == 0) {
+            return;
+        }
+
+        if (isCommitted())
+            return;
+
+        // Ignore any call from an included servlet
+        if (included)
+            return;
+
+        setHeader(name, "" + value);
+
+    }
+
+
+    /**
+     * Set the HTTP status to be returned with this response.
+     *
+     * @param status The new HTTP status
+     */
+    @Override
+    public void setStatus(int status) {
+        setStatus(status, null);
+    }
+
+
+    /**
+     * Set the HTTP status and message to be returned with this response.
+     *
+     * @param status The new HTTP status
+     * @param message The associated text message
+     *
+     * @deprecated As of Version 2.1 of the Java Servlet API, this method
+     *  has been deprecated due to the ambiguous meaning of the message
+     *  parameter.
+     */
+    @Override
+    @Deprecated
+    public void setStatus(int status, String message) {
+
+        if (isCommitted())
+            return;
+
+        // Ignore any call from an included servlet
+        if (included)
+            return;
+
+        coyoteResponse.setStatus(status);
+        coyoteResponse.setMessage(message);
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Return <code>true</code> if the specified URL should be encoded with
+     * a session identifier.  This will be true if all of the following
+     * conditions are met:
+     * <ul>
+     * <li>The request we are responding to asked for a valid session
+     * <li>The requested session ID was not received via a cookie
+     * <li>The specified URL points back to somewhere within the web
+     *     application that is responding to this request
+     * </ul>
+     *
+     * @param location Absolute URL to be validated
+     */
+    protected boolean isEncodeable(final String location) {
+
+        if (location == null)
+            return (false);
+
+        // Is this an intra-document reference?
+        if (location.startsWith("#"))
+            return (false);
+
+        // Are we in a valid session that is not using cookies?
+        final Request hreq = request;
+        final Session session = hreq.getSessionInternal(false);
+        if (session == null)
+            return (false);
+        if (hreq.isRequestedSessionIdFromCookie())
+            return (false);
+        
+        // Is URL encoding permitted
+        if (!hreq.getServletContext().getEffectiveSessionTrackingModes().
+                contains(SessionTrackingMode.URL))
+            return false;
+
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (
+                AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
+
+                @Override
+                public Boolean run(){
+                    return Boolean.valueOf(doIsEncodeable(hreq, session, location));
+                }
+            })).booleanValue();
+        } else {
+            return doIsEncodeable(hreq, session, location);
+        }
+    }
+
+    private boolean doIsEncodeable(Request hreq, Session session, 
+                                   String location) {
+        // Is this a valid absolute URL?
+        URL url = null;
+        try {
+            url = new URL(location);
+        } catch (MalformedURLException e) {
+            return (false);
+        }
+
+        // Does this URL match down to (and including) the context path?
+        if (!hreq.getScheme().equalsIgnoreCase(url.getProtocol()))
+            return (false);
+        if (!hreq.getServerName().equalsIgnoreCase(url.getHost()))
+            return (false);
+        int serverPort = hreq.getServerPort();
+        if (serverPort == -1) {
+            if ("https".equals(hreq.getScheme()))
+                serverPort = 443;
+            else
+                serverPort = 80;
+        }
+        int urlPort = url.getPort();
+        if (urlPort == -1) {
+            if ("https".equals(url.getProtocol()))
+                urlPort = 443;
+            else
+                urlPort = 80;
+        }
+        if (serverPort != urlPort)
+            return (false);
+
+        String contextPath = getContext().getPath();
+        if (contextPath != null) {
+            String file = url.getFile();
+            if ((file == null) || !file.startsWith(contextPath))
+                return (false);
+            String tok = ";" +
+                    ApplicationSessionCookieConfig.getSessionUriParamName(
+                                request.getContext()) +
+                    "=" + session.getIdInternal();
+            if( file.indexOf(tok, contextPath.length()) >= 0 )
+                return (false);
+        }
+
+        // This URL belongs to our web application, so it is encodeable
+        return (true);
+
+    }
+
+
+    /**
+     * Convert (if necessary) and return the absolute URL that represents the
+     * resource referenced by this possibly relative URL.  If this URL is
+     * already absolute, return it unchanged.
+     *
+     * @param location URL to be (possibly) converted and then returned
+     *
+     * @exception IllegalArgumentException if a MalformedURLException is
+     *  thrown when converting the relative URL to an absolute one
+     */
+    private String toAbsolute(String location) {
+
+        if (location == null)
+            return (location);
+
+        boolean leadingSlash = location.startsWith("/");
+
+        if (leadingSlash || !hasScheme(location)) {
+
+            redirectURLCC.recycle();
+
+            String scheme = request.getScheme();
+            String name = request.getServerName();
+            int port = request.getServerPort();
+
+            try {
+                redirectURLCC.append(scheme, 0, scheme.length());
+                redirectURLCC.append("://", 0, 3);
+                redirectURLCC.append(name, 0, name.length());
+                if ((scheme.equals("http") && port != 80)
+                    || (scheme.equals("https") && port != 443)) {
+                    redirectURLCC.append(':');
+                    String portS = port + "";
+                    redirectURLCC.append(portS, 0, portS.length());
+                }
+                if (!leadingSlash) {
+                    String relativePath = request.getDecodedRequestURI();
+                    int pos = relativePath.lastIndexOf('/');
+                    relativePath = relativePath.substring(0, pos);
+                    
+                    String encodedURI = null;
+                    final String frelativePath = relativePath;
+                    if (SecurityUtil.isPackageProtectionEnabled() ){
+                        try{
+                            encodedURI = AccessController.doPrivileged( 
+                                new PrivilegedExceptionAction<String>(){                                
+                                    @Override
+                                    public String run() throws IOException{
+                                        return urlEncoder.encodeURL(frelativePath);
+                                    }
+                           });   
+                        } catch (PrivilegedActionException pae){
+                            IllegalArgumentException iae =
+                                new IllegalArgumentException(location);
+                            iae.initCause(pae.getException());
+                            throw iae;
+                        }
+                    } else {
+                        encodedURI = urlEncoder.encodeURL(relativePath);
+                    }
+                    redirectURLCC.append(encodedURI, 0, encodedURI.length());
+                    redirectURLCC.append('/');
+                }
+                redirectURLCC.append(location, 0, location.length());
+            } catch (IOException e) {
+                IllegalArgumentException iae =
+                    new IllegalArgumentException(location);
+                iae.initCause(e);
+                throw iae;
+            }
+
+            return redirectURLCC.toString();
+
+        } else {
+
+            return (location);
+
+        }
+
+    }
+
+
+    /**
+     * Determine if a URI string has a <code>scheme</code> component.
+     */
+    private boolean hasScheme(String uri) {
+        int len = uri.length();
+        for(int i=0; i < len ; i++) {
+            char c = uri.charAt(i);
+            if(c == ':') {
+                return i > 0;
+            } else if(!URL.isSchemeChar(c)) {
+                return false;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Return the specified URL with the specified session identifier
+     * suitably encoded.
+     *
+     * @param url URL to be encoded with the session id
+     * @param sessionId Session id to be included in the encoded URL
+     */
+    protected String toEncoded(String url, String sessionId) {
+
+        if ((url == null) || (sessionId == null))
+            return (url);
+
+        String path = url;
+        String query = "";
+        String anchor = "";
+        int question = url.indexOf('?');
+        if (question >= 0) {
+            path = url.substring(0, question);
+            query = url.substring(question);
+        }
+        int pound = path.indexOf('#');
+        if (pound >= 0) {
+            anchor = path.substring(pound);
+            path = path.substring(0, pound);
+        }
+        StringBuilder sb = new StringBuilder(path);
+        if( sb.length() > 0 ) { // jsessionid can't be first.
+            sb.append(";");
+            sb.append(ApplicationSessionCookieConfig.getSessionUriParamName(
+                    request.getContext()));
+            sb.append("=");
+            sb.append(sessionId);
+        }
+        sb.append(anchor);
+        sb.append(query);
+        return (sb.toString());
+
+    }
+
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/connector/ResponseFacade.java b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/ResponseFacade.java
new file mode 100644
index 0000000..ecb62ef
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/ResponseFacade.java
@@ -0,0 +1,622 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.connector;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Collection;
+import java.util.Locale;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.Globals;
+import org.apache.catalina.security.SecurityUtil;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Facade class that wraps a Coyote response object. 
+ * All methods are delegated to the wrapped response.
+ *
+ * @author Remy Maucherat
+ * @author Jean-Francois Arcand
+ * @version $Id: ResponseFacade.java,v 1.1 2011/06/28 21:08:19 rherrmann Exp $
+ */
+@SuppressWarnings("deprecation")
+public class ResponseFacade 
+    implements HttpServletResponse {
+
+
+    // ----------------------------------------------------------- DoPrivileged
+    
+    private final class SetContentTypePrivilegedAction
+            implements PrivilegedAction<Void> {
+
+        private String contentType;
+
+        public SetContentTypePrivilegedAction(String contentType){
+            this.contentType = contentType;
+        }
+        
+        @Override
+        public Void run() {
+            response.setContentType(contentType);
+            return null;
+        }            
+    }
+
+    private final class DateHeaderPrivilegedAction
+            implements PrivilegedAction<Void> {
+
+        private String name;
+        private long value;
+        private boolean add;
+
+        DateHeaderPrivilegedAction(String name, long value, boolean add) {
+            this.name = name;
+            this.value = value;
+            this.add = add;
+        }
+
+        @Override
+        public Void run() {
+            if(add) {
+                response.addDateHeader(name, value);
+            } else {
+                response.setDateHeader(name, value);
+            }
+            return null;
+        }
+    }
+    
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a wrapper for the specified response.
+     *
+     * @param response The response to be wrapped
+     */
+    public ResponseFacade(Response response) {
+
+         this.response = response;
+    }
+
+
+    // ----------------------------------------------- Class/Instance Variables
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
+     * The wrapped response.
+     */
+    protected Response response = null;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Clear facade.
+     */
+    public void clear() {
+        response = null;
+    }
+
+
+    /**
+     * Prevent cloning the facade.
+     */
+    @Override
+    protected Object clone()
+        throws CloneNotSupportedException {
+        throw new CloneNotSupportedException();
+    }
+
+
+    public void finish() {
+
+        if (response == null) {
+            throw new IllegalStateException(
+                            sm.getString("responseFacade.nullResponse"));
+        }
+
+        response.setSuspended(true);
+    }
+
+
+    public boolean isFinished() {
+
+        if (response == null) {
+            throw new IllegalStateException(
+                            sm.getString("responseFacade.nullResponse"));
+        }
+
+        return response.isSuspended();
+    }
+
+
+    public long getContentWritten() {
+
+        if (response == null) {
+            throw new IllegalStateException(
+                            sm.getString("responseFacade.nullResponse"));
+        }
+
+        return response.getContentWritten();
+    }
+
+    // ------------------------------------------------ ServletResponse Methods
+
+
+    @Override
+    public String getCharacterEncoding() {
+
+        if (response == null) {
+            throw new IllegalStateException(
+                            sm.getString("responseFacade.nullResponse"));
+        }
+
+        return response.getCharacterEncoding();
+    }
+
+
+    @Override
+    public ServletOutputStream getOutputStream()
+        throws IOException {
+
+        //        if (isFinished())
+        //            throw new IllegalStateException
+        //                (/*sm.getString("responseFacade.finished")*/);
+
+        ServletOutputStream sos = response.getOutputStream();
+        if (isFinished())
+            response.setSuspended(true);
+        return (sos);
+
+    }
+
+
+    @Override
+    public PrintWriter getWriter()
+        throws IOException {
+
+        //        if (isFinished())
+        //            throw new IllegalStateException
+        //                (/*sm.getString("responseFacade.finished")*/);
+
+        PrintWriter writer = response.getWriter();
+        if (isFinished())
+            response.setSuspended(true);
+        return (writer);
+
+    }
+
+
+    @Override
+    public void setContentLength(int len) {
+
+        if (isCommitted())
+            return;
+
+        response.setContentLength(len);
+
+    }
+
+
+    @Override
+    public void setContentType(String type) {
+
+        if (isCommitted())
+            return;
+        
+        if (SecurityUtil.isPackageProtectionEnabled()){
+            AccessController.doPrivileged(new SetContentTypePrivilegedAction(type));
+        } else {
+            response.setContentType(type);            
+        }
+    }
+
+
+    @Override
+    public void setBufferSize(int size) {
+
+        if (isCommitted())
+            throw new IllegalStateException
+                (/*sm.getString("responseBase.reset.ise")*/);
+
+        response.setBufferSize(size);
+
+    }
+
+
+    @Override
+    public int getBufferSize() {
+
+        if (response == null) {
+            throw new IllegalStateException(
+                            sm.getString("responseFacade.nullResponse"));
+        }
+
+        return response.getBufferSize();
+    }
+
+
+    @Override
+    public void flushBuffer()
+        throws IOException {
+
+        if (isFinished())
+            //            throw new IllegalStateException
+            //                (/*sm.getString("responseFacade.finished")*/);
+            return;
+
+        if (SecurityUtil.isPackageProtectionEnabled()){
+            try{
+                AccessController.doPrivileged(
+                        new PrivilegedExceptionAction<Void>(){
+
+                    @Override
+                    public Void run() throws IOException{
+                        response.setAppCommitted(true);
+
+                        response.flushBuffer();
+                        return null;
+                    }
+                });
+            } catch(PrivilegedActionException e){
+                Exception ex = e.getException();
+                if (ex instanceof IOException){
+                    throw (IOException)ex;
+                }
+            }
+        } else {
+            response.setAppCommitted(true);
+
+            response.flushBuffer();            
+        }
+
+    }
+
+
+    @Override
+    public void resetBuffer() {
+
+        if (isCommitted())
+            throw new IllegalStateException
+                (/*sm.getString("responseBase.reset.ise")*/);
+
+        response.resetBuffer();
+
+    }
+
+
+    @Override
+    public boolean isCommitted() {
+
+        if (response == null) {
+            throw new IllegalStateException(
+                            sm.getString("responseFacade.nullResponse"));
+        }
+
+        return (response.isAppCommitted());
+    }
+
+
+    @Override
+    public void reset() {
+
+        if (isCommitted())
+            throw new IllegalStateException
+                (/*sm.getString("responseBase.reset.ise")*/);
+
+        response.reset();
+
+    }
+
+
+    @Override
+    public void setLocale(Locale loc) {
+
+        if (isCommitted())
+            return;
+
+        response.setLocale(loc);
+    }
+
+
+    @Override
+    public Locale getLocale() {
+
+        if (response == null) {
+            throw new IllegalStateException(
+                            sm.getString("responseFacade.nullResponse"));
+        }
+
+        return response.getLocale();
+    }
+
+
+    @Override
+    public void addCookie(Cookie cookie) {
+
+        if (isCommitted())
+            return;
+
+        response.addCookie(cookie);
+
+    }
+
+
+    @Override
+    public boolean containsHeader(String name) {
+
+        if (response == null) {
+            throw new IllegalStateException(
+                            sm.getString("responseFacade.nullResponse"));
+        }
+
+        return response.containsHeader(name);
+    }
+
+
+    @Override
+    public String encodeURL(String url) {
+
+        if (response == null) {
+            throw new IllegalStateException(
+                            sm.getString("responseFacade.nullResponse"));
+        }
+
+        return response.encodeURL(url);
+    }
+
+
+    @Override
+    public String encodeRedirectURL(String url) {
+
+        if (response == null) {
+            throw new IllegalStateException(
+                            sm.getString("responseFacade.nullResponse"));
+        }
+
+        return response.encodeRedirectURL(url);
+    }
+
+
+    @Override
+    public String encodeUrl(String url) {
+
+        if (response == null) {
+            throw new IllegalStateException(
+                            sm.getString("responseFacade.nullResponse"));
+        }
+
+        return response.encodeURL(url);
+    }
+
+
+    @Override
+    public String encodeRedirectUrl(String url) {
+
+        if (response == null) {
+            throw new IllegalStateException(
+                            sm.getString("responseFacade.nullResponse"));
+        }
+
+        return response.encodeRedirectURL(url);
+    }
+
+
+    @Override
+    public void sendError(int sc, String msg)
+        throws IOException {
+
+        if (isCommitted())
+            throw new IllegalStateException
+                (/*sm.getString("responseBase.reset.ise")*/);
+
+        response.setAppCommitted(true);
+
+        response.sendError(sc, msg);
+
+    }
+
+
+    @Override
+    public void sendError(int sc)
+        throws IOException {
+
+        if (isCommitted())
+            throw new IllegalStateException
+                (/*sm.getString("responseBase.reset.ise")*/);
+
+        response.setAppCommitted(true);
+
+        response.sendError(sc);
+
+    }
+
+
+    @Override
+    public void sendRedirect(String location)
+        throws IOException {
+
+        if (isCommitted())
+            throw new IllegalStateException
+                (/*sm.getString("responseBase.reset.ise")*/);
+
+        response.setAppCommitted(true);
+
+        response.sendRedirect(location);
+
+    }
+
+
+    @Override
+    public void setDateHeader(String name, long date) {
+
+        if (isCommitted())
+            return;
+
+        if(Globals.IS_SECURITY_ENABLED) {
+            AccessController.doPrivileged(new DateHeaderPrivilegedAction
+                                             (name, date, false));
+        } else {
+            response.setDateHeader(name, date);
+        }
+
+    }
+
+
+    @Override
+    public void addDateHeader(String name, long date) {
+
+        if (isCommitted())
+            return;
+
+        if(Globals.IS_SECURITY_ENABLED) {
+            AccessController.doPrivileged(new DateHeaderPrivilegedAction
+                                             (name, date, true));
+        } else {
+            response.addDateHeader(name, date);
+        }
+
+    }
+
+
+    @Override
+    public void setHeader(String name, String value) {
+
+        if (isCommitted())
+            return;
+
+        response.setHeader(name, value);
+
+    }
+
+
+    @Override
+    public void addHeader(String name, String value) {
+
+        if (isCommitted())
+            return;
+
+        response.addHeader(name, value);
+
+    }
+
+
+    @Override
+    public void setIntHeader(String name, int value) {
+
+        if (isCommitted())
+            return;
+
+        response.setIntHeader(name, value);
+
+    }
+
+
+    @Override
+    public void addIntHeader(String name, int value) {
+
+        if (isCommitted())
+            return;
+
+        response.addIntHeader(name, value);
+
+    }
+
+
+    @Override
+    public void setStatus(int sc) {
+
+        if (isCommitted())
+            return;
+
+        response.setStatus(sc);
+
+    }
+
+
+    @Override
+    public void setStatus(int sc, String sm) {
+
+        if (isCommitted())
+            return;
+
+        response.setStatus(sc, sm);
+    }
+
+
+    @Override
+    public String getContentType() {
+
+        if (response == null) {
+            throw new IllegalStateException(
+                            sm.getString("responseFacade.nullResponse"));
+        }
+
+        return response.getContentType();
+    }
+
+
+    @Override
+    public void setCharacterEncoding(String arg0) {
+
+        if (response == null) {
+            throw new IllegalStateException(
+                            sm.getString("responseFacade.nullResponse"));
+        }
+
+        response.setCharacterEncoding(arg0);
+    }
+
+    @Override
+    public int getStatus() {
+        return response.getStatus();
+    }
+    
+    @Override
+    public String getHeader(String name) {
+        return response.getHeader(name);
+    }
+    
+    @Override
+    public Collection<String> getHeaderNames() {
+        return response.getHeaderNames();
+    }
+    
+    @Override
+    public Collection<String> getHeaders(String name) {
+        return response.getHeaders(name);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/connector/mbeans-descriptors.xml b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/mbeans-descriptors.xml
new file mode 100644
index 0000000..bd1da68
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/connector/mbeans-descriptors.xml
@@ -0,0 +1,204 @@
+<?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>
+
+  <!-- This MBean contains Connector attributes and some common attributes of
+       associated ProtocolHandler instances. Common attributes extracted out
+       from all ProtocolHandler implementations are denoted by a comment
+       'common' above their description. Each attribute particular to given
+       ProtocolHandler implementation can be found in relevant ProtocolHandler
+       MBean. -->
+  <mbean         name="CoyoteConnector"
+            className="org.apache.catalina.mbeans.ConnectorMBean"
+          description="Implementation of a Coyote connector"
+               domain="Catalina"
+                group="Connector"
+                 type="org.apache.catalina.connector.Connector">
+
+    <!-- Common -->
+    <attribute   name="acceptCount"
+          description="The accept count for this Connector"
+                 type="int"/>
+    
+    <!-- Common -->
+    <attribute   name="address"
+          description="The IP address on which to bind"
+                 type="java.lang.String"/>
+                 
+    <attribute   name="allowTrace"
+          description="Allow disabling TRACE method"
+                 type="boolean"/>
+
+    <attribute   name="className"
+          description="Fully qualified class name of the managed object"
+                 type="java.lang.String"
+            writeable="false"/>
+    
+    <!-- Common -->
+    <attribute   name="connectionLinger"
+          description="Linger value on the incoming connection"
+                 type="int"/>
+
+    <!-- Common -->
+    <attribute   name="connectionTimeout"
+          description="Timeout value on the incoming connection"
+                 type="int"/>
+                 
+    <attribute   name="emptySessionPath"
+          description="The 'empty session path' flag for this Connector"
+                 type="boolean"/>
+
+    <attribute   name="enableLookups"
+          description="The 'enable DNS lookups' flag for this Connector"
+                 type="boolean"/>
+
+    <attribute   name="executorName"
+          description="The name of the executor - if any - associated with this Connector"
+                 type="java.lang.String"
+            writeable="false"/>
+
+    <!-- Common -->
+    <attribute   name="keepAliveTimeout"
+          description="The number of seconds Tomcat will wait for a subsequent request before closing the connection"
+                 type="int"/>
+
+    <attribute   name="maxKeepAliveRequests"
+          description="Maximum number of Keep-Alive requests to honor per connection"
+                 type="int"/>
+
+    <attribute   name="maxPostSize"
+          description="Maximum size in bytes of a POST which will be handled by the servlet API provided features"
+                 type="int"/>
+                 
+    <attribute   name="maxSavePostSize"
+          description="Maximum size of a POST which will be saved by the container during authentication"
+                 type="int"/>                
+
+    <!-- Common -->
+    <attribute   name="maxThreads"
+          description="The maximum number of request processing threads to be created"
+                 type="int"/>
+
+    <attribute   name="minSpareThreads"
+          description="The number of request processing threads that will be created"
+                 type="int"/>
+
+    <!-- Common -->
+    <attribute   name="packetSize"
+          description="The ajp packet size."
+                 type="int"/>
+                 
+    <attribute   name="port"
+          description="The port number on which we listen for ajp13 requests"
+                type="int"/>
+    
+    <!-- Common -->            
+    <attribute   name="processorCache"
+          description="The processor cache size."
+                 type="int"/>    
+
+    <attribute   name="protocol"
+          description="Coyote protocol handler in use"
+                 type="java.lang.String"/>
+
+    <attribute   name="protocolHandlerClassName"
+          description="Coyote Protocol handler class name"
+                 type="java.lang.String"
+            writeable="false"/>
+
+    <attribute   name="proxyName"
+          description="Ther Server name to which we should pretend requests to this Connector"
+                 type="java.lang.String"/>
+
+    <attribute   name="proxyPort"
+          description="Ther Server port to which we should pretend requests to this Connector"
+                 type="int"/>
+
+    <attribute   name="redirectPort"
+          description="The redirect port for non-SSL to SSL redirects"
+                 type="int"/>
+
+    <attribute   name="scheme"
+          description="Protocol name for this Connector (http, https)"
+                 type="java.lang.String"/>
+
+    <attribute   name="secret"
+          description="Authentication secret (I guess ... not in Javadocs)"
+            readable = "false" 
+                 type="java.lang.String"/>
+
+    <attribute   name="secure"
+          description="Is this a secure (SSL) Connector?"
+                 type="boolean"/>
+                 
+    <attribute   name="sslProtocols"
+          description="Comma-separated list of SSL protocol variants to be enabled"
+                 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"/>
+
+    <!-- Common -->
+    <attribute   name="tcpNoDelay"
+          description="Should we use TCP no delay?"
+                 type="boolean"/>
+    
+    <!-- Common -->
+    <attribute    name="threadPriority"
+           description="The thread priority for processors"
+                  type="int"/>
+                  
+    <attribute   name="URIEncoding"
+          description="Character encoding used to decode the URI"
+                 type="java.lang.String"/>
+
+    <attribute   name="useBodyEncodingForURI"
+          description="Should the body encoding be used for URI query parameters"
+                 type="boolean"/>
+                 
+    <attribute   name="useIPVHosts"
+          description="Should IP-based virtual hosting be used? "
+                 type="boolean"/>
+
+    <attribute    name="xpoweredBy"
+           description="Is generation of X-Powered-By response header enabled/disabled?"
+                  type="boolean"/>
+
+    <operation name="start" description="Start" impact="ACTION" returnType="void" />
+    <operation name="stop" description="Stop" impact="ACTION" returnType="void" />
+    <operation name="pause" description="Start" impact="ACTION" returnType="void" />
+    <operation name="resume" description="Stop" impact="ACTION" returnType="void" />
+    <operation name="init" description="Init" impact="ACTION" returnType="void" />
+    <operation name="destroy" description="Destroy" impact="ACTION" returnType="void" />
+
+  </mbean>
+
+  <mbean   name="Mapper"
+    description="Maps requests received by the associated connector to Hosts, Contexts and Wrappers"
+         domain="Catalina"
+          group="Mapper"
+           type="org.apache.catalina.connector.MapperListener">
+
+    <attribute name="connectorName"
+        description="Name of the associated connector"
+               type="java.lang.String"
+          writeable="false"/>
+  </mbean>
+</mbeans-descriptors>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationContext.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationContext.java
new file mode 100644
index 0000000..e998e58
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationContext.java
@@ -0,0 +1,1608 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.naming.Binding;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+import javax.servlet.Filter;
+import javax.servlet.FilterRegistration;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextAttributeEvent;
+import javax.servlet.ServletContextAttributeListener;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletRequestAttributeListener;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.descriptor.JspConfigDescriptor;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionListener;
+
+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.LifecycleState;
+import org.apache.catalina.Service;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.connector.Connector;
+import org.apache.catalina.deploy.FilterDef;
+import org.apache.catalina.util.Enumerator;
+import org.apache.catalina.util.RequestUtil;
+import org.apache.catalina.util.ResourceSet;
+import org.apache.catalina.util.ServerInfo;
+import org.apache.naming.resources.DirContextURLStreamHandler;
+import org.apache.naming.resources.Resource;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.buf.CharChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.mapper.MappingData;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Standard implementation of <code>ServletContext</code> that represents
+ * a web application's execution environment.  An instance of this class is
+ * associated with each instance of <code>StandardContext</code>.
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @version $Id: ApplicationContext.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+public class ApplicationContext
+    implements ServletContext {
+
+    protected static final boolean STRICT_SERVLET_COMPLIANCE;
+
+    protected static final boolean GET_RESOURCE_REQUIRE_SLASH;
+
+
+    static {
+        STRICT_SERVLET_COMPLIANCE = Globals.STRICT_SERVLET_COMPLIANCE;
+        
+        String requireSlash = System.getProperty(
+                "org.apache.catalina.core.ApplicationContext.GET_RESOURCE_REQUIRE_SLASH");
+        if (requireSlash == null) {
+            GET_RESOURCE_REQUIRE_SLASH = STRICT_SERVLET_COMPLIANCE;
+        } else {
+            GET_RESOURCE_REQUIRE_SLASH =
+                Boolean.valueOf(requireSlash).booleanValue();
+        }
+    }
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new instance of this class, associated with the specified
+     * Context instance.
+     *
+     * @param context The associated Context instance
+     */
+    public ApplicationContext(StandardContext context) {
+        super();
+        this.context = context;
+        
+        // Populate session tracking modes
+        populateSessionTrackingModes();
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The context attributes for this context.
+     */
+    protected Map<String,Object> attributes =
+        new ConcurrentHashMap<String,Object>();
+
+
+    /**
+     * List of read only attributes for this context.
+     */
+    private Map<String,String> readOnlyAttributes =
+        new ConcurrentHashMap<String,String>();
+
+
+    /**
+     * The Context instance with which we are associated.
+     */
+    private StandardContext context = null;
+
+
+    /**
+     * Empty String collection to serve as the basis for empty enumerations.
+     */
+    private static final List<String> emptyString = Collections.emptyList();
+
+    /**
+     * Empty Servlet collection to serve as the basis for empty enumerations.
+     */
+    private static final List<Servlet> emptyServlet = Collections.emptyList();
+
+
+    /**
+     * The facade around this object.
+     */
+    private ServletContext facade = new ApplicationContextFacade(this);
+
+
+    /**
+     * The merged context initialization parameters for this Context.
+     */
+    private Map<String,String> parameters =
+        new ConcurrentHashMap<String,String>();
+
+
+    /**
+     * The string manager for this package.
+     */
+    private static final StringManager sm =
+      StringManager.getManager(Constants.Package);
+
+
+    /**
+     * Thread local data used during request dispatch.
+     */
+    private ThreadLocal<DispatchData> dispatchData =
+        new ThreadLocal<DispatchData>();
+
+
+    /**
+     * Session Cookie config
+     */
+    private SessionCookieConfig sessionCookieConfig =
+        new ApplicationSessionCookieConfig();
+    
+    /**
+     * Session tracking modes
+     */
+    private Set<SessionTrackingMode> sessionTrackingModes = null;
+    private Set<SessionTrackingMode> defaultSessionTrackingModes = null;
+    private Set<SessionTrackingMode> supportedSessionTrackingModes = null;
+
+    /**
+     * Flag that indicates if a new {@link ServletContextListener} may be added
+     * to the application. Once the first {@link ServletContextListener} is
+     * called, no more may be added.
+     */
+    private boolean newServletContextListenerAllowed = true;
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return the resources object that is mapped to a specified path.
+     * The path must begin with a "/" and is interpreted as relative to the
+     * current context root.
+     */
+    public DirContext getResources() {
+
+        return context.getResources();
+
+    }
+
+
+    // ------------------------------------------------- ServletContext Methods
+
+
+    /**
+     * Return the value of the specified context attribute, if any;
+     * otherwise return <code>null</code>.
+     *
+     * @param name Name of the context attribute to return
+     */
+    @Override
+    public Object getAttribute(String name) {
+
+        return (attributes.get(name));
+
+    }
+
+
+    /**
+     * Return an enumeration of the names of the context attributes
+     * associated with this context.
+     */
+    @Override
+    public Enumeration<String> getAttributeNames() {
+
+        return new Enumerator<String>(attributes.keySet(), true);
+
+    }
+
+
+    /**
+     * Return a <code>ServletContext</code> object that corresponds to a
+     * specified URI on the server.  This method allows servlets to gain
+     * access to the context for various parts of the server, and as needed
+     * obtain <code>RequestDispatcher</code> objects or resources from the
+     * context.  The given path must be absolute (beginning with a "/"),
+     * and is interpreted based on our virtual host's document root.
+     *
+     * @param uri Absolute URI of a resource on the server
+     */
+    @Override
+    public ServletContext getContext(String uri) {
+
+        // Validate the format of the specified argument
+        if ((uri == null) || (!uri.startsWith("/")))
+            return (null);
+
+        Context child = null;
+        try {
+            Host host = (Host) context.getParent();
+            String mapuri = uri;
+            while (true) {
+                child = (Context) host.findChild(mapuri);
+                if (child != null)
+                    break;
+                int slash = mapuri.lastIndexOf('/');
+                if (slash < 0)
+                    break;
+                mapuri = mapuri.substring(0, slash);
+            }
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            return (null);
+        }
+
+        if (child == null)
+            return (null);
+
+        if (context.getCrossContext()) {
+            // If crossContext is enabled, can always return the context
+            return child.getServletContext();
+        } else if (child == context) {
+            // Can still return the current context
+            return context.getServletContext();
+        } else {
+            // Nothing to return
+            return (null);
+        }
+    }
+
+    
+    /**
+     * Return the main path associated with this context.
+     */
+    @Override
+    public String getContextPath() {
+        return context.getPath();
+    }
+    
+
+    /**
+     * Return the value of the specified initialization parameter, or
+     * <code>null</code> if this parameter does not exist.
+     *
+     * @param name Name of the initialization parameter to retrieve
+     */
+    @Override
+    public String getInitParameter(final String name) {
+        return parameters.get(name);
+    }
+
+
+    /**
+     * Return the names of the context's initialization parameters, or an
+     * empty enumeration if the context has no initialization parameters.
+     */
+    @Override
+    public Enumeration<String> getInitParameterNames() {
+        return (new Enumerator<String>(parameters.keySet()));
+    }
+
+
+    /**
+     * Return the major version of the Java Servlet API that we implement.
+     */
+    @Override
+    public int getMajorVersion() {
+
+        return (Constants.MAJOR_VERSION);
+
+    }
+
+
+    /**
+     * Return the minor version of the Java Servlet API that we implement.
+     */
+    @Override
+    public int getMinorVersion() {
+
+        return (Constants.MINOR_VERSION);
+
+    }
+
+
+    /**
+     * Return the MIME type of the specified file, or <code>null</code> if
+     * the MIME type cannot be determined.
+     *
+     * @param file Filename for which to identify a MIME type
+     */
+    @Override
+    public String getMimeType(String file) {
+
+        if (file == null)
+            return (null);
+        int period = file.lastIndexOf(".");
+        if (period < 0)
+            return (null);
+        String extension = file.substring(period + 1);
+        if (extension.length() < 1)
+            return (null);
+        return (context.findMimeMapping(extension));
+
+    }
+
+
+    /**
+     * Return a <code>RequestDispatcher</code> object that acts as a
+     * wrapper for the named servlet.
+     *
+     * @param name Name of the servlet for which a dispatcher is requested
+     */
+    @Override
+    public RequestDispatcher getNamedDispatcher(String name) {
+
+        // Validate the name argument
+        if (name == null)
+            return (null);
+
+        // Create and return a corresponding request dispatcher
+        Wrapper wrapper = (Wrapper) context.findChild(name);
+        if (wrapper == null)
+            return (null);
+        
+        return new ApplicationDispatcher(wrapper, null, null, null, null, name);
+
+    }
+
+
+    /**
+     * 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
+    public String getRealPath(String path) {
+        return context.getRealPath(path);
+    }
+
+
+    /**
+     * Return a <code>RequestDispatcher</code> instance that acts as a
+     * wrapper for the resource at the given path.  The path must begin
+     * with a "/" and is interpreted as relative to the current context root.
+     *
+     * @param path The path to the desired resource.
+     */
+    @Override
+    public RequestDispatcher getRequestDispatcher(String path) {
+
+        // Validate the path argument
+        if (path == null)
+            return (null);
+        if (!path.startsWith("/"))
+            throw new IllegalArgumentException
+                (sm.getString
+                 ("applicationContext.requestDispatcher.iae", path));
+
+        // Get query string
+        String queryString = null;
+        String normalizedPath = path;
+        int pos = normalizedPath.indexOf('?');
+        if (pos >= 0) {
+            queryString = normalizedPath.substring(pos + 1);
+            normalizedPath = normalizedPath.substring(0, pos);
+        }
+
+        normalizedPath = RequestUtil.normalize(normalizedPath);
+        if (normalizedPath == null)
+            return (null);
+
+        pos = normalizedPath.length(); 
+
+        // Use the thread local URI and mapping data
+        DispatchData dd = dispatchData.get();
+        if (dd == null) {
+            dd = new DispatchData();
+            dispatchData.set(dd);
+        }
+
+        MessageBytes uriMB = dd.uriMB;
+        uriMB.recycle();
+
+        // Use the thread local mapping data
+        MappingData mappingData = dd.mappingData;
+
+        // Map the URI
+        CharChunk uriCC = uriMB.getCharChunk();
+        try {
+            uriCC.append(context.getPath(), 0, context.getPath().length());
+            /*
+             * Ignore any trailing path params (separated by ';') for mapping
+             * purposes
+             */
+            int semicolon = normalizedPath.indexOf(';');
+            if (pos >= 0 && semicolon > pos) {
+                semicolon = -1;
+            }
+            uriCC.append(normalizedPath, 0, semicolon > 0 ? semicolon : pos);
+            context.getMapper().map(uriMB, mappingData);
+            if (mappingData.wrapper == null) {
+                return (null);
+            }
+            /*
+             * Append any trailing path params (separated by ';') that were
+             * ignored for mapping purposes, so that they're reflected in the
+             * RequestDispatcher's requestURI
+             */
+            if (semicolon > 0) {
+                uriCC.append(normalizedPath, semicolon, pos - semicolon);
+            }
+        } catch (Exception e) {
+            // Should never happen
+            log(sm.getString("applicationContext.mapping.error"), e);
+            return (null);
+        }
+
+        Wrapper wrapper = (Wrapper) mappingData.wrapper;
+        String wrapperPath = mappingData.wrapperPath.toString();
+        String pathInfo = mappingData.pathInfo.toString();
+
+        mappingData.recycle();
+        
+        // Construct a RequestDispatcher to process this request
+        return new ApplicationDispatcher
+            (wrapper, uriCC.toString(), wrapperPath, pathInfo, 
+             queryString, null);
+
+    }
+
+
+
+    /**
+     * Return the URL to the resource that is mapped to a specified path.
+     * The path must begin with a "/" and is interpreted as relative to the
+     * current context root.
+     *
+     * @param path The path to the desired resource
+     *
+     * @exception MalformedURLException if the path is not given
+     *  in the correct form
+     */
+    @Override
+    public URL getResource(String path)
+        throws MalformedURLException {
+
+        if (path == null ||
+                !path.startsWith("/") && GET_RESOURCE_REQUIRE_SLASH)
+            throw new MalformedURLException(sm.getString(
+                    "applicationContext.requestDispatcher.iae", path));
+        
+        String normPath = RequestUtil.normalize(path);
+        if (normPath == null)
+            return (null);
+
+        DirContext resources = context.getResources();
+        if (resources != null) {
+            String fullPath = context.getPath() + normPath;
+            String hostName = context.getParent().getName();
+            try {
+                resources.lookup(path);
+                return new URL
+                    ("jndi", "", 0, getJNDIUri(hostName, fullPath),
+                     new DirContextURLStreamHandler(resources));
+            } catch (NamingException e) {
+                // Ignore
+            } catch (Exception e) {
+                // Unexpected
+                log(sm.getString("applicationContext.lookup.error", path,
+                        getContextPath()), e);
+            }
+        }
+
+        return (null);
+
+    }
+
+
+    /**
+     * Return the requested resource as an <code>InputStream</code>.  The
+     * path must be specified according to the rules described under
+     * <code>getResource</code>.  If no such resource can be identified,
+     * return <code>null</code>.
+     *
+     * @param path The path to the desired resource.
+     */
+    @Override
+    public InputStream getResourceAsStream(String path) {
+
+        if (path == null)
+            return (null);
+
+        if (!path.startsWith("/") && GET_RESOURCE_REQUIRE_SLASH)
+            return null;
+
+        String normalizedPath = RequestUtil.normalize(path);
+        if (normalizedPath == null)
+            return (null);
+
+        DirContext resources = context.getResources();
+        if (resources != null) {
+            try {
+                Object resource = resources.lookup(normalizedPath);
+                if (resource instanceof Resource)
+                    return (((Resource) resource).streamContent());
+            } catch (NamingException e) {
+                // Ignore
+            } catch (Exception e) {
+                // Unexpected
+                log(sm.getString("applicationContext.lookup.error", path,
+                        getContextPath()), e);
+            }
+        }
+        return (null);
+
+    }
+
+
+    /**
+     * Return a Set containing the resource paths of resources member of the
+     * specified collection. Each path will be a String starting with
+     * a "/" character. The returned set is immutable.
+     *
+     * @param path Collection path
+     */
+    @Override
+    public Set<String> getResourcePaths(String path) {
+
+        // Validate the path argument
+        if (path == null) {
+            return null;
+        }
+        if (!path.startsWith("/")) {
+            throw new IllegalArgumentException
+                (sm.getString("applicationContext.resourcePaths.iae", path));
+        }
+
+        String normalizedPath = RequestUtil.normalize(path);
+        if (normalizedPath == null)
+            return (null);
+
+        DirContext resources = context.getResources();
+        if (resources != null) {
+            return (getResourcePathsInternal(resources, normalizedPath));
+        }
+        return (null);
+
+    }
+
+
+    /**
+     * Internal implementation of getResourcesPath() logic.
+     *
+     * @param resources Directory context to search
+     * @param path Collection path
+     */
+    private Set<String> getResourcePathsInternal(DirContext resources,
+            String path) {
+
+        ResourceSet<String> set = new ResourceSet<String>();
+        try {
+            listCollectionPaths(set, resources, path);
+        } catch (NamingException e) {
+            return (null);
+        }
+        set.setLocked(true);
+        return (set);
+
+    }
+
+
+    /**
+     * Return the name and version of the servlet container.
+     */
+    @Override
+    public String getServerInfo() {
+
+        return (ServerInfo.getServerInfo());
+
+    }
+
+
+    /**
+     * @deprecated As of Java Servlet API 2.1, with no direct replacement.
+     */
+    @Override
+    @Deprecated
+    public Servlet getServlet(String name) {
+
+        return (null);
+
+    }
+
+
+    /**
+     * Return the display name of this web application.
+     */
+    @Override
+    public String getServletContextName() {
+
+        return (context.getDisplayName());
+
+    }
+
+
+    /**
+     * @deprecated As of Java Servlet API 2.1, with no direct replacement.
+     */
+    @Override
+    @Deprecated
+    public Enumeration<String> getServletNames() {
+        return (new Enumerator<String>(emptyString));
+    }
+
+
+    /**
+     * @deprecated As of Java Servlet API 2.1, with no direct replacement.
+     */
+    @Override
+    @Deprecated
+    public Enumeration<Servlet> getServlets() {
+        return (new Enumerator<Servlet>(emptyServlet));
+    }
+
+
+    /**
+     * Writes the specified message to a servlet log file.
+     *
+     * @param message Message to be written
+     */
+    @Override
+    public void log(String message) {
+
+        context.getLogger().info(message);
+
+    }
+
+
+    /**
+     * Writes the specified exception and message to a servlet log file.
+     *
+     * @param exception Exception to be reported
+     * @param message Message to be written
+     *
+     * @deprecated As of Java Servlet API 2.1, use
+     *  <code>log(String, Throwable)</code> instead
+     */
+    @Override
+    @Deprecated
+    public void log(Exception exception, String message) {
+        
+        context.getLogger().error(message, exception);
+
+    }
+
+
+    /**
+     * Writes the specified message and exception to a servlet log file.
+     *
+     * @param message Message to be written
+     * @param throwable Exception to be reported
+     */
+    @Override
+    public void log(String message, Throwable throwable) {
+        
+        context.getLogger().error(message, throwable);
+
+    }
+
+
+    /**
+     * Remove the context attribute with the specified name, if any.
+     *
+     * @param name Name of the context attribute to be removed
+     */
+    @Override
+    public void removeAttribute(String name) {
+
+        Object value = null;
+        boolean found = false;
+
+        // Remove the specified attribute
+        // Check for read only attribute
+        if (readOnlyAttributes.containsKey(name))
+            return;
+        found = attributes.containsKey(name);
+        if (found) {
+            value = attributes.get(name);
+            attributes.remove(name);
+        } else {
+            return;
+        }
+
+        // Notify interested application event listeners
+        Object listeners[] = context.getApplicationEventListeners();
+        if ((listeners == null) || (listeners.length == 0))
+            return;
+        ServletContextAttributeEvent event =
+          new ServletContextAttributeEvent(context.getServletContext(),
+                                            name, value);
+        for (int i = 0; i < listeners.length; i++) {
+            if (!(listeners[i] instanceof ServletContextAttributeListener))
+                continue;
+            ServletContextAttributeListener listener =
+                (ServletContextAttributeListener) listeners[i];
+            try {
+                context.fireContainerEvent("beforeContextAttributeRemoved",
+                                           listener);
+                listener.attributeRemoved(event);
+                context.fireContainerEvent("afterContextAttributeRemoved",
+                                           listener);
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                context.fireContainerEvent("afterContextAttributeRemoved",
+                                           listener);
+                // FIXME - should we do anything besides log these?
+                log(sm.getString("applicationContext.attributeEvent"), t);
+            }
+        }
+
+    }
+
+
+    /**
+     * Bind the specified value with the specified context attribute name,
+     * replacing any existing value for that name.
+     *
+     * @param name Attribute name to be bound
+     * @param value New attribute value to be bound
+     */
+    @Override
+    public void setAttribute(String name, Object value) {
+
+        // Name cannot be null
+        if (name == null)
+            throw new IllegalArgumentException
+                (sm.getString("applicationContext.setAttribute.namenull"));
+
+        // Null value is the same as removeAttribute()
+        if (value == null) {
+            removeAttribute(name);
+            return;
+        }
+
+        Object oldValue = null;
+        boolean replaced = false;
+
+        // Add or replace the specified attribute
+        // Check for read only attribute
+        if (readOnlyAttributes.containsKey(name))
+            return;
+        oldValue = attributes.get(name);
+        if (oldValue != null)
+            replaced = true;
+        attributes.put(name, value);
+
+        // Notify interested application event listeners
+        Object listeners[] = context.getApplicationEventListeners();
+        if ((listeners == null) || (listeners.length == 0))
+            return;
+        ServletContextAttributeEvent event = null;
+        if (replaced)
+            event =
+                new ServletContextAttributeEvent(context.getServletContext(),
+                                                 name, oldValue);
+        else
+            event =
+                new ServletContextAttributeEvent(context.getServletContext(),
+                                                 name, value);
+
+        for (int i = 0; i < listeners.length; i++) {
+            if (!(listeners[i] instanceof ServletContextAttributeListener))
+                continue;
+            ServletContextAttributeListener listener =
+                (ServletContextAttributeListener) listeners[i];
+            try {
+                if (replaced) {
+                    context.fireContainerEvent
+                        ("beforeContextAttributeReplaced", listener);
+                    listener.attributeReplaced(event);
+                    context.fireContainerEvent("afterContextAttributeReplaced",
+                                               listener);
+                } else {
+                    context.fireContainerEvent("beforeContextAttributeAdded",
+                                               listener);
+                    listener.attributeAdded(event);
+                    context.fireContainerEvent("afterContextAttributeAdded",
+                                               listener);
+                }
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                if (replaced)
+                    context.fireContainerEvent("afterContextAttributeReplaced",
+                                               listener);
+                else
+                    context.fireContainerEvent("afterContextAttributeAdded",
+                                               listener);
+                // FIXME - should we do anything besides log these?
+                log(sm.getString("applicationContext.attributeEvent"), t);
+            }
+        }
+
+    }
+
+
+    /**
+     * Add filter to context.
+     * @param   filterName  Name of filter to add
+     * @param   filterClass Name of filter class
+     * @return  <code>null</code> if the filter has already been fully defined,
+     *          else a {@link javax.servlet.FilterRegistration.Dynamic} object
+     *          that can be used to further configure the filter
+     * @throws IllegalStateException if the context has already been initialised
+     * @throws UnsupportedOperationException - if this context was passed to the
+     *         {@link ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)}
+     *         method of a {@link ServletContextListener} that was not declared
+     *         in web.xml, a web-fragment or annotated with
+     *         {@link javax.servlet.annotation.WebListener}.
+     */
+    @Override
+    public FilterRegistration.Dynamic addFilter(String filterName,
+            String filterClass) throws IllegalStateException {
+        
+        return addFilter(filterName, filterClass, null);
+    }
+
+    
+    /**
+     * Add filter to context.
+     * @param   filterName  Name of filter to add
+     * @param   filter      Filter to add
+     * @return  <code>null</code> if the filter has already been fully defined,
+     *          else a {@link javax.servlet.FilterRegistration.Dynamic} object
+     *          that can be used to further configure the filter
+     * @throws IllegalStateException if the context has already been initialised
+     * @throws UnsupportedOperationException - if this context was passed to the
+     *         {@link ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)}
+     *         method of a {@link ServletContextListener} that was not declared
+     *         in web.xml, a web-fragment or annotated with
+     *         {@link javax.servlet.annotation.WebListener}.
+     */
+    @Override
+    public FilterRegistration.Dynamic addFilter(String filterName,
+            Filter filter) throws IllegalStateException {
+        
+        return addFilter(filterName, null, filter);
+    }
+
+    
+    /**
+     * Add filter to context.
+     * @param   filterName  Name of filter to add
+     * @param   filterClass Class of filter to add
+     * @return  <code>null</code> if the filter has already been fully defined,
+     *          else a {@link javax.servlet.FilterRegistration.Dynamic} object
+     *          that can be used to further configure the filter
+     * @throws IllegalStateException if the context has already been initialised
+     * @throws UnsupportedOperationException - if this context was passed to the
+     *         {@link ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)}
+     *         method of a {@link ServletContextListener} that was not declared
+     *         in web.xml, a web-fragment or annotated with
+     *         {@link javax.servlet.annotation.WebListener}.
+     */
+    @Override
+    public FilterRegistration.Dynamic addFilter(String filterName,
+            Class<? extends Filter> filterClass) throws IllegalStateException {
+        
+        return addFilter(filterName, filterClass.getName(), null);
+    }
+
+    private FilterRegistration.Dynamic addFilter(String filterName,
+            String filterClass, Filter filter) throws IllegalStateException {
+        
+        if (!context.getState().equals(LifecycleState.STARTING_PREP)) {
+            //TODO Spec breaking enhancement to ignore this restriction
+            throw new IllegalStateException(
+                    sm.getString("applicationContext.addFilter.ise",
+                            getContextPath()));
+        }
+
+        // TODO SERVLET3
+        // throw UnsupportedOperationException - if this context was passed to the
+        // {@link ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)}
+        // method of a {@link ServletContextListener} that was not declared
+        // in web.xml, a web-fragment or annotated with
+        // {@link javax.servlet.annotation.WebListener}.
+
+        FilterDef filterDef = context.findFilterDef(filterName);
+        
+        // Assume a 'complete' FilterRegistration is one that has a class and
+        // a name
+        if (filterDef == null) {
+            filterDef = new FilterDef();
+            filterDef.setFilterName(filterName);
+            context.addFilterDef(filterDef);
+        } else {
+            if (filterDef.getFilterName() != null &&
+                    filterDef.getFilterClass() != null) {
+                return null;
+            }
+        }
+
+        if (filter == null) {
+            filterDef.setFilterClass(filterClass);
+        } else {
+            filterDef.setFilterClass(filter.getClass().getName());
+            filterDef.setFilter(filter);
+        }
+        
+        return new ApplicationFilterRegistration(filterDef, context);
+    } 
+    
+    @Override
+    public <T extends Filter> T createFilter(Class<T> c)
+    throws ServletException {
+        try {
+            @SuppressWarnings("unchecked")
+            T filter = (T) context.getInstanceManager().newInstance(c.getName());
+            return filter;
+        } catch (IllegalAccessException e) {
+            throw new ServletException(e);
+        } catch (InvocationTargetException e) {
+            throw new ServletException(e);
+        } catch (NamingException e) {
+            throw new ServletException(e);
+        } catch (InstantiationException e) {
+            throw new ServletException(e);
+        } catch (ClassNotFoundException e) {
+            throw new ServletException(e);
+        }
+    }
+
+
+    @Override
+    public FilterRegistration getFilterRegistration(String filterName) {
+        FilterDef filterDef = context.findFilterDef(filterName);
+        if (filterDef == null) {
+            return null;
+        }
+        return new ApplicationFilterRegistration(filterDef, context);
+    }
+
+    
+    /**
+     * Add servlet to context.
+     * @param   servletName  Name of servlet to add
+     * @param   servletClass Name of servlet class
+     * @return  <code>null</code> if the servlet has already been fully defined,
+     *          else a {@link javax.servlet.ServletRegistration.Dynamic} object
+     *          that can be used to further configure the servlet
+     * @throws IllegalStateException if the context has already been initialised
+     * @throws UnsupportedOperationException - if this context was passed to the
+     *         {@link ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)}
+     *         method of a {@link ServletContextListener} that was not declared
+     *         in web.xml, a web-fragment or annotated with
+     *         {@link javax.servlet.annotation.WebListener}.
+     */
+    @Override
+    public ServletRegistration.Dynamic addServlet(String servletName,
+            String servletClass) throws IllegalStateException {
+        
+        return addServlet(servletName, servletClass, null);
+    }
+
+
+    /**
+     * Add servlet to context.
+     * @param   servletName Name of servlet to add
+     * @param   servlet     Servlet instance to add
+     * @return  <code>null</code> if the servlet has already been fully defined,
+     *          else a {@link javax.servlet.ServletRegistration.Dynamic} object
+     *          that can be used to further configure the servlet
+     * @throws IllegalStateException if the context has already been initialised
+     * @throws UnsupportedOperationException - if this context was passed to the
+     *         {@link ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)}
+     *         method of a {@link ServletContextListener} that was not declared
+     *         in web.xml, a web-fragment or annotated with
+     *         {@link javax.servlet.annotation.WebListener}.
+     */
+    @Override
+    public ServletRegistration.Dynamic addServlet(String servletName,
+            Servlet servlet) throws IllegalStateException {
+
+        return addServlet(servletName, null, servlet);
+    }
+
+    
+    /**
+     * Add servlet to context.
+     * @param   servletName  Name of servlet to add
+     * @param   servletClass Class of servlet to add
+     * @return  <code>null</code> if the servlet has already been fully defined,
+     *          else a {@link javax.servlet.ServletRegistration.Dynamic} object
+     *          that can be used to further configure the servlet
+     * @throws IllegalStateException if the context has already been initialised
+     * @throws UnsupportedOperationException - if this context was passed to the
+     *         {@link ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)}
+     *         method of a {@link ServletContextListener} that was not declared
+     *         in web.xml, a web-fragment or annotated with
+     *         {@link javax.servlet.annotation.WebListener}.
+     */
+    @Override
+    public ServletRegistration.Dynamic addServlet(String servletName,
+            Class<? extends Servlet> servletClass)
+    throws IllegalStateException {
+
+        return addServlet(servletName, servletClass.getName(), null);
+    }
+
+    private ServletRegistration.Dynamic addServlet(String servletName,
+            String servletClass, Servlet servlet) throws IllegalStateException {
+        
+        if (!context.getState().equals(LifecycleState.STARTING_PREP)) {
+            //TODO Spec breaking enhancement to ignore this restriction
+            throw new IllegalStateException(
+                    sm.getString("applicationContext.addServlet.ise",
+                            getContextPath()));
+        }
+        
+        // TODO SERVLET3
+        // throw UnsupportedOperationException - if this context was passed to the
+        // {@link ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)}
+        // method of a {@link ServletContextListener} that was not declared
+        // in web.xml, a web-fragment or annotated with
+        // {@link javax.servlet.annotation.WebListener}.
+
+        Wrapper wrapper = (Wrapper) context.findChild(servletName);
+        
+        // Assume a 'complete' FilterRegistration is one that has a class and
+        // a name
+        if (wrapper == null) {
+            wrapper = context.createWrapper();
+            wrapper.setName(servletName);
+            context.addChild(wrapper);
+        } else {
+            if (wrapper.getName() != null &&
+                    wrapper.getServletClass() != null) {
+                return null;
+            }
+        }
+
+        if (servlet == null) {
+            wrapper.setServletClass(servletClass);
+        } else {
+            wrapper.setServletClass(servlet.getClass().getName());
+            wrapper.setServlet(servlet);
+        }
+
+        return context.dynamicServletAdded(wrapper);
+    }
+
+
+    @Override
+    public <T extends Servlet> T createServlet(Class<T> c)
+    throws ServletException {
+        try {
+            @SuppressWarnings("unchecked")
+            T servlet = (T) context.getInstanceManager().newInstance(c.getName());
+            context.dynamicServletCreated(servlet);
+            return servlet;
+        } catch (IllegalAccessException e) {
+            throw new ServletException(e);
+        } catch (InvocationTargetException e) {
+            throw new ServletException(e);
+        } catch (NamingException e) {
+            throw new ServletException(e);
+        } catch (InstantiationException e) {
+            throw new ServletException(e);
+        } catch (ClassNotFoundException e) {
+            throw new ServletException(e);
+        }
+    }
+
+
+    @Override
+    public ServletRegistration getServletRegistration(String servletName) {
+        Wrapper wrapper = (Wrapper) context.findChild(servletName);
+        if (wrapper == null) {
+            return null;
+        }
+        
+        return new ApplicationServletRegistration(wrapper, context);
+    }
+    
+
+    /**
+     * By default {@link SessionTrackingMode#URL} is always supported, {@link
+     * SessionTrackingMode#COOKIE} is supported unless the <code>cookies</code>
+     * attribute has been set to <code>false</code> for the context and {@link
+     * SessionTrackingMode#SSL} is supported if at least one of the connectors
+     * used by this context has the attribute <code>secure</code> set to
+     * <code>true</code>.
+     */
+    @Override
+    public Set<SessionTrackingMode> getDefaultSessionTrackingModes() {
+        return defaultSessionTrackingModes;
+    }
+
+    private void populateSessionTrackingModes() {
+        // URL re-writing is always enabled by default
+        defaultSessionTrackingModes = EnumSet.of(SessionTrackingMode.URL); 
+        supportedSessionTrackingModes = EnumSet.of(SessionTrackingMode.URL);
+        
+        if (context.getCookies()) {
+            defaultSessionTrackingModes.add(SessionTrackingMode.COOKIE);
+            supportedSessionTrackingModes.add(SessionTrackingMode.COOKIE);
+        }
+
+        // SSL not enabled by default as it can only used on its own 
+        // Context > Host > Engine > Service
+        Service s = ((Engine) context.getParent().getParent()).getService();
+        Connector[] connectors = s.findConnectors();
+        // Need at least one SSL enabled connector to use the SSL session ID.
+        for (Connector connector : connectors) {
+            if (Boolean.TRUE.equals(connector.getAttribute("SSLEnabled"))) {
+                supportedSessionTrackingModes.add(SessionTrackingMode.SSL);
+                break;
+            }
+        } 
+    }
+
+    /**
+     * Return the supplied value if one was previously set, else return the
+     * defaults.
+     */
+    @Override
+    public Set<SessionTrackingMode> getEffectiveSessionTrackingModes() {
+        if (sessionTrackingModes != null) {
+            return sessionTrackingModes;
+        }
+        return defaultSessionTrackingModes;
+    }
+
+
+    @Override
+    public SessionCookieConfig getSessionCookieConfig() {
+        return sessionCookieConfig;
+    }
+
+
+    /**
+     * @throws IllegalStateException if the context has already been initialised
+     * @throws IllegalArgumentException If SSL is requested in combination with
+     *                                  anything else or if an unsupported
+     *                                  tracking mode is requested
+     */
+    @Override
+    public void setSessionTrackingModes(
+            Set<SessionTrackingMode> sessionTrackingModes) {
+
+        if (!context.getState().equals(LifecycleState.STARTING_PREP)) {
+            throw new IllegalStateException(
+                    sm.getString("applicationContext.setSessionTracking.ise",
+                            getContextPath()));
+        }
+        
+        // Check that only supported tracking modes have been requested
+        for (SessionTrackingMode sessionTrackingMode : sessionTrackingModes) {
+            if (!supportedSessionTrackingModes.contains(sessionTrackingMode)) {
+                throw new IllegalArgumentException(sm.getString(
+                        "applicationContext.setSessionTracking.iae.invalid",
+                        sessionTrackingMode.toString(), getContextPath()));
+            }
+        }
+
+        // Check SSL has not be configured with anything else
+        if (sessionTrackingModes.contains(SessionTrackingMode.SSL)) {
+            if (sessionTrackingModes.size() > 1) {
+                throw new IllegalArgumentException(sm.getString(
+                        "applicationContext.setSessionTracking.iae.ssl",
+                        getContextPath()));
+            }
+        }
+        
+        this.sessionTrackingModes = sessionTrackingModes;
+    }
+
+
+    @Override
+    public boolean setInitParameter(String name, String value) {
+        if (parameters.containsKey(name)) {
+            return false;
+        }
+        
+        parameters.put(name, value);
+        return true;
+    }
+    
+    
+    @Override
+    public void addListener(Class<? extends EventListener> listenerClass) {
+        EventListener listener;
+        try {
+            listener = createListener(listenerClass);
+        } catch (ServletException e) {
+            throw new IllegalArgumentException(sm.getString(
+                    "applicationContext.addListener.iae.init",
+                    listenerClass.getName()), e);
+        }
+        addListener(listener);
+    }
+
+
+    @Override
+    public void addListener(String className) {
+        
+        try {
+            Object obj = context.getInstanceManager().newInstance(className);
+
+            if (!(obj instanceof EventListener)) {
+                throw new IllegalArgumentException(sm.getString(
+                        "applicationContext.addListener.iae.wrongType",
+                        className));
+            }
+
+            EventListener listener = (EventListener) obj;
+            addListener(listener);
+        } catch (IllegalAccessException e) {
+            throw new IllegalArgumentException(sm.getString(
+                    "applicationContext.addListener.iae.cnfe", className),
+                    e);
+        } catch (InvocationTargetException e) {
+            throw new IllegalArgumentException(sm.getString(
+                    "applicationContext.addListener.iae.cnfe", className),
+                    e);
+        } catch (NamingException e) {
+            throw new IllegalArgumentException(sm.getString(
+                    "applicationContext.addListener.iae.cnfe", className),
+                    e);
+        } catch (InstantiationException e) {
+            throw new IllegalArgumentException(sm.getString(
+                    "applicationContext.addListener.iae.cnfe", className),
+                    e);
+        } catch (ClassNotFoundException e) {
+            throw new IllegalArgumentException(sm.getString(
+                    "applicationContext.addListener.iae.cnfe", className),
+                    e);
+        }
+        
+    }
+
+
+    @Override
+    public <T extends EventListener> void addListener(T t) {
+        if (!context.getState().equals(LifecycleState.STARTING_PREP)) {
+            throw new IllegalStateException(
+                    sm.getString("applicationContext.addListener.ise",
+                            getContextPath()));
+        }
+
+        // TODO SERVLET3
+        // throw UnsupportedOperationException - if this context was passed to the
+        // {@link ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)}
+        // method of a {@link ServletContextListener} that was not declared
+        // in web.xml, a web-fragment or annotated with
+        // {@link javax.servlet.annotation.WebListener}.
+        
+        boolean match = false;
+        if (t instanceof ServletContextAttributeListener ||
+                t instanceof ServletRequestListener ||
+                t instanceof ServletRequestAttributeListener ||
+                t instanceof HttpSessionAttributeListener) {
+            context.addApplicationEventListener(t);
+            match = true;
+        }
+        
+        if (t instanceof HttpSessionListener
+                || (t instanceof ServletContextListener &&
+                        newServletContextListenerAllowed)) {
+            context.addApplicationLifecycleListener(t);
+            match = true;
+        }
+        
+        if (match) return;
+        
+        if (t instanceof ServletContextListener) {
+            throw new IllegalArgumentException(sm.getString(
+                    "applicationContext.addListener.iae.sclNotAllowed",
+                    t.getClass().getName()));
+        } else {
+            throw new IllegalArgumentException(sm.getString(
+                    "applicationContext.addListener.iae.wrongType",
+                    t.getClass().getName()));
+        }
+    }
+
+
+    @Override
+    public <T extends EventListener> T createListener(Class<T> c)
+            throws ServletException {
+        try {
+            @SuppressWarnings("unchecked")
+            T listener =
+                (T) context.getInstanceManager().newInstance(c.getName());
+            if (listener instanceof ServletContextListener ||
+                    listener instanceof ServletContextAttributeListener ||
+                    listener instanceof ServletRequestListener ||
+                    listener instanceof ServletRequestAttributeListener ||
+                    listener instanceof HttpSessionListener ||
+                    listener instanceof HttpSessionAttributeListener) {
+                return listener;
+            }
+            throw new IllegalArgumentException(sm.getString(
+                    "applicationContext.addListener.iae.wrongType",
+                    listener.getClass().getName()));
+        } catch (IllegalAccessException e) {
+            throw new ServletException(e);
+        } catch (InvocationTargetException e) {
+            throw new ServletException(e);
+        } catch (NamingException e) {
+            throw new ServletException(e);
+        } catch (InstantiationException e) {
+            throw new ServletException(e);
+        } catch (ClassNotFoundException e) {
+            throw new ServletException(e);
+        }    }
+
+
+    @Override
+    public void declareRoles(String... roleNames) {
+        
+        if (!context.getState().equals(LifecycleState.STARTING_PREP)) {
+            //TODO Spec breaking enhancement to ignore this restriction
+            throw new IllegalStateException(
+                    sm.getString("applicationContext.addRole.ise",
+                            getContextPath()));
+        }
+        
+        if (roleNames == null) {
+            throw new IllegalArgumentException(
+                    sm.getString("applicationContext.roles.iae",
+                            getContextPath()));
+        }
+        
+        for (String role : roleNames) {
+            if (role == null || "".equals(role)) {
+                throw new IllegalArgumentException(
+                        sm.getString("applicationContext.role.iae",
+                                getContextPath()));
+            }
+            context.addSecurityRole(role);
+        }
+    }
+
+
+    @Override
+    public ClassLoader getClassLoader() {
+        ClassLoader result = context.getLoader().getClassLoader();
+        if (Globals.IS_SECURITY_ENABLED) {
+            ClassLoader tccl = Thread.currentThread().getContextClassLoader();
+            ClassLoader parent = result;
+            while (parent != null) {
+                if (parent == tccl) {
+                    break;
+                }
+                parent = parent.getParent();
+            }
+            if (parent == null) {
+                System.getSecurityManager().checkPermission(
+                        new RuntimePermission("getClassLoader"));
+            }
+        }
+        
+        return result;
+    }
+
+
+    @Override
+    public int getEffectiveMajorVersion() {
+        return context.getEffectiveMajorVersion();
+    }
+
+
+    @Override
+    public int getEffectiveMinorVersion() {
+        return context.getEffectiveMinorVersion();
+    }
+
+
+    @Override
+    public Map<String, ? extends FilterRegistration> getFilterRegistrations() {
+        Map<String, ApplicationFilterRegistration> result =
+            new HashMap<String, ApplicationFilterRegistration>();
+        
+        FilterDef[] filterDefs = context.findFilterDefs();
+        for (FilterDef filterDef : filterDefs) {
+            result.put(filterDef.getFilterName(),
+                    new ApplicationFilterRegistration(filterDef, context));
+        }
+
+        return result;
+    }
+
+
+    @Override
+    public JspConfigDescriptor getJspConfigDescriptor() {
+        return context.getJspConfigDescriptor();
+    }
+
+
+    @Override
+    public Map<String, ? extends ServletRegistration> getServletRegistrations() {
+        Map<String, ApplicationServletRegistration> result =
+            new HashMap<String, ApplicationServletRegistration>();
+        
+        Container[] wrappers = context.findChildren();
+        for (Container wrapper : wrappers) {
+            result.put(((Wrapper) wrapper).getName(),
+                    new ApplicationServletRegistration(
+                            (Wrapper) wrapper, context));
+        }
+
+        return result;
+    }
+
+    
+    // -------------------------------------------------------- Package Methods
+    protected StandardContext getContext() {
+        return this.context;
+    }
+    
+    protected Map<String,String> getReadonlyAttributes() {
+        return this.readOnlyAttributes;
+    }
+    /**
+     * Clear all application-created attributes.
+     */
+    protected void clearAttributes() {
+
+        // Create list of attributes to be removed
+        ArrayList<String> list = new ArrayList<String>();
+        Iterator<String> iter = attributes.keySet().iterator();
+        while (iter.hasNext()) {
+            list.add(iter.next());
+        }
+
+        // Remove application originated attributes
+        // (read only attributes will be left in place)
+        Iterator<String> keys = list.iterator();
+        while (keys.hasNext()) {
+            String key = keys.next();
+            removeAttribute(key);
+        }
+        
+    }
+    
+    
+    /**
+     * Return the facade associated with this ApplicationContext.
+     */
+    protected ServletContext getFacade() {
+
+        return (this.facade);
+
+    }
+
+
+    /**
+     * Set an attribute as read only.
+     */
+    void setAttributeReadOnly(String name) {
+
+        if (attributes.containsKey(name))
+            readOnlyAttributes.put(name, name);
+
+    }
+
+
+    protected void setNewServletContextListenerAllowed(boolean allowed) {
+        this.newServletContextListenerAllowed = allowed;
+    }
+    
+    /**
+     * List resource paths (recursively), and store all of them in the given
+     * Set.
+     */
+    private static void listCollectionPaths(Set<String> set,
+            DirContext resources, String path) throws NamingException {
+
+        Enumeration<Binding> childPaths = resources.listBindings(path);
+        while (childPaths.hasMoreElements()) {
+            Binding binding = childPaths.nextElement();
+            String name = binding.getName();
+            StringBuilder childPath = new StringBuilder(path);
+            if (!"/".equals(path) && !path.endsWith("/"))
+                childPath.append("/");
+            childPath.append(name);
+            Object object = binding.getObject();
+            if (object instanceof DirContext) {
+                childPath.append("/");
+            }
+            set.add(childPath.toString());
+        }
+
+    }
+
+
+    /**
+     * Get full path, based on the host name and the context path.
+     */
+    private static String getJNDIUri(String hostName, String path) {
+        String result;
+        
+        if (path.startsWith("/")) {
+            result = "/" + hostName + path;
+        } else {
+            result = "/" + hostName + "/" + path;
+        }
+        
+        return result;
+    }
+
+
+    /**
+     * Internal class used as thread-local storage when doing path
+     * mapping during dispatch.
+     */
+    private static final class DispatchData {
+
+        public MessageBytes uriMB;
+        public MappingData mappingData;
+
+        public DispatchData() {
+            uriMB = MessageBytes.newInstance();
+            CharChunk uriCC = uriMB.getCharChunk();
+            uriCC.setLimit(-1);
+            mappingData = new MappingData();
+        }
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationContextFacade.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationContextFacade.java
new file mode 100644
index 0000000..e12c777
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationContextFacade.java
@@ -0,0 +1,882 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.EnumSet;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterRegistration;
+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.catalina.Globals;
+import org.apache.catalina.security.SecurityUtil;
+import org.apache.tomcat.util.ExceptionUtils;
+
+
+/**
+ * Facade object which masks the internal <code>ApplicationContext</code>
+ * object from the web application.
+ *
+ * @author Remy Maucherat
+ * @author Jean-Francois Arcand
+ * @version $Id: ApplicationContextFacade.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+public class ApplicationContextFacade implements ServletContext {
+        
+    // ---------------------------------------------------------- Attributes
+    /**
+     * Cache Class object used for reflection.
+     */
+    private HashMap<String,Class<?>[]> classCache;
+    
+    
+    /**
+     * Cache method object.
+     */
+    private HashMap<String,Method> objectCache;
+    
+    
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new instance of this class, associated with the specified
+     * Context instance.
+     *
+     * @param context The associated Context instance
+     */
+    public ApplicationContextFacade(ApplicationContext context) {
+        super();
+        this.context = context;
+        
+        classCache = new HashMap<String,Class<?>[]>();
+        objectCache = new HashMap<String,Method>();
+        initClassCache();
+    }
+    
+    
+    private void initClassCache(){
+        Class<?>[] clazz = new Class[]{String.class};
+        classCache.put("getContext", clazz);
+        classCache.put("getMimeType", clazz);
+        classCache.put("getResourcePaths", clazz);
+        classCache.put("getResource", clazz);
+        classCache.put("getResourceAsStream", clazz);
+        classCache.put("getRequestDispatcher", clazz);
+        classCache.put("getNamedDispatcher", clazz);
+        classCache.put("getServlet", clazz);
+        classCache.put("setInitParameter", new Class[]{String.class, String.class});
+        classCache.put("createServlet", new Class[]{Class.class});
+        classCache.put("addServlet", new Class[]{String.class, String.class});
+        classCache.put("createFilter", new Class[]{Class.class});
+        classCache.put("addFilter", new Class[]{String.class, String.class});
+        classCache.put("createListener", new Class[]{Class.class});
+        classCache.put("addListener", clazz);
+        classCache.put("getFilterRegistration", clazz);
+        classCache.put("getServletRegistration", clazz);
+        classCache.put("getInitParameter", clazz);
+        classCache.put("setAttribute", new Class[]{String.class, Object.class});
+        classCache.put("removeAttribute", clazz);
+        classCache.put("getRealPath", clazz);
+        classCache.put("getAttribute", clazz);
+        classCache.put("log", clazz);
+        classCache.put("setSessionTrackingModes", new Class[]{EnumSet.class} );
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Wrapped application context.
+     */
+    private ApplicationContext context = null;
+
+
+    // ------------------------------------------------- ServletContext Methods
+
+
+    @Override
+    public ServletContext getContext(String uripath) {
+        ServletContext theContext = null;
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            theContext = (ServletContext)
+                doPrivileged("getContext", new Object[]{uripath});
+        } else {
+            theContext = context.getContext(uripath);
+        }
+        if ((theContext != null) &&
+            (theContext instanceof ApplicationContext)){
+            theContext = ((ApplicationContext)theContext).getFacade();
+        }
+        return (theContext);
+    }
+
+
+    @Override
+    public int getMajorVersion() {
+        return context.getMajorVersion();
+    }
+
+
+    @Override
+    public int getMinorVersion() {
+        return context.getMinorVersion();
+    }
+
+
+    @Override
+    public String getMimeType(String file) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (String)doPrivileged("getMimeType", new Object[]{file});
+        } else {
+            return context.getMimeType(file);
+        }
+    }
+
+    @Override
+    @SuppressWarnings("unchecked") // doPrivileged() returns the correct type
+    public Set<String> getResourcePaths(String path) {
+        if (SecurityUtil.isPackageProtectionEnabled()){
+            return (Set<String>)doPrivileged("getResourcePaths",
+                    new Object[]{path});
+        } else {
+            return context.getResourcePaths(path);
+        }
+    }
+
+
+    @Override
+    public URL getResource(String path)
+        throws MalformedURLException {
+        if (Globals.IS_SECURITY_ENABLED) {
+            try {
+                return (URL) invokeMethod(context, "getResource", 
+                                          new Object[]{path});
+            } catch(Throwable t) {
+                if (t instanceof MalformedURLException){
+                    throw (MalformedURLException)t;
+                }
+                return null;
+            }
+        } else {
+            return context.getResource(path);
+        }
+    }
+
+
+    @Override
+    public InputStream getResourceAsStream(String path) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (InputStream) doPrivileged("getResourceAsStream", 
+                                              new Object[]{path});
+        } else {
+            return context.getResourceAsStream(path);
+        }
+    }
+
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(final String path) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (RequestDispatcher) doPrivileged("getRequestDispatcher", 
+                                                    new Object[]{path});
+        } else {
+            return context.getRequestDispatcher(path);
+        }
+    }
+
+
+    @Override
+    public RequestDispatcher getNamedDispatcher(String name) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (RequestDispatcher) doPrivileged("getNamedDispatcher", 
+                                                    new Object[]{name});
+        } else {
+            return context.getNamedDispatcher(name);
+        }
+    }
+
+
+    /**
+     * @deprecated
+     */
+    @Override
+    @Deprecated
+    public Servlet getServlet(String name)
+        throws ServletException {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            try {
+                return (Servlet) invokeMethod(context, "getServlet", 
+                                              new Object[]{name});
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                if (t instanceof ServletException) {
+                    throw (ServletException) t;
+                }
+                return null;
+            }
+        } else {
+            return context.getServlet(name);
+        }
+    }
+
+
+    /**
+     * @deprecated
+     */
+    @Override
+    @SuppressWarnings("unchecked") // doPrivileged() returns the correct type
+    @Deprecated
+    public Enumeration<Servlet> getServlets() {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (Enumeration<Servlet>) doPrivileged("getServlets", null);
+        } else {
+            return context.getServlets();
+        }
+    }
+
+
+    /**
+     * @deprecated
+     */
+    @Override
+    @SuppressWarnings("unchecked") // doPrivileged() returns the correct type
+    @Deprecated
+    public Enumeration<String> getServletNames() {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (Enumeration<String>) doPrivileged("getServletNames", null);
+        } else {
+            return context.getServletNames();
+        }
+   }
+
+
+    @Override
+    public void log(String msg) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            doPrivileged("log", new Object[]{msg} );
+        } else {
+            context.log(msg);
+        }
+    }
+
+
+    /**
+     * @deprecated
+     */
+    @Override
+    @Deprecated
+    public void log(Exception exception, String msg) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            doPrivileged("log", new Class[]{Exception.class, String.class}, 
+                         new Object[]{exception,msg});
+        } else {
+            context.log(exception, msg);
+        }
+    }
+
+
+    @Override
+    public void log(String message, Throwable throwable) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            doPrivileged("log", new Class[]{String.class, Throwable.class}, 
+                         new Object[]{message, throwable});
+        } else {
+            context.log(message, throwable);
+        }
+    }
+
+
+    @Override
+    public String getRealPath(String path) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (String) doPrivileged("getRealPath", new Object[]{path});
+        } else {
+            return context.getRealPath(path);
+        }
+    }
+
+
+    @Override
+    public String getServerInfo() {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (String) doPrivileged("getServerInfo", null);
+        } else {
+            return context.getServerInfo();
+        }
+    }
+
+
+    @Override
+    public String getInitParameter(String name) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (String) doPrivileged("getInitParameter", 
+                                         new Object[]{name});
+        } else {
+            return context.getInitParameter(name);
+        }
+    }
+
+
+    @Override
+    @SuppressWarnings("unchecked") // doPrivileged() returns the correct type
+    public Enumeration<String> getInitParameterNames() {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (Enumeration<String>) doPrivileged(
+                    "getInitParameterNames", null);
+        } else {
+            return context.getInitParameterNames();
+        }
+    }
+
+
+    @Override
+    public Object getAttribute(String name) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return doPrivileged("getAttribute", new Object[]{name});
+        } else {
+            return context.getAttribute(name);
+        }
+     }
+
+
+    @Override
+    @SuppressWarnings("unchecked") // doPrivileged() returns the correct type
+    public Enumeration<String> getAttributeNames() {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (Enumeration<String>) doPrivileged(
+                    "getAttributeNames", null);
+        } else {
+            return context.getAttributeNames();
+        }
+    }
+
+
+    @Override
+    public void setAttribute(String name, Object object) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            doPrivileged("setAttribute", new Object[]{name,object});
+        } else {
+            context.setAttribute(name, object);
+        }
+    }
+
+
+    @Override
+    public void removeAttribute(String name) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            doPrivileged("removeAttribute", new Object[]{name});
+        } else {
+            context.removeAttribute(name);
+        }
+    }
+
+
+    @Override
+    public String getServletContextName() {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (String) doPrivileged("getServletContextName", null);
+        } else {
+            return context.getServletContextName();
+        }
+    }
+
+       
+    @Override
+    public String getContextPath() {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (String) doPrivileged("getContextPath", null);
+        } else {
+            return context.getContextPath();
+        }
+    }
+
+       
+    @Override
+    public FilterRegistration.Dynamic addFilter(String filterName,
+            String className) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (FilterRegistration.Dynamic) doPrivileged(
+                    "addFilter", new Object[]{filterName, className});
+        } else {
+            return context.addFilter(filterName, className);
+        }
+    }
+
+
+    @Override
+    public FilterRegistration.Dynamic addFilter(String filterName,
+            Filter filter) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (FilterRegistration.Dynamic) doPrivileged(
+                    "addFilter", new Class[]{String.class, Filter.class}, new Object[]{filterName, filter});
+        } else {
+            return context.addFilter(filterName, filter);
+        }
+    }
+
+
+    @Override
+    public FilterRegistration.Dynamic addFilter(String filterName,
+            Class<? extends Filter> filterClass) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (FilterRegistration.Dynamic) doPrivileged(
+                    "addFilter", new Object[]{filterName, filterClass.getName()});
+        } else {
+            return context.addFilter(filterName, filterClass);
+        }
+    }
+
+    @Override
+    @SuppressWarnings("unchecked") // doPrivileged() returns the correct type
+    public <T extends Filter> T createFilter(Class<T> c)
+    throws ServletException {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            try {
+                return (T) invokeMethod(context, "createFilter", 
+                                              new Object[]{c});
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                if (t instanceof ServletException) {
+                    throw (ServletException) t;
+                }
+                return null;
+            }
+        } else {
+            return context.createFilter(c);
+        }
+    }
+
+
+    @Override
+    public FilterRegistration getFilterRegistration(String filterName) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (FilterRegistration) doPrivileged(
+                    "getFilterRegistration", new Object[]{filterName});
+        } else {
+            return context.getFilterRegistration(filterName);
+        }
+    }
+    
+    
+    @Override
+    public ServletRegistration.Dynamic addServlet(String servletName,
+            String className) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (ServletRegistration.Dynamic) doPrivileged(
+                    "addServlet", new Object[]{servletName, className});
+        } else {
+            return context.addServlet(servletName, className);
+        }
+    }
+
+
+    @Override
+    public ServletRegistration.Dynamic addServlet(String servletName,
+            Servlet servlet) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (ServletRegistration.Dynamic) doPrivileged(
+                    "addServlet", new Class[]{String.class, Servlet.class}, new Object[]{servletName, servlet});
+        } else {
+            return context.addServlet(servletName, servlet);
+        }
+    }
+
+
+    @Override
+    public ServletRegistration.Dynamic addServlet(String servletName,
+            Class<? extends Servlet> servletClass) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (ServletRegistration.Dynamic) doPrivileged(
+                    "addServlet", new Object[]{servletName, servletClass.getName()});
+        } else {
+            return context.addServlet(servletName, servletClass);
+        }
+    }
+
+
+    @Override
+    @SuppressWarnings("unchecked") // doPrivileged() returns the correct type
+    public <T extends Servlet> T createServlet(Class<T> c)
+    throws ServletException {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            try {
+                return (T) invokeMethod(context, "createServlet", 
+                                              new Object[]{c});
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                if (t instanceof ServletException) {
+                    throw (ServletException) t;
+                }
+                return null;
+            }
+        } else {
+            return context.createServlet(c);
+        }
+    }
+
+    
+    @Override
+    public ServletRegistration getServletRegistration(String servletName) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (ServletRegistration) doPrivileged(
+                    "getServletRegistration", new Object[]{servletName});
+        } else {
+            return context.getServletRegistration(servletName);
+        }
+    }
+    
+    
+    @Override
+    @SuppressWarnings("unchecked") // doPrivileged() returns the correct type
+    public Set<SessionTrackingMode> getDefaultSessionTrackingModes() {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (EnumSet<SessionTrackingMode>)
+                doPrivileged("getDefaultSessionTrackingModes", null);
+        } else {
+            return context.getDefaultSessionTrackingModes();
+        }
+    }
+
+    @Override
+    @SuppressWarnings("unchecked") // doPrivileged() returns the correct type
+    public Set<SessionTrackingMode> getEffectiveSessionTrackingModes() {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (EnumSet<SessionTrackingMode>)
+                doPrivileged("getEffectiveSessionTrackingModes", null);
+        } else {
+            return context.getEffectiveSessionTrackingModes();
+        }
+    }
+
+
+    @Override
+    public SessionCookieConfig getSessionCookieConfig() {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (SessionCookieConfig)
+                doPrivileged("getSessionCookieConfig", null);
+        } else {
+            return context.getSessionCookieConfig();
+        }
+    }
+
+
+    @Override
+    public void setSessionTrackingModes(
+            Set<SessionTrackingMode> sessionTrackingModes) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            doPrivileged("setSessionTrackingModes",
+                    new Object[]{sessionTrackingModes});
+        } else {
+            context.setSessionTrackingModes(sessionTrackingModes);
+        }
+    }
+
+
+    @Override
+    public boolean setInitParameter(String name, String value) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return ((Boolean) doPrivileged("setInitParameter",
+                    new Object[]{name, value})).booleanValue();
+        } else {
+            return context.setInitParameter(name, value);
+        }
+    }
+
+
+    @Override
+    public void addListener(Class<? extends EventListener> listenerClass) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            doPrivileged("addListener",
+                    new Object[]{listenerClass.getName()});
+        } else {
+            context.addListener(listenerClass);
+        }
+    }
+
+
+    @Override
+    public void addListener(String className) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            doPrivileged("addListener",
+                    new Object[]{className});
+        } else {
+            context.addListener(className);
+        }
+    }
+
+
+    @Override
+    public <T extends EventListener> void addListener(T t) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            doPrivileged("addListener",
+                    new Object[]{t.getClass().getName()});
+        } else {
+            context.addListener(t);
+        }
+    }
+
+
+    @Override
+    @SuppressWarnings("unchecked") // doPrivileged() returns the correct type
+    public <T extends EventListener> T createListener(Class<T> c)
+            throws ServletException {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            try {
+                return (T) invokeMethod(context, "createListener", 
+                                              new Object[]{c});
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                if (t instanceof ServletException) {
+                    throw (ServletException) t;
+                }
+                return null;
+            }
+        } else {
+            return context.createListener(c);
+        }
+    }
+
+
+    @Override
+    public void declareRoles(String... roleNames) {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+//FIXME
+            doPrivileged("declareRoles",
+                    new Object[]{roleNames});
+        } else {
+            context.declareRoles(roleNames);
+        }
+    }
+
+
+    @Override
+    public ClassLoader getClassLoader() {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (ClassLoader) doPrivileged("getClassLoader", null);
+        } else {
+            return context.getClassLoader();
+        }
+    }
+
+
+    @Override
+    public int getEffectiveMajorVersion() {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return ((Integer) doPrivileged("getEffectiveMajorVersion",
+                    null)).intValue();
+        } else  {
+            return context.getEffectiveMajorVersion();
+        }
+    }
+
+
+    @Override
+    public int getEffectiveMinorVersion() {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return ((Integer) doPrivileged("getEffectiveMinorVersion",
+                    null)).intValue();
+        } else  {
+            return context.getEffectiveMinorVersion();
+        }
+    }
+
+
+    @Override
+    @SuppressWarnings("unchecked") // doPrivileged() returns the correct type
+    public Map<String, ? extends FilterRegistration> getFilterRegistrations() {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (Map<String, ? extends FilterRegistration>) doPrivileged(
+                    "getFilterRegistrations", null);
+        } else {
+            return context.getFilterRegistrations();
+        }
+    }
+
+
+    @Override
+    public JspConfigDescriptor getJspConfigDescriptor() {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (JspConfigDescriptor) doPrivileged("getJspConfigDescriptor",
+                    null);
+        } else {
+            return context.getJspConfigDescriptor();
+        }
+    }
+
+
+    @Override
+    @SuppressWarnings("unchecked") // doPrivileged() returns the correct type
+    public Map<String, ? extends ServletRegistration> getServletRegistrations() {
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            return (Map<String, ? extends ServletRegistration>) doPrivileged(
+                    "getServletRegistrations", null);
+        } else {
+            return context.getServletRegistrations();
+        }
+    }
+
+    /**
+     * Use reflection to invoke the requested method. Cache the method object 
+     * to speed up the process
+     * @param methodName The method to call.
+     * @param params The arguments passed to the called method.
+     */
+    private Object doPrivileged(final String methodName, final Object[] params) {
+        try{
+            return invokeMethod(context, methodName, params);
+        }catch(Throwable t){
+            throw new RuntimeException(t.getMessage(), t);
+        }
+    }
+
+    
+    /**
+     * Use reflection to invoke the requested method. Cache the method object 
+     * to speed up the process
+     * @param appContext The AppliationContext object on which the method
+     *                   will be invoked
+     * @param methodName The method to call.
+     * @param params The arguments passed to the called method.
+     */
+    private Object invokeMethod(ApplicationContext appContext,
+                                final String methodName, 
+                                Object[] params) 
+        throws Throwable{
+
+        try{
+            Method method = objectCache.get(methodName);
+            if (method == null){
+                method = appContext.getClass()
+                    .getMethod(methodName, classCache.get(methodName));
+                objectCache.put(methodName, method);
+            }
+            
+            return executeMethod(method,appContext,params);
+        } catch (Exception ex){
+            handleException(ex);
+            return null;
+        } finally {
+            params = null;
+        }
+    }
+    
+    /**
+     * Use reflection to invoke the requested method. Cache the method object 
+     * to speed up the process
+     * @param methodName The method to invoke.
+     * @param clazz The class where the method is.
+     * @param params The arguments passed to the called method.
+     */    
+    private Object doPrivileged(final String methodName, 
+                                final Class<?>[] clazz,
+                                Object[] params) {
+
+        try{
+            Method method = context.getClass().getMethod(methodName, clazz);
+            return executeMethod(method,context,params);
+        } catch (Exception ex){
+            try {
+                handleException(ex);
+            } catch (Throwable t){
+                ExceptionUtils.handleThrowable(t);
+                throw new RuntimeException(t.getMessage());
+            }
+            return null;
+        } finally {
+            params = null;
+        }
+    }
+    
+    
+    /**
+     * Executes the method of the specified <code>ApplicationContext</code>
+     * @param method The method object to be invoked.
+     * @param context The AppliationContext object on which the method
+     *                   will be invoked
+     * @param params The arguments passed to the called method.
+     */
+    private Object executeMethod(final Method method, 
+                                 final ApplicationContext context,
+                                 final Object[] params) 
+            throws PrivilegedActionException, 
+                   IllegalAccessException,
+                   InvocationTargetException {
+                                     
+        if (SecurityUtil.isPackageProtectionEnabled()){
+           return AccessController.doPrivileged(new PrivilegedExceptionAction<Object>(){
+                @Override
+                public Object run() throws IllegalAccessException, InvocationTargetException{
+                    return method.invoke(context,  params);
+                }
+            });
+        } else {
+            return method.invoke(context, params);
+        }        
+    }
+
+    
+    /**
+     *
+     * Throw the real exception.
+     * @param ex The current exception
+     */
+    private void handleException(Exception ex)
+        throws Throwable {
+
+        Throwable realException;
+        
+        if (ex instanceof PrivilegedActionException) {
+            ex = ((PrivilegedActionException) ex).getException();
+        }
+        
+        if (ex instanceof InvocationTargetException) {
+            realException =
+                ((InvocationTargetException) ex).getTargetException();
+        } else {
+            realException = ex;
+        }   
+        
+        throw realException;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationDispatcher.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationDispatcher.java
new file mode 100644
index 0000000..5def8b7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationDispatcher.java
@@ -0,0 +1,1021 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestWrapper;
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletResponseWrapper;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Globals;
+import org.apache.catalina.InstanceEvent;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.connector.ClientAbortException;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.RequestFacade;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.connector.ResponseFacade;
+import org.apache.catalina.util.InstanceSupport;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Standard implementation of <code>RequestDispatcher</code> that allows a
+ * request to be forwarded to a different resource to create the ultimate
+ * response, or to include the output of another resource in the response
+ * from this resource.  This implementation allows application level servlets
+ * to wrap the request and/or response objects that are passed on to the
+ * called resource, as long as the wrapping classes extend
+ * <code>javax.servlet.ServletRequestWrapper</code> and
+ * <code>javax.servlet.ServletResponseWrapper</code>.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: ApplicationDispatcher.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+final class ApplicationDispatcher
+    implements RequestDispatcher {
+
+    protected static final boolean STRICT_SERVLET_COMPLIANCE;
+
+    protected static final boolean WRAP_SAME_OBJECT;
+
+
+    static {
+        STRICT_SERVLET_COMPLIANCE = Globals.STRICT_SERVLET_COMPLIANCE;
+        
+        String wrapSameObject = System.getProperty(
+                "org.apache.catalina.core.ApplicationDispatcher.WRAP_SAME_OBJECT");
+        if (wrapSameObject == null) {
+            WRAP_SAME_OBJECT = STRICT_SERVLET_COMPLIANCE;
+        } else {
+            WRAP_SAME_OBJECT =
+                Boolean.valueOf(wrapSameObject).booleanValue();
+        }
+    }
+
+
+    protected class PrivilegedForward
+            implements PrivilegedExceptionAction<Void> {
+        private ServletRequest request;
+        private ServletResponse response;
+
+        PrivilegedForward(ServletRequest request, ServletResponse response)
+        {
+            this.request = request;
+            this.response = response;
+        }
+
+        @Override
+        public Void run() throws java.lang.Exception {
+            doForward(request,response);
+            return null;
+        }
+    }
+
+    protected class PrivilegedInclude implements
+            PrivilegedExceptionAction<Void> {
+        private ServletRequest request;
+        private ServletResponse response;
+
+        PrivilegedInclude(ServletRequest request, ServletResponse response)
+        {
+            this.request = request;
+            this.response = response;
+        }
+
+        @Override
+        public Void run() throws ServletException, IOException {
+            DispatcherType type = DispatcherType.INCLUDE;
+            if (request.getDispatcherType()==DispatcherType.ASYNC) type = DispatcherType.ASYNC; 
+            doInclude(request,response,type);
+            return null;
+        }
+    }
+
+    
+    /**
+     * Used to pass state when the request dispatcher is used. Using instance
+     * variables causes threading issues and state is too complex to pass and
+     * return single ServletRequest or ServletResponse objects.
+     */
+    private static class State {
+        State(ServletRequest request, ServletResponse response,
+                boolean including) {
+            this.outerRequest = request;
+            this.outerResponse = response;
+            this.including = including;
+        }
+
+        /**
+         * The outermost request that will be passed on to the invoked servlet.
+         */
+        ServletRequest outerRequest = null;
+
+
+        /**
+         * The outermost response that will be passed on to the invoked servlet.
+         */
+        ServletResponse outerResponse = null;
+        
+        /**
+         * The request wrapper we have created and installed (if any).
+         */
+        ServletRequest wrapRequest = null;
+
+
+        /**
+         * The response wrapper we have created and installed (if any).
+         */
+        ServletResponse wrapResponse = null;
+        
+        /**
+         * Are we performing an include() instead of a forward()?
+         */
+        boolean including = false;
+
+        /**
+         * Outermost HttpServletRequest in the chain
+         */
+        HttpServletRequest hrequest = null;
+
+        /**
+         * Outermost HttpServletResponse in the chain
+         */
+        HttpServletResponse hresponse = null;
+    }
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new instance of this class, configured according to the
+     * specified parameters.  If both servletPath and pathInfo are
+     * <code>null</code>, it will be assumed that this RequestDispatcher
+     * was acquired by name, rather than by path.
+     *
+     * @param wrapper The Wrapper associated with the resource that will
+     *  be forwarded to or included (required)
+     * @param requestURI The request URI to this resource (if any)
+     * @param servletPath The revised servlet path to this resource (if any)
+     * @param pathInfo The revised extra path information to this resource
+     *  (if any)
+     * @param queryString Query string parameters included with this request
+     *  (if any)
+     * @param name Servlet name (if a named dispatcher was created)
+     *  else <code>null</code>
+     */
+    public ApplicationDispatcher
+        (Wrapper wrapper, String requestURI, String servletPath,
+         String pathInfo, String queryString, String name) {
+
+        super();
+
+        // Save all of our configuration parameters
+        this.wrapper = wrapper;
+        this.context = (Context) wrapper.getParent();
+        this.requestURI = requestURI;
+        this.servletPath = servletPath;
+        this.pathInfo = pathInfo;
+        this.queryString = queryString;
+        this.name = name;
+        if (wrapper instanceof StandardWrapper)
+            this.support = ((StandardWrapper) wrapper).getInstanceSupport();
+        else
+            this.support = new InstanceSupport(wrapper);
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * The Context this RequestDispatcher is associated with.
+     */
+    private Context context = null;
+
+
+    /**
+     * Descriptive information about this implementation.
+     */
+    private static final String info =
+        "org.apache.catalina.core.ApplicationDispatcher/1.0";
+
+
+    /**
+     * The servlet name for a named dispatcher.
+     */
+    private String name = null;
+
+
+    /**
+     * The extra path information for this RequestDispatcher.
+     */
+    private String pathInfo = null;
+
+
+    /**
+     * The query string parameters for this RequestDispatcher.
+     */
+    private String queryString = null;
+
+
+    /**
+     * The request URI for this RequestDispatcher.
+     */
+    private String requestURI = null;
+
+
+    /**
+     * The servlet path for this RequestDispatcher.
+     */
+    private String servletPath = null;
+
+
+    /**
+     * The StringManager for this package.
+     */
+    private static final StringManager sm =
+      StringManager.getManager(Constants.Package);
+
+
+    /**
+     * The InstanceSupport instance associated with our Wrapper (used to
+     * send "before dispatch" and "after dispatch" events.
+     */
+    private InstanceSupport support = null;
+
+
+    /**
+     * The Wrapper associated with the resource that will be forwarded to
+     * or included.
+     */
+    private Wrapper wrapper = null;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the descriptive information about this implementation.
+     */
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Forward this request and response to another resource for processing.
+     * Any runtime exception, IOException, or ServletException thrown by the
+     * called servlet will be propagated to the caller.
+     *
+     * @param request The servlet request to be forwarded
+     * @param response The servlet response to be forwarded
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet exception occurs
+     */
+    @Override
+    public void forward(ServletRequest request, ServletResponse response)
+        throws ServletException, IOException
+    {
+        if (Globals.IS_SECURITY_ENABLED) {
+            try {
+                PrivilegedForward dp = new PrivilegedForward(request,response);
+                AccessController.doPrivileged(dp);
+            } catch (PrivilegedActionException pe) {
+                Exception e = pe.getException();
+                if (e instanceof ServletException)
+                    throw (ServletException) e;
+                throw (IOException) e;
+            }
+        } else {
+            doForward(request,response);
+        }
+    }
+
+    private void doForward(ServletRequest request, ServletResponse response)
+        throws ServletException, IOException
+    {
+        
+        // Reset any output that has been buffered, but keep headers/cookies
+        if (response.isCommitted()) {
+            throw new IllegalStateException
+                (sm.getString("applicationDispatcher.forward.ise"));
+        }
+        try {
+            response.resetBuffer();
+        } catch (IllegalStateException e) {
+            throw e;
+        }
+
+        // Set up to handle the specified request and response
+        State state = new State(request, response, false);
+
+        if (WRAP_SAME_OBJECT) {
+            // Check SRV.8.2 / SRV.14.2.5.1 compliance
+            checkSameObjects(request, response);
+        }
+
+        wrapResponse(state);
+        // Handle an HTTP named dispatcher forward
+        if ((servletPath == null) && (pathInfo == null)) {
+
+            ApplicationHttpRequest wrequest =
+                (ApplicationHttpRequest) wrapRequest(state);
+            HttpServletRequest hrequest = state.hrequest;
+            wrequest.setRequestURI(hrequest.getRequestURI());
+            wrequest.setContextPath(hrequest.getContextPath());
+            wrequest.setServletPath(hrequest.getServletPath());
+            wrequest.setPathInfo(hrequest.getPathInfo());
+            wrequest.setQueryString(hrequest.getQueryString());
+
+            processRequest(request,response,state);
+        }
+
+        // Handle an HTTP path-based forward
+        else {
+
+            ApplicationHttpRequest wrequest =
+                (ApplicationHttpRequest) wrapRequest(state);
+            String contextPath = context.getPath();
+            HttpServletRequest hrequest = state.hrequest;
+            if (hrequest.getAttribute(
+                    RequestDispatcher.FORWARD_REQUEST_URI) == null) {
+                wrequest.setAttribute(RequestDispatcher.FORWARD_REQUEST_URI,
+                                      hrequest.getRequestURI());
+                wrequest.setAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH,
+                                      hrequest.getContextPath());
+                wrequest.setAttribute(RequestDispatcher.FORWARD_SERVLET_PATH,
+                                      hrequest.getServletPath());
+                wrequest.setAttribute(RequestDispatcher.FORWARD_PATH_INFO,
+                                      hrequest.getPathInfo());
+                wrequest.setAttribute(RequestDispatcher.FORWARD_QUERY_STRING,
+                                      hrequest.getQueryString());
+            }
+ 
+            wrequest.setContextPath(contextPath);
+            wrequest.setRequestURI(requestURI);
+            wrequest.setServletPath(servletPath);
+            wrequest.setPathInfo(pathInfo);
+            if (queryString != null) {
+                wrequest.setQueryString(queryString);
+                wrequest.setQueryParams(queryString);
+            }
+
+            processRequest(request,response,state);
+        }
+
+        // This is not a real close in order to support error processing
+        if (wrapper.getLogger().isDebugEnabled() )
+            wrapper.getLogger().debug(" Disabling the response for futher output");
+
+        if  (response instanceof ResponseFacade) {
+            ((ResponseFacade) response).finish();
+        } else {
+            // Servlet SRV.6.2.2. The Request/Response may have been wrapped
+            // and may no longer be instance of RequestFacade 
+            if (wrapper.getLogger().isDebugEnabled()){
+                wrapper.getLogger().debug( " The Response is vehiculed using a wrapper: " 
+                           + response.getClass().getName() );
+            }
+
+            // Close anyway
+            try {
+                PrintWriter writer = response.getWriter();
+                writer.close();
+            } catch (IllegalStateException e) {
+                try {
+                    ServletOutputStream stream = response.getOutputStream();
+                    stream.close();
+                } catch (IllegalStateException f) {
+                    // Ignore
+                } catch (IOException f) {
+                    // Ignore
+                }
+            } catch (IOException e) {
+                // Ignore
+            }
+        }
+
+    }
+
+    
+    /**
+     * Prepare the request based on the filter configuration.
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     * @param state The RD state
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    private void processRequest(ServletRequest request, 
+                                ServletResponse response,
+                                State state)
+        throws IOException, ServletException {
+                
+        DispatcherType disInt = (DispatcherType) request.getAttribute(ApplicationFilterFactory.DISPATCHER_TYPE_ATTR);
+        if (disInt != null) {
+            boolean doInvoke = true;
+            
+            if (context.getFireRequestListenersOnForwards() &&
+                    !context.fireRequestInitEvent(request)) {
+                doInvoke = false;
+            }
+
+            if (doInvoke) {
+                if (disInt != DispatcherType.ERROR) {
+                    state.outerRequest.setAttribute
+                        (ApplicationFilterFactory.DISPATCHER_REQUEST_PATH_ATTR,
+                         getCombinedPath());
+                    state.outerRequest.setAttribute
+                        (ApplicationFilterFactory.DISPATCHER_TYPE_ATTR,
+                         DispatcherType.FORWARD);
+                    invoke(state.outerRequest, response, state);
+                } else {
+                    invoke(state.outerRequest, response, state);
+                }
+                
+                if (context.getFireRequestListenersOnForwards()) {
+                    context.fireRequestDestroyEvent(request);
+                }
+            }
+        }
+    }
+    
+    
+    /**
+     * Combine the servletPath and the pathInfo. If pathInfo is
+     * <code>null</code> it is ignored. If servletPath is <code>null</code> then
+     * <code>null</code> is returned.
+     * @return The combined path with pathInfo appended to servletInfo
+     */
+    private String getCombinedPath() {
+        if (servletPath == null) {
+            return null;
+        }
+        if (pathInfo == null) {
+            return servletPath;
+        }
+        return servletPath + pathInfo;
+    }
+
+
+    /**
+     * Include the response from another resource in the current response.
+     * Any runtime exception, IOException, or ServletException thrown by the
+     * called servlet will be propagated to the caller.
+     *
+     * @param request The servlet request that is including this one
+     * @param response The servlet response to be appended to
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet exception occurs
+     */
+    @Override
+    public void include(ServletRequest request, ServletResponse response)
+        throws ServletException, IOException
+    {
+        if (Globals.IS_SECURITY_ENABLED) {
+            try {
+                PrivilegedInclude dp = new PrivilegedInclude(request,response);
+                AccessController.doPrivileged(dp);
+            } catch (PrivilegedActionException pe) {
+                Exception e = pe.getException();
+
+                if (e instanceof ServletException)
+                    throw (ServletException) e;
+                throw (IOException) e;
+            }
+        } else {
+            DispatcherType type = DispatcherType.INCLUDE;
+            if (request.getDispatcherType()==DispatcherType.ASYNC) type = DispatcherType.ASYNC; 
+            doInclude(request,response,type);
+        }
+    }
+
+    private void doInclude(ServletRequest request, ServletResponse response, DispatcherType type)
+        throws ServletException, IOException
+    {
+        // Set up to handle the specified request and response
+        State state = new State(request, response, true);
+
+        if (WRAP_SAME_OBJECT) {
+            // Check SRV.8.2 / SRV.14.2.5.1 compliance
+            checkSameObjects(request, response);
+        }
+        
+        // Create a wrapped response to use for this request
+        wrapResponse(state);
+
+        // Handle an HTTP named dispatcher include
+        if (name != null) {
+
+            ApplicationHttpRequest wrequest =
+                (ApplicationHttpRequest) wrapRequest(state);
+            wrequest.setAttribute(Globals.NAMED_DISPATCHER_ATTR, name);
+            if (servletPath != null)
+                wrequest.setServletPath(servletPath);
+            wrequest.setAttribute(ApplicationFilterFactory.DISPATCHER_TYPE_ATTR,
+                    type);
+            wrequest.setAttribute(
+                    ApplicationFilterFactory.DISPATCHER_REQUEST_PATH_ATTR,
+                    getCombinedPath());
+            invoke(state.outerRequest, state.outerResponse, state);
+        }
+
+        // Handle an HTTP path based include
+        else {
+
+            ApplicationHttpRequest wrequest =
+                (ApplicationHttpRequest) wrapRequest(state);
+            String contextPath = context.getPath();
+            if (requestURI != null)
+                wrequest.setAttribute(RequestDispatcher.INCLUDE_REQUEST_URI,
+                                      requestURI);
+            if (contextPath != null)
+                wrequest.setAttribute(RequestDispatcher.INCLUDE_CONTEXT_PATH,
+                                      contextPath);
+            if (servletPath != null)
+                wrequest.setAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH,
+                                      servletPath);
+            if (pathInfo != null)
+                wrequest.setAttribute(RequestDispatcher.INCLUDE_PATH_INFO,
+                                      pathInfo);
+            if (queryString != null) {
+                wrequest.setAttribute(RequestDispatcher.INCLUDE_QUERY_STRING,
+                                      queryString);
+                wrequest.setQueryParams(queryString);
+            }
+            
+            wrequest.setAttribute(ApplicationFilterFactory.DISPATCHER_TYPE_ATTR,
+                    type);
+            wrequest.setAttribute(
+                    ApplicationFilterFactory.DISPATCHER_REQUEST_PATH_ATTR,
+                    getCombinedPath());
+            invoke(state.outerRequest, state.outerResponse, state);
+        }
+
+    }
+
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Ask the resource represented by this RequestDispatcher to process
+     * the associated request, and create (or append to) the associated
+     * response.
+     * <p>
+     * <strong>IMPLEMENTATION NOTE</strong>: This implementation assumes
+     * that no filters are applied to a forwarded or included resource,
+     * because they were already done for the original request.
+     *
+     * @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 error occurs
+     */
+    private void invoke(ServletRequest request, ServletResponse response,
+            State state) throws IOException, ServletException {
+
+        // Checking to see if the context classloader is the current context
+        // classloader. If it's not, we're saving it, and setting the context
+        // classloader to the Context classloader
+        ClassLoader oldCCL = Thread.currentThread().getContextClassLoader();
+        ClassLoader contextClassLoader = context.getLoader().getClassLoader();
+
+        if (oldCCL != contextClassLoader) {
+            Thread.currentThread().setContextClassLoader(contextClassLoader);
+        } else {
+            oldCCL = null;
+        }
+
+        // Initialize local variables we may need
+        HttpServletResponse hresponse = state.hresponse;
+        Servlet servlet = null;
+        IOException ioException = null;
+        ServletException servletException = null;
+        RuntimeException runtimeException = null;
+        boolean unavailable = false;
+
+        // Check for the servlet being marked unavailable
+        if (wrapper.isUnavailable()) {
+            wrapper.getLogger().warn(
+                    sm.getString("applicationDispatcher.isUnavailable", 
+                    wrapper.getName()));
+            long available = wrapper.getAvailable();
+            if ((available > 0L) && (available < Long.MAX_VALUE))
+                hresponse.setDateHeader("Retry-After", available);
+            hresponse.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, sm
+                    .getString("applicationDispatcher.isUnavailable", wrapper
+                            .getName()));
+            unavailable = true;
+        }
+
+        // Allocate a servlet instance to process this request
+        try {
+            if (!unavailable) {
+                servlet = wrapper.allocate();
+            }
+        } catch (ServletException e) {
+            wrapper.getLogger().error(sm.getString("applicationDispatcher.allocateException",
+                             wrapper.getName()), StandardWrapper.getRootCause(e));
+            servletException = e;
+        } catch (Throwable e) {
+            ExceptionUtils.handleThrowable(e);
+            wrapper.getLogger().error(sm.getString("applicationDispatcher.allocateException",
+                             wrapper.getName()), e);
+            servletException = new ServletException
+                (sm.getString("applicationDispatcher.allocateException",
+                              wrapper.getName()), e);
+            servlet = null;
+        }
+                
+        // Get the FilterChain Here
+        ApplicationFilterFactory factory = ApplicationFilterFactory.getInstance();
+        ApplicationFilterChain filterChain = factory.createFilterChain(request,
+                                                                wrapper,servlet);
+        
+        // Call the service() method for the allocated servlet instance
+        try {
+            support.fireInstanceEvent(InstanceEvent.BEFORE_DISPATCH_EVENT,
+                                      servlet, request, response);
+            // for includes/forwards
+            if ((servlet != null) && (filterChain != null)) {
+               filterChain.doFilter(request, response);
+             }
+            // Servlet Service Method is called by the FilterChain
+            support.fireInstanceEvent(InstanceEvent.AFTER_DISPATCH_EVENT,
+                                      servlet, request, response);
+        } catch (ClientAbortException e) {
+            support.fireInstanceEvent(InstanceEvent.AFTER_DISPATCH_EVENT,
+                                      servlet, request, response);
+            ioException = e;
+        } catch (IOException e) {
+            support.fireInstanceEvent(InstanceEvent.AFTER_DISPATCH_EVENT,
+                                      servlet, request, response);
+            wrapper.getLogger().error(sm.getString("applicationDispatcher.serviceException",
+                             wrapper.getName()), e);
+            ioException = e;
+        } catch (UnavailableException e) {
+            support.fireInstanceEvent(InstanceEvent.AFTER_DISPATCH_EVENT,
+                                      servlet, request, response);
+            wrapper.getLogger().error(sm.getString("applicationDispatcher.serviceException",
+                             wrapper.getName()), e);
+            servletException = e;
+            wrapper.unavailable(e);
+        } catch (ServletException e) {
+            support.fireInstanceEvent(InstanceEvent.AFTER_DISPATCH_EVENT,
+                                      servlet, request, response);
+            Throwable rootCause = StandardWrapper.getRootCause(e);
+            if (!(rootCause instanceof ClientAbortException)) {
+                wrapper.getLogger().error(sm.getString("applicationDispatcher.serviceException",
+                        wrapper.getName()), rootCause);
+            }
+            servletException = e;
+        } catch (RuntimeException e) {
+            support.fireInstanceEvent(InstanceEvent.AFTER_DISPATCH_EVENT,
+                                      servlet, request, response);
+            wrapper.getLogger().error(sm.getString("applicationDispatcher.serviceException",
+                             wrapper.getName()), e);
+            runtimeException = e;
+        }
+
+        // Release the filter chain (if any) for this request
+        try {
+            if (filterChain != null)
+                filterChain.release();
+        } catch (Throwable e) {
+            ExceptionUtils.handleThrowable(e);
+            wrapper.getLogger().error(sm.getString("standardWrapper.releaseFilters",
+                             wrapper.getName()), e);
+            // FIXME: Exception handling needs to be similar to what is in the StandardWrapperValue
+        }
+
+        // Deallocate the allocated servlet instance
+        try {
+            if (servlet != null) {
+                wrapper.deallocate(servlet);
+            }
+        } catch (ServletException e) {
+            wrapper.getLogger().error(sm.getString("applicationDispatcher.deallocateException",
+                             wrapper.getName()), e);
+            servletException = e;
+        } catch (Throwable e) {
+            ExceptionUtils.handleThrowable(e);
+            wrapper.getLogger().error(sm.getString("applicationDispatcher.deallocateException",
+                             wrapper.getName()), e);
+            servletException = new ServletException
+                (sm.getString("applicationDispatcher.deallocateException",
+                              wrapper.getName()), e);
+        }
+
+        // Reset the old context class loader
+        if (oldCCL != null)
+            Thread.currentThread().setContextClassLoader(oldCCL);
+        
+        // Unwrap request/response if needed
+        // See Bugzilla 30949
+        unwrapRequest(state);
+        unwrapResponse(state);
+        // Recycle request if necessary (also BZ 30949)
+        recycleRequestWrapper(state);
+        
+        // Rethrow an exception if one was thrown by the invoked servlet
+        if (ioException != null)
+            throw ioException;
+        if (servletException != null)
+            throw servletException;
+        if (runtimeException != null)
+            throw runtimeException;
+
+    }
+
+
+    /**
+     * Unwrap the request if we have wrapped it.
+     */
+    private void unwrapRequest(State state) {
+
+        if (state.wrapRequest == null)
+            return;
+
+        ServletRequest previous = null;
+        ServletRequest current = state.outerRequest;
+        while (current != null) {
+
+            // If we run into the container request we are done
+            if ((current instanceof Request)
+                || (current instanceof RequestFacade))
+                break;
+
+            // Remove the current request if it is our wrapper
+            if (current == state.wrapRequest) {
+                ServletRequest next =
+                  ((ServletRequestWrapper) current).getRequest();
+                if (previous == null)
+                    state.outerRequest = next;
+                else
+                    ((ServletRequestWrapper) previous).setRequest(next);
+                break;
+            }
+
+            // Advance to the next request in the chain
+            previous = current;
+            current = ((ServletRequestWrapper) current).getRequest();
+
+        }
+
+    }
+    
+    /**
+     * Unwrap the response if we have wrapped it.
+     */
+    private void unwrapResponse(State state) {
+
+        if (state.wrapResponse == null)
+            return;
+
+        ServletResponse previous = null;
+        ServletResponse current = state.outerResponse;
+        while (current != null) {
+
+            // If we run into the container response we are done
+            if ((current instanceof Response)
+                || (current instanceof ResponseFacade))
+                break;
+
+            // Remove the current response if it is our wrapper
+            if (current == state.wrapResponse) {
+                ServletResponse next =
+                  ((ServletResponseWrapper) current).getResponse();
+                if (previous == null)
+                    state.outerResponse = next;
+                else
+                    ((ServletResponseWrapper) previous).setResponse(next);
+                break;
+            }
+
+            // Advance to the next response in the chain
+            previous = current;
+            current = ((ServletResponseWrapper) current).getResponse();
+
+        }
+
+    }
+
+
+    /**
+     * Create and return a request wrapper that has been inserted in the
+     * appropriate spot in the request chain.
+     */
+    private ServletRequest wrapRequest(State state) {
+
+        // Locate the request we should insert in front of
+        ServletRequest previous = null;
+        ServletRequest current = state.outerRequest;
+        while (current != null) {
+            if(state.hrequest == null && (current instanceof HttpServletRequest))
+                state.hrequest = (HttpServletRequest)current;
+            if (!(current instanceof ServletRequestWrapper))
+                break;
+            if (current instanceof ApplicationHttpRequest)
+                break;
+            if (current instanceof ApplicationRequest)
+                break;
+            previous = current;
+            current = ((ServletRequestWrapper) current).getRequest();
+        }
+
+        // Instantiate a new wrapper at this point and insert it in the chain
+        ServletRequest wrapper = null;
+        if ((current instanceof ApplicationHttpRequest) ||
+            (current instanceof Request) ||
+            (current instanceof HttpServletRequest)) {
+            // Compute a crossContext flag
+            HttpServletRequest hcurrent = (HttpServletRequest) current;
+            boolean crossContext = false;
+            if ((state.outerRequest instanceof ApplicationHttpRequest) ||
+                (state.outerRequest instanceof Request) ||
+                (state.outerRequest instanceof HttpServletRequest)) {
+                HttpServletRequest houterRequest = 
+                    (HttpServletRequest) state.outerRequest;
+                Object contextPath = houterRequest.getAttribute(
+                        RequestDispatcher.INCLUDE_CONTEXT_PATH);
+                if (contextPath == null) {
+                    // Forward
+                    contextPath = houterRequest.getContextPath();
+                }
+                crossContext = !(context.getPath().equals(contextPath));
+            }
+            wrapper = new ApplicationHttpRequest
+                (hcurrent, context, crossContext);
+        } else {
+            wrapper = new ApplicationRequest(current);
+        }
+        if (previous == null)
+            state.outerRequest = wrapper;
+        else
+            ((ServletRequestWrapper) previous).setRequest(wrapper);
+        state.wrapRequest = wrapper;
+        return (wrapper);
+
+    }
+
+
+    /**
+     * Create and return a response wrapper that has been inserted in the
+     * appropriate spot in the response chain.
+     */
+    private ServletResponse wrapResponse(State state) {
+
+        // Locate the response we should insert in front of
+        ServletResponse previous = null;
+        ServletResponse current = state.outerResponse;
+        while (current != null) {
+            if(state.hresponse == null && (current instanceof HttpServletResponse)) {
+                state.hresponse = (HttpServletResponse)current;
+                if(!state.including) // Forward only needs hresponse
+                    return null;
+            }
+            if (!(current instanceof ServletResponseWrapper))
+                break;
+            if (current instanceof ApplicationHttpResponse)
+                break;
+            if (current instanceof ApplicationResponse)
+                break;
+            previous = current;
+            current = ((ServletResponseWrapper) current).getResponse();
+        }
+
+        // Instantiate a new wrapper at this point and insert it in the chain
+        ServletResponse wrapper = null;
+        if ((current instanceof ApplicationHttpResponse) ||
+            (current instanceof Response) ||
+            (current instanceof HttpServletResponse))
+            wrapper =
+                new ApplicationHttpResponse((HttpServletResponse) current,
+                        state.including);
+        else
+            wrapper = new ApplicationResponse(current, state.including);
+        if (previous == null)
+            state.outerResponse = wrapper;
+        else
+            ((ServletResponseWrapper) previous).setResponse(wrapper);
+        state.wrapResponse = wrapper;
+        return (wrapper);
+
+    }
+
+    private void checkSameObjects(ServletRequest appRequest,
+            ServletResponse appResponse) throws ServletException {
+        ServletRequest originalRequest =
+            ApplicationFilterChain.getLastServicedRequest();
+        ServletResponse originalResponse =
+            ApplicationFilterChain.getLastServicedResponse();
+        
+        // Some forwards, eg from valves will not set original values 
+        if (originalRequest == null || originalResponse == null) {
+            return;
+        }
+        
+        boolean same = false;
+        ServletRequest dispatchedRequest = appRequest;
+        
+        //find the request that was passed into the service method
+        while (originalRequest instanceof ServletRequestWrapper &&
+                ((ServletRequestWrapper) originalRequest).getRequest()!=null ) {
+            originalRequest =
+                ((ServletRequestWrapper) originalRequest).getRequest();
+        }
+        //compare with the dispatched request
+        while (!same) {
+            if (originalRequest.equals(dispatchedRequest)) {
+                same = true;
+            }
+            if (!same && dispatchedRequest instanceof ServletRequestWrapper) {
+                dispatchedRequest =
+                    ((ServletRequestWrapper) dispatchedRequest).getRequest();
+            } else {
+                break;
+            }
+        }
+        if (!same) {
+            throw new ServletException(sm.getString(
+                    "applicationDispatcher.specViolation.request"));
+        }
+        
+        same = false;
+        ServletResponse dispatchedResponse = appResponse;
+        
+        //find the response that was passed into the service method
+        while (originalResponse instanceof ServletResponseWrapper &&
+                ((ServletResponseWrapper) originalResponse).getResponse() != 
+                    null ) {
+            originalResponse =
+                ((ServletResponseWrapper) originalResponse).getResponse();
+        }
+        //compare with the dispatched response
+        while (!same) {
+            if (originalResponse.equals(dispatchedResponse)) {
+                same = true;
+            }
+            
+            if (!same && dispatchedResponse instanceof ServletResponseWrapper) {
+                dispatchedResponse =
+                    ((ServletResponseWrapper) dispatchedResponse).getResponse();
+            } else {
+                break;
+            }
+        }
+
+        if (!same) {
+            throw new ServletException(sm.getString(
+                    "applicationDispatcher.specViolation.response"));
+        }
+    }
+
+    private void recycleRequestWrapper(State state) {
+        if (state.wrapRequest instanceof ApplicationHttpRequest) {
+            ((ApplicationHttpRequest) state.wrapRequest).recycle();        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationFilterChain.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationFilterChain.java
new file mode 100644
index 0000000..94e99c5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationFilterChain.java
@@ -0,0 +1,591 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+
+import java.io.IOException;
+import java.security.Principal;
+import java.security.PrivilegedActionException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.Servlet;
+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;
+import org.apache.catalina.InstanceEvent;
+import org.apache.catalina.comet.CometEvent;
+import org.apache.catalina.comet.CometFilter;
+import org.apache.catalina.comet.CometFilterChain;
+import org.apache.catalina.comet.CometProcessor;
+import org.apache.catalina.security.SecurityUtil;
+import org.apache.catalina.util.InstanceSupport;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Implementation of <code>javax.servlet.FilterChain</code> used to manage
+ * the execution of a set of filters for a particular request.  When the
+ * set of defined filters has all been executed, the next call to
+ * <code>doFilter()</code> will execute the servlet's <code>service()</code>
+ * method itself.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: ApplicationFilterChain.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+final class ApplicationFilterChain implements FilterChain, CometFilterChain {
+
+    // Used to enforce requirements of SRV.8.2 / SRV.14.2.5.1
+    private static final ThreadLocal<ServletRequest> lastServicedRequest;
+    private static final ThreadLocal<ServletResponse> lastServicedResponse;
+
+    static {
+        if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
+            lastServicedRequest = new ThreadLocal<ServletRequest>();
+            lastServicedResponse = new ThreadLocal<ServletResponse>();
+        } else {
+            lastServicedRequest = null;
+            lastServicedResponse = null;
+        }
+    }
+
+    // -------------------------------------------------------------- Constants
+
+
+    public static final int INCREMENT = 10;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new chain instance with no defined filters.
+     */
+    public ApplicationFilterChain() {
+
+        super();
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Filters.
+     */
+    private ApplicationFilterConfig[] filters = 
+        new ApplicationFilterConfig[0];
+
+
+    /**
+     * The int which is used to maintain the current position 
+     * in the filter chain.
+     */
+    private int pos = 0;
+
+
+    /**
+     * The int which gives the current number of filters in the chain.
+     */
+    private int n = 0;
+
+
+    /**
+     * The servlet instance to be executed by this chain.
+     */
+    private Servlet servlet = null;
+
+
+    /**
+     * The string manager for our package.
+     */
+    private static final StringManager sm =
+      StringManager.getManager(Constants.Package);
+
+
+    /**
+     * The InstanceSupport instance associated with our Wrapper (used to
+     * send "before filter" and "after filter" events.
+     */
+    private InstanceSupport support = null;
+
+    
+    /**
+     * Static class array used when the SecurityManager is turned on and 
+     * <code>doFilter</code> is invoked.
+     */
+    private static Class<?>[] classType = new Class[]{ServletRequest.class, 
+                                                      ServletResponse.class,
+                                                      FilterChain.class};
+                                                   
+    /**
+     * Static class array used when the SecurityManager is turned on and 
+     * <code>service</code> is invoked.
+     */                                                 
+    private static Class<?>[] classTypeUsedInService = new Class[]{
+                                                         ServletRequest.class,
+                                                         ServletResponse.class};
+
+    /**
+     * Static class array used when the SecurityManager is turned on and 
+     * <code>doFilterEvent</code> is invoked.
+     */
+    private static Class<?>[] cometClassType = 
+        new Class[]{ CometEvent.class, CometFilterChain.class};
+                                                   
+    /**
+     * Static class array used when the SecurityManager is turned on and 
+     * <code>event</code> is invoked.
+     */                                                 
+    private static Class<?>[] classTypeUsedInEvent = 
+        new Class[] { CometEvent.class };
+
+
+    // ---------------------------------------------------- FilterChain Methods
+
+
+    /**
+     * Invoke the next filter in this chain, passing the specified request
+     * and response.  If there are no more filters in this chain, invoke
+     * the <code>service()</code> method of the servlet itself.
+     *
+     * @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 exception occurs
+     */
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response)
+        throws IOException, ServletException {
+
+        if( Globals.IS_SECURITY_ENABLED ) {
+            final ServletRequest req = request;
+            final ServletResponse res = response;
+            try {
+                java.security.AccessController.doPrivileged(
+                    new java.security.PrivilegedExceptionAction<Void>() {
+                        @Override
+                        public Void run() 
+                            throws ServletException, IOException {
+                            internalDoFilter(req,res);
+                            return null;
+                        }
+                    }
+                );
+            } catch( PrivilegedActionException pe) {
+                Exception e = pe.getException();
+                if (e instanceof ServletException)
+                    throw (ServletException) e;
+                else if (e instanceof IOException)
+                    throw (IOException) e;
+                else if (e instanceof RuntimeException)
+                    throw (RuntimeException) e;
+                else
+                    throw new ServletException(e.getMessage(), e);
+            }
+        } else {
+            internalDoFilter(request,response);
+        }
+    }
+
+    private void internalDoFilter(ServletRequest request, 
+                                  ServletResponse response)
+        throws IOException, ServletException {
+
+        // Call the next filter if there is one
+        if (pos < n) {
+            ApplicationFilterConfig filterConfig = filters[pos++];
+            Filter filter = null;
+            try {
+                filter = filterConfig.getFilter();
+                support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,
+                                          filter, request, response);
+                
+                if (request.isAsyncSupported() && "false".equalsIgnoreCase(
+                        filterConfig.getFilterDef().getAsyncSupported())) {
+                    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
+                            Boolean.FALSE);
+                }
+                if( Globals.IS_SECURITY_ENABLED ) {
+                    final ServletRequest req = request;
+                    final ServletResponse res = response;
+                    Principal principal = 
+                        ((HttpServletRequest) req).getUserPrincipal();
+
+                    Object[] args = new Object[]{req, res, this};
+                    SecurityUtil.doAsPrivilege
+                        ("doFilter", filter, classType, args, principal);
+                    
+                } else {  
+                    filter.doFilter(request, response, this);
+                }
+
+                support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
+                                          filter, request, response);
+            } catch (IOException e) {
+                if (filter != null)
+                    support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
+                                              filter, request, response, e);
+                throw e;
+            } catch (ServletException e) {
+                if (filter != null)
+                    support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
+                                              filter, request, response, e);
+                throw e;
+            } catch (RuntimeException e) {
+                if (filter != null)
+                    support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
+                                              filter, request, response, e);
+                throw e;
+            } catch (Throwable e) {
+                ExceptionUtils.handleThrowable(e);
+                if (filter != null)
+                    support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
+                                              filter, request, response, e);
+                throw new ServletException
+                  (sm.getString("filterChain.filter"), e);
+            }
+            return;
+        }
+
+        // We fell off the end of the chain -- call the servlet instance
+        try {
+            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
+                lastServicedRequest.set(request);
+                lastServicedResponse.set(response);
+            }
+
+            support.fireInstanceEvent(InstanceEvent.BEFORE_SERVICE_EVENT,
+                                      servlet, request, response);
+            if (request.isAsyncSupported()
+                    && !support.getWrapper().isAsyncSupported()) {
+                request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
+                        Boolean.FALSE);
+            }
+            // Use potentially wrapped request from this point
+            if ((request instanceof HttpServletRequest) &&
+                (response instanceof HttpServletResponse)) {
+                    
+                if( Globals.IS_SECURITY_ENABLED ) {
+                    final ServletRequest req = request;
+                    final ServletResponse res = response;
+                    Principal principal = 
+                        ((HttpServletRequest) req).getUserPrincipal();
+                    Object[] args = new Object[]{req, res};
+                    SecurityUtil.doAsPrivilege("service",
+                                               servlet,
+                                               classTypeUsedInService, 
+                                               args,
+                                               principal);   
+                } else {  
+                    servlet.service(request, response);
+                }
+            } else {
+                servlet.service(request, response);
+            }
+            support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
+                                      servlet, request, response);
+        } catch (IOException e) {
+            support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
+                                      servlet, request, response, e);
+            throw e;
+        } catch (ServletException e) {
+            support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
+                                      servlet, request, response, e);
+            throw e;
+        } catch (RuntimeException e) {
+            support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
+                                      servlet, request, response, e);
+            throw e;
+        } catch (Throwable e) {
+            ExceptionUtils.handleThrowable(e);
+            support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
+                                      servlet, request, response, e);
+            throw new ServletException
+              (sm.getString("filterChain.servlet"), e);
+        } finally {
+            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
+                lastServicedRequest.set(null);
+                lastServicedResponse.set(null);
+            }
+        }
+
+    }
+
+
+    /**
+     * Process the event, using the security manager if the option is enabled.
+     * 
+     * @param event the event to process
+     * 
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet exception occurs
+     */
+    @Override
+    public void doFilterEvent(CometEvent event)
+        throws IOException, ServletException {
+
+        if( Globals.IS_SECURITY_ENABLED ) {
+            final CometEvent ev = event;
+            try {
+                java.security.AccessController.doPrivileged(
+                    new java.security.PrivilegedExceptionAction<Void>() {
+                        @Override
+                        public Void run() 
+                            throws ServletException, IOException {
+                            internalDoFilterEvent(ev);
+                            return null;
+                        }
+                    }
+                );
+            } catch( PrivilegedActionException pe) {
+                Exception e = pe.getException();
+                if (e instanceof ServletException)
+                    throw (ServletException) e;
+                else if (e instanceof IOException)
+                    throw (IOException) e;
+                else if (e instanceof RuntimeException)
+                    throw (RuntimeException) e;
+                else
+                    throw new ServletException(e.getMessage(), e);
+            }
+        } else {
+            internalDoFilterEvent(event);
+        }
+    }
+
+    
+    /**
+     * The last request passed to a servlet for servicing from the current
+     * thread.
+     * 
+     * @return The last request to be serviced. 
+     */
+    public static ServletRequest getLastServicedRequest() {
+        return lastServicedRequest.get();
+    }
+
+    
+    /**
+     * The last response passed to a servlet for servicing from the current
+     * thread.
+     * 
+     * @return The last response to be serviced. 
+     */
+    public static ServletResponse getLastServicedResponse() {
+        return lastServicedResponse.get();
+    }
+    
+    
+    private void internalDoFilterEvent(CometEvent event)
+        throws IOException, ServletException {
+
+        // Call the next filter if there is one
+        if (pos < n) {
+            ApplicationFilterConfig filterConfig = filters[pos++];
+            CometFilter filter = null;
+            try {
+                filter = (CometFilter) filterConfig.getFilter();
+                // FIXME: No instance listener processing for events for now
+                /*
+                support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,
+                        filter, event);
+                        */
+
+                if( Globals.IS_SECURITY_ENABLED ) {
+                    final CometEvent ev = event;
+                    Principal principal = 
+                        ev.getHttpServletRequest().getUserPrincipal();
+
+                    Object[] args = new Object[]{ev, this};
+                    SecurityUtil.doAsPrivilege("doFilterEvent", filter,
+                            cometClassType, args, principal);
+
+                } else {  
+                    filter.doFilterEvent(event, this);
+                }
+
+                /*support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
+                        filter, event);*/
+            } catch (IOException e) {
+                /*
+                if (filter != null)
+                    support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
+                            filter, event, e);
+                            */
+                throw e;
+            } catch (ServletException e) {
+                /*
+                if (filter != null)
+                    support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
+                            filter, event, e);
+                            */
+                throw e;
+            } catch (RuntimeException e) {
+                /*
+                if (filter != null)
+                    support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
+                            filter, event, e);
+                            */
+                throw e;
+            } catch (Throwable e) {
+                ExceptionUtils.handleThrowable(e);
+                /*if (filter != null)
+                    support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
+                            filter, event, e);*/
+                throw new ServletException
+                    (sm.getString("filterChain.filter"), e);
+            }
+            return;
+        }
+
+        // We fell off the end of the chain -- call the servlet instance
+        try {
+            /*
+            support.fireInstanceEvent(InstanceEvent.BEFORE_SERVICE_EVENT,
+                    servlet, request, response);
+                    */
+            if( Globals.IS_SECURITY_ENABLED ) {
+                final CometEvent ev = event;
+                Principal principal = 
+                    ev.getHttpServletRequest().getUserPrincipal();
+                Object[] args = new Object[]{ ev };
+                SecurityUtil.doAsPrivilege("event",
+                        servlet,
+                        classTypeUsedInEvent, 
+                        args,
+                        principal);
+            } else {  
+                ((CometProcessor) servlet).event(event);
+            }
+            /*
+            support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
+                    servlet, request, response);*/
+        } catch (IOException e) {
+            /*
+            support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
+                    servlet, request, response, e);
+                    */
+            throw e;
+        } catch (ServletException e) {
+            /*
+            support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
+                    servlet, request, response, e);
+                    */
+            throw e;
+        } catch (RuntimeException e) {
+            /*
+            support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
+                    servlet, request, response, e);
+                    */
+            throw e;
+        } catch (Throwable e) {
+            ExceptionUtils.handleThrowable(e);
+            /*
+            support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
+                    servlet, request, response, e);
+                    */
+            throw new ServletException
+                (sm.getString("filterChain.servlet"), e);
+        }
+
+    }
+
+
+    // -------------------------------------------------------- Package Methods
+
+
+    /**
+     * Add a filter to the set of filters that will be executed in this chain.
+     *
+     * @param filterConfig The FilterConfig for the servlet to be executed
+     */
+    void addFilter(ApplicationFilterConfig filterConfig) {
+
+        // Prevent the same filter being added multiple times
+        for(ApplicationFilterConfig filter:filters)
+            if(filter==filterConfig)
+                return;
+
+        if (n == filters.length) {
+            ApplicationFilterConfig[] newFilters =
+                new ApplicationFilterConfig[n + INCREMENT];
+            System.arraycopy(filters, 0, newFilters, 0, n);
+            filters = newFilters;
+        }
+        filters[n++] = filterConfig;
+
+    }
+
+
+    /**
+     * Release references to the filters and wrapper executed by this chain.
+     */
+    void release() {
+
+        for (int i = 0; i < n; i++) {
+            filters[i] = null;
+        }
+        n = 0;
+        pos = 0;
+        servlet = null;
+        support = null;
+
+    }
+
+
+    /**
+     * Prepare for reuse of the filters and wrapper executed by this chain.
+     */
+    void reuse() {
+        pos = 0;
+    }
+
+
+    /**
+     * Set the servlet that will be executed at the end of this chain.
+     *
+     * @param servlet The Wrapper for the servlet to be executed
+     */
+    void setServlet(Servlet servlet) {
+
+        this.servlet = servlet;
+
+    }
+
+
+    /**
+     * Set the InstanceSupport object used for event notifications
+     * for this filter chain.
+     *
+     * @param support The InstanceSupport object for our Wrapper
+     */
+    void setSupport(InstanceSupport support) {
+
+        this.support = support;
+
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationFilterConfig.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationFilterConfig.java
new file mode 100644
index 0000000..e2c3392
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationFilterConfig.java
@@ -0,0 +1,447 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+
+import java.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.management.ObjectName;
+import javax.naming.NamingException;
+import javax.servlet.Filter;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Globals;
+import org.apache.catalina.deploy.FilterDef;
+import org.apache.catalina.security.SecurityUtil;
+import org.apache.catalina.util.Enumerator;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.InstanceManager;
+import org.apache.tomcat.util.log.SystemLogHandler;
+import org.apache.tomcat.util.modeler.Registry;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Implementation of a <code>javax.servlet.FilterConfig</code> useful in
+ * managing the filter instances instantiated when a web application
+ * is first started.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: ApplicationFilterConfig.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+public final class ApplicationFilterConfig implements FilterConfig, Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    private static final org.apache.juli.logging.Log log =
+        LogFactory.getLog(ApplicationFilterConfig.class);
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new ApplicationFilterConfig for the specified filter
+     * definition.
+     *
+     * @param context The context with which we are associated
+     * @param filterDef Filter definition for which a FilterConfig is to be
+     *  constructed
+     *
+     * @exception ClassCastException if the specified class does not implement
+     *  the <code>javax.servlet.Filter</code> interface
+     * @exception ClassNotFoundException if the filter class cannot be found
+     * @exception IllegalAccessException if the filter class cannot be
+     *  publicly instantiated
+     * @exception InstantiationException if an exception occurs while
+     *  instantiating the filter object
+     * @exception ServletException if thrown by the filter's init() method
+     * @throws NamingException
+     * @throws InvocationTargetException
+     */
+    ApplicationFilterConfig(Context context, FilterDef filterDef)
+        throws ClassCastException, ClassNotFoundException,
+               IllegalAccessException, InstantiationException,
+               ServletException, InvocationTargetException, NamingException {
+
+        super();
+
+        this.context = context;
+        setFilterDef(filterDef);
+        if (filterDef.getFilter() != null) {
+            this.filter = filterDef.getFilter();
+            getInstanceManager().newInstance(filter);
+            initFilter();
+        }
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The Context with which we are associated.
+     */
+    private transient Context context = null;
+
+
+    /**
+     * The application Filter we are configured for.
+     */
+    private transient Filter filter = null;
+
+
+    /**
+     * The <code>FilterDef</code> that defines our associated Filter.
+     */
+    private FilterDef filterDef = null;
+
+    /**
+     * the InstanceManager used to create and destroy filter instances.
+     */
+    private transient InstanceManager instanceManager;
+
+    /**
+     * JMX registration name
+     */
+    private ObjectName oname;
+
+    // --------------------------------------------------- FilterConfig Methods
+
+
+    /**
+     * Return the name of the filter we are configuring.
+     */
+    @Override
+    public String getFilterName() {
+        return (filterDef.getFilterName());
+    }
+
+    /**
+     * Return the class of the filter we are configuring.
+     */
+    public String getFilterClass() {
+        return filterDef.getFilterClass();
+    }
+
+    /**
+     * Return a <code>String</code> containing the value of the named
+     * initialization parameter, or <code>null</code> if the parameter
+     * does not exist.
+     *
+     * @param name Name of the requested initialization parameter
+     */
+    @Override
+    public String getInitParameter(String name) {
+
+        Map<String,String> map = filterDef.getParameterMap();
+        if (map == null) {
+            return (null);
+        }
+
+        return map.get(name);
+
+    }
+
+
+    /**
+     * Return an <code>Enumeration</code> of the names of the initialization
+     * parameters for this Filter.
+     */
+    @Override
+    public Enumeration<String> getInitParameterNames() {
+        Map<String,String> map = filterDef.getParameterMap();
+        
+        if (map == null) {
+            return (new Enumerator<String>(new ArrayList<String>()));
+        }
+
+        return new Enumerator<String>(map.keySet());
+
+    }
+
+
+    /**
+     * Return the ServletContext of our associated web application.
+     */
+    @Override
+    public ServletContext getServletContext() {
+
+        return this.context.getServletContext();
+
+    }
+
+
+    /**
+     * Return a String representation of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("ApplicationFilterConfig[");
+        sb.append("name=");
+        sb.append(filterDef.getFilterName());
+        sb.append(", filterClass=");
+        sb.append(filterDef.getFilterClass());
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+    public Map<String, String> getFilterInitParameterMap() {
+        return Collections.unmodifiableMap(filterDef.getParameterMap());
+    }
+
+    // -------------------------------------------------------- Package Methods
+
+
+    /**
+     * Return the application Filter we are configured for.
+     *
+     * @exception ClassCastException if the specified class does not implement
+     *  the <code>javax.servlet.Filter</code> interface
+     * @exception ClassNotFoundException if the filter class cannot be found
+     * @exception IllegalAccessException if the filter class cannot be
+     *  publicly instantiated
+     * @exception InstantiationException if an exception occurs while
+     *  instantiating the filter object
+     * @exception ServletException if thrown by the filter's init() method
+     * @throws NamingException
+     * @throws InvocationTargetException
+     */
+    Filter getFilter() throws ClassCastException, ClassNotFoundException,
+        IllegalAccessException, InstantiationException, ServletException,
+        InvocationTargetException, NamingException {
+
+        // Return the existing filter instance, if any
+        if (this.filter != null)
+            return (this.filter);
+
+        // Identify the class loader we will be using
+        String filterClass = filterDef.getFilterClass();
+        this.filter = (Filter) getInstanceManager().newInstance(filterClass);
+        
+        initFilter();
+        
+        return (this.filter);
+
+    }
+
+    private void initFilter() throws ServletException {
+        if (context instanceof StandardContext &&
+                context.getSwallowOutput()) {
+            try {
+                SystemLogHandler.startCapture();
+                filter.init(this);
+            } finally {
+                String capturedlog = SystemLogHandler.stopCapture();
+                if (capturedlog != null && capturedlog.length() > 0) {
+                    getServletContext().log(capturedlog);
+                }
+            }
+        } else {
+            filter.init(this);
+        }
+        
+        // Expose filter via JMX
+        registerJMX();
+    }
+
+    /**
+     * Return the filter definition we are configured for.
+     */
+    FilterDef getFilterDef() {
+
+        return (this.filterDef);
+
+    }
+
+    /**
+     * Release the Filter instance associated with this FilterConfig,
+     * if there is one.
+     */
+    void release() {
+
+        unregisterJMX();
+        
+        if (this.filter != null)
+        {
+            if (Globals.IS_SECURITY_ENABLED) {
+                try {
+                    SecurityUtil.doAsPrivilege("destroy", filter);
+                } catch(java.lang.Exception ex){
+                    context.getLogger().error("ApplicationFilterConfig.doAsPrivilege", ex);
+                }
+                SecurityUtil.remove(filter);
+            } else {
+                filter.destroy();
+            }
+            if (!context.getIgnoreAnnotations()) {
+                try {
+                    ((StandardContext) context).getInstanceManager().destroyInstance(this.filter);
+                } catch (Exception e) {
+                    context.getLogger().error("ApplicationFilterConfig.preDestroy", e);
+                }
+            }
+        }
+        this.filter = null;
+
+     }
+
+
+    /**
+     * Set the filter definition we are configured for.  This has the side
+     * effect of instantiating an instance of the corresponding filter class.
+     *
+     * @param filterDef The new filter definition
+     *
+     * @exception ClassCastException if the specified class does not implement
+     *  the <code>javax.servlet.Filter</code> interface
+     * @exception ClassNotFoundException if the filter class cannot be found
+     * @exception IllegalAccessException if the filter class cannot be
+     *  publicly instantiated
+     * @exception InstantiationException if an exception occurs while
+     *  instantiating the filter object
+     * @exception ServletException if thrown by the filter's init() method
+     * @throws NamingException
+     * @throws InvocationTargetException
+     */
+    void setFilterDef(FilterDef filterDef)
+        throws ClassCastException, ClassNotFoundException,
+               IllegalAccessException, InstantiationException,
+               ServletException, InvocationTargetException, NamingException {
+
+        this.filterDef = filterDef;
+        if (filterDef == null) {
+
+            // Release any previously allocated filter instance
+            if (this.filter != null){
+                if (Globals.IS_SECURITY_ENABLED) {
+                    try{
+                        SecurityUtil.doAsPrivilege("destroy", filter);
+                    } catch(java.lang.Exception ex){
+                        context.getLogger().error("ApplicationFilterConfig.doAsPrivilege", ex);
+                    }
+                    SecurityUtil.remove(filter);
+                } else {
+                    filter.destroy();
+                }
+                if (!context.getIgnoreAnnotations()) {
+                    try {
+                        ((StandardContext) context).getInstanceManager().destroyInstance(this.filter);
+                    } catch (Exception e) {
+                        context.getLogger().error("ApplicationFilterConfig.preDestroy", e);
+                    }
+                }
+            }
+            this.filter = null;
+
+        } else {
+            // Allocate a new filter instance if necessary
+            if (filterDef.getFilter() == null) {
+                getFilter();
+            }
+        }
+
+    }
+
+
+    // -------------------------------------------------------- Private Methods
+
+    private InstanceManager getInstanceManager() {
+        if (instanceManager == null) {
+            if (context instanceof StandardContext) {
+                instanceManager = ((StandardContext)context).getInstanceManager();
+            } else {
+                instanceManager = new DefaultInstanceManager(null,
+                        new HashMap<String, Map<String, String>>(),
+                        context,
+                        getClass().getClassLoader()); 
+            }
+        }
+        return instanceManager;
+    }
+
+    private void registerJMX() {
+        String parentName = context.getName();
+        if (!parentName.startsWith("/")) {
+            parentName = "/" + parentName;
+        }
+
+        String hostName = context.getParent().getName();
+        hostName = (hostName == null) ? "DEFAULT" : hostName;
+
+        // domain == engine name
+        String domain = context.getParent().getParent().getName();
+
+        String webMod = "//" + hostName + parentName;
+        String onameStr = null;
+        if (context instanceof StandardContext) {
+            StandardContext standardContext = (StandardContext) context;
+            onameStr = domain + ":j2eeType=Filter,name=" +
+                 filterDef.getFilterName() + ",WebModule=" + webMod +
+                 ",J2EEApplication=" +
+                 standardContext.getJ2EEApplication() + ",J2EEServer=" +
+                 standardContext.getJ2EEServer();
+        } else {
+            onameStr = domain + ":j2eeType=Filter,name=" +
+                 filterDef.getFilterName() + ",WebModule=" + webMod;
+        }
+        try {
+            oname = new ObjectName(onameStr);
+            Registry.getRegistry(null, null).registerComponent(this, oname,
+                    null);
+        } catch (Exception ex) {
+            log.info(sm.getString("applicationFilterConfig.jmxRegisterFail",
+                    getFilterClass(), getFilterName()), ex);
+        }
+    }
+    
+    private void unregisterJMX() {
+        // unregister this component
+        if (oname != null) {
+            try {
+                Registry.getRegistry(null, null).unregisterComponent(oname);
+                if (log.isDebugEnabled())
+                    log.debug(sm.getString(
+                            "applicationFilterConfig.jmxUnregister",
+                            getFilterClass(), getFilterName()));
+            } catch(Exception ex) {
+                log.error(sm.getString(
+                        "applicationFilterConfig.jmxUnregisterFail",
+                        getFilterClass(), getFilterName()), ex);
+            }
+        }
+
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationFilterFactory.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationFilterFactory.java
new file mode 100644
index 0000000..57fbf98
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationFilterFactory.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.catalina.core;
+
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Servlet;
+import javax.servlet.ServletRequest;
+
+import org.apache.catalina.Globals;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.comet.CometFilter;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.deploy.FilterMap;
+
+/**
+ * Factory for the creation and caching of Filters and creation 
+ * of Filter Chains.
+ *
+ * @author Greg Murray
+ * @author Remy Maucherat
+ * @version $Revision: 1.0
+ */
+
+public final class ApplicationFilterFactory {
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    public static final String DISPATCHER_TYPE_ATTR = 
+        Globals.DISPATCHER_TYPE_ATTR;
+    public static final String DISPATCHER_REQUEST_PATH_ATTR = 
+        Globals.DISPATCHER_REQUEST_PATH_ATTR;
+
+    private static ApplicationFilterFactory factory = null;
+
+
+    // ----------------------------------------------------------- Constructors
+
+    private ApplicationFilterFactory() {
+        // Prevent instantiation outside of the getInstanceMethod().
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return the factory instance.
+     */
+    public static ApplicationFilterFactory getInstance() {
+        if (factory == null) {
+            factory = new ApplicationFilterFactory();
+        }
+        return factory;
+    }
+
+
+    /**
+     * Construct and return a FilterChain implementation that will wrap the
+     * execution of the specified servlet instance.  If we should not execute
+     * a filter chain at all, return <code>null</code>.
+     *
+     * @param request The servlet request we are processing
+     * @param servlet The servlet instance to be wrapped
+     */
+    public ApplicationFilterChain createFilterChain
+        (ServletRequest request, Wrapper wrapper, Servlet servlet) {
+
+        // get the dispatcher type
+        DispatcherType dispatcher = null; 
+        if (request.getAttribute(DISPATCHER_TYPE_ATTR) != null) {
+            dispatcher = (DispatcherType) request.getAttribute(DISPATCHER_TYPE_ATTR);
+        }
+        String requestPath = null;
+        Object attribute = request.getAttribute(DISPATCHER_REQUEST_PATH_ATTR);
+        
+        if (attribute != null){
+            requestPath = attribute.toString();
+        }
+        
+        // If there is no servlet to execute, return null
+        if (servlet == null)
+            return (null);
+
+        boolean comet = false;
+        
+        // Create and initialize a filter chain object
+        ApplicationFilterChain filterChain = null;
+        if (request instanceof Request) {
+            Request req = (Request) request;
+            comet = req.isComet();
+            if (Globals.IS_SECURITY_ENABLED) {
+                // Security: Do not recycle
+                filterChain = new ApplicationFilterChain();
+                if (comet) {
+                    req.setFilterChain(filterChain);
+                }
+            } else {
+                filterChain = (ApplicationFilterChain) req.getFilterChain();
+                if (filterChain == null) {
+                    filterChain = new ApplicationFilterChain();
+                    req.setFilterChain(filterChain);
+                }
+            }
+        } else {
+            // Request dispatcher in use
+            filterChain = new ApplicationFilterChain();
+        }
+
+        filterChain.setServlet(servlet);
+
+        filterChain.setSupport
+            (((StandardWrapper)wrapper).getInstanceSupport());
+
+        // Acquire the filter mappings for this Context
+        StandardContext context = (StandardContext) wrapper.getParent();
+        FilterMap filterMaps[] = context.findFilterMaps();
+
+        // If there are no filter mappings, we are done
+        if ((filterMaps == null) || (filterMaps.length == 0))
+            return (filterChain);
+
+        // Acquire the information we will need to match filter mappings
+        String servletName = wrapper.getName();
+
+        // Add the relevant path-mapped filters to this filter chain
+        for (int i = 0; i < filterMaps.length; i++) {
+            if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
+                continue;
+            }
+            if (!matchFiltersURL(filterMaps[i], requestPath))
+                continue;
+            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
+                context.findFilterConfig(filterMaps[i].getFilterName());
+            if (filterConfig == null) {
+                // FIXME - log configuration problem
+                continue;
+            }
+            boolean isCometFilter = false;
+            if (comet) {
+                try {
+                    isCometFilter = filterConfig.getFilter() instanceof CometFilter;
+                } catch (Exception e) {
+                    // Note: The try catch is there because getFilter has a lot of 
+                    // declared exceptions. However, the filter is allocated much
+                    // earlier
+                }
+                if (isCometFilter) {
+                    filterChain.addFilter(filterConfig);
+                }
+            } else {
+                filterChain.addFilter(filterConfig);
+            }
+        }
+
+        // Add filters that match on servlet name second
+        for (int i = 0; i < filterMaps.length; i++) {
+            if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
+                continue;
+            }
+            if (!matchFiltersServlet(filterMaps[i], servletName))
+                continue;
+            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
+                context.findFilterConfig(filterMaps[i].getFilterName());
+            if (filterConfig == null) {
+                // FIXME - log configuration problem
+                continue;
+            }
+            boolean isCometFilter = false;
+            if (comet) {
+                try {
+                    isCometFilter = filterConfig.getFilter() instanceof CometFilter;
+                } catch (Exception e) {
+                    // Note: The try catch is there because getFilter has a lot of 
+                    // declared exceptions. However, the filter is allocated much
+                    // earlier
+                }
+                if (isCometFilter) {
+                    filterChain.addFilter(filterConfig);
+                }
+            } else {
+                filterChain.addFilter(filterConfig);
+            }
+        }
+
+        // Return the completed filter chain
+        return (filterChain);
+
+    }
+
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Return <code>true</code> if the context-relative request path
+     * matches the requirements of the specified filter mapping;
+     * otherwise, return <code>false</code>.
+     *
+     * @param filterMap Filter mapping being checked
+     * @param requestPath Context-relative request path of this request
+     */
+    private boolean matchFiltersURL(FilterMap filterMap, String requestPath) {
+
+        // Check the specific "*" special URL pattern, which also matches
+        // named dispatches
+        if (filterMap.getMatchAllUrlPatterns())
+            return (true);
+        
+        if (requestPath == null)
+            return (false);
+
+        // Match on context relative request path
+        String[] testPaths = filterMap.getURLPatterns();
+        
+        for (int i = 0; i < testPaths.length; i++) {
+            if (matchFiltersURL(testPaths[i], requestPath)) {
+                return (true);
+            }
+        }
+        
+        // No match
+        return (false);
+        
+    }
+    
+
+    /**
+     * Return <code>true</code> if the context-relative request path
+     * matches the requirements of the specified filter mapping;
+     * otherwise, return <code>false</code>.
+     *
+     * @param testPath URL mapping being checked
+     * @param requestPath Context-relative request path of this request
+     */
+    private boolean matchFiltersURL(String testPath, String requestPath) {
+        
+        if (testPath == null)
+            return (false);
+
+        // Case 1 - Exact Match
+        if (testPath.equals(requestPath))
+            return (true);
+
+        // Case 2 - Path Match ("/.../*")
+        if (testPath.equals("/*"))
+            return (true);
+        if (testPath.endsWith("/*")) {
+            if (testPath.regionMatches(0, requestPath, 0, 
+                                       testPath.length() - 2)) {
+                if (requestPath.length() == (testPath.length() - 2)) {
+                    return (true);
+                } else if ('/' == requestPath.charAt(testPath.length() - 2)) {
+                    return (true);
+                }
+            }
+            return (false);
+        }
+
+        // Case 3 - Extension Match
+        if (testPath.startsWith("*.")) {
+            int slash = requestPath.lastIndexOf('/');
+            int period = requestPath.lastIndexOf('.');
+            if ((slash >= 0) && (period > slash) 
+                && (period != requestPath.length() - 1)
+                && ((requestPath.length() - period) 
+                    == (testPath.length() - 1))) {
+                return (testPath.regionMatches(2, requestPath, period + 1,
+                                               testPath.length() - 2));
+            }
+        }
+
+        // Case 4 - "Default" Match
+        return (false); // NOTE - Not relevant for selecting filters
+
+    }
+
+
+    /**
+     * Return <code>true</code> if the specified servlet name matches
+     * the requirements of the specified filter mapping; otherwise
+     * return <code>false</code>.
+     *
+     * @param filterMap Filter mapping being checked
+     * @param servletName Servlet name being checked
+     */
+    private boolean matchFiltersServlet(FilterMap filterMap, 
+                                        String servletName) {
+
+        if (servletName == null) {
+            return (false);
+        }
+        // Check the specific "*" special servlet name
+        else if (filterMap.getMatchAllServletNames()) {
+            return (true);
+        } else {
+            String[] servletNames = filterMap.getServletNames();
+            for (int i = 0; i < servletNames.length; i++) {
+                if (servletName.equals(servletNames[i])) {
+                    return (true);
+                }
+            }
+            return false;
+        }
+
+    }
+
+
+    /**
+     * Convenience method which returns true if  the dispatcher type
+     * matches the dispatcher types specified in the FilterMap
+     */
+    private boolean matchDispatcher(FilterMap filterMap, DispatcherType type) {
+        switch (type) {
+            case FORWARD : {
+                if ((filterMap.getDispatcherMapping() & FilterMap.FORWARD) > 0) {
+                        return true;
+                }
+                break;
+            }
+            case INCLUDE : {
+                if ((filterMap.getDispatcherMapping() & FilterMap.INCLUDE) > 0) {
+                    return true;
+                }
+                break;
+            }
+            case REQUEST : {
+                if ((filterMap.getDispatcherMapping() & FilterMap.REQUEST) > 0) {
+                    return true;
+                }
+                break;
+            }
+            case ERROR : {
+                if ((filterMap.getDispatcherMapping() & FilterMap.ERROR) > 0) {
+                    return true;
+                }
+                break;
+            }
+            case ASYNC : {
+                if ((filterMap.getDispatcherMapping() & FilterMap.ASYNC) > 0) {
+                    return true;
+                }
+                break;
+            }
+        }
+        return false;
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationFilterRegistration.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationFilterRegistration.java
new file mode 100644
index 0000000..93a6227
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationFilterRegistration.java
@@ -0,0 +1,214 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.core;
+
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.FilterRegistration;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.deploy.FilterDef;
+import org.apache.catalina.deploy.FilterMap;
+import org.apache.catalina.util.ParameterMap;
+import org.apache.tomcat.util.res.StringManager;
+
+public class ApplicationFilterRegistration
+        implements FilterRegistration.Dynamic {
+
+    /**
+     * The string manager for this package.
+     */
+    private static final StringManager sm =
+      StringManager.getManager(Constants.Package);
+    
+    private FilterDef filterDef;
+    private Context context;
+    
+    public ApplicationFilterRegistration(FilterDef filterDef,
+            Context context) {
+        this.filterDef = filterDef;
+        this.context = context;
+        
+    }
+
+    @Override
+    public void addMappingForServletNames(
+            EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter,
+            String... servletNames) {
+
+        FilterMap filterMap = new FilterMap();
+        
+        filterMap.setFilterName(filterDef.getFilterName());
+        
+        if (dispatcherTypes != null) {
+            for (DispatcherType dispatcherType : dispatcherTypes) {
+                filterMap.setDispatcher(dispatcherType.name());
+            }
+        }
+        
+        if (servletNames != null) {
+            for (String servletName : servletNames) {
+                filterMap.addServletName(servletName);
+            }
+        
+            if (isMatchAfter) {
+                context.addFilterMapBefore(filterMap);
+            } else {
+                context.addFilterMap(filterMap);
+            }
+        }
+        // else error?
+    }
+
+    @Override
+    public void addMappingForUrlPatterns(
+            EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter,
+            String... urlPatterns) {
+
+        FilterMap filterMap = new FilterMap();
+
+        filterMap.setFilterName(filterDef.getFilterName());
+
+        if (dispatcherTypes != null) {
+            for (DispatcherType dispatcherType : dispatcherTypes) {
+                filterMap.setDispatcher(dispatcherType.name());
+            }
+        }
+        
+        if (urlPatterns != null) {
+            for (String urlPattern : urlPatterns) {
+                filterMap.addURLPattern(urlPattern);
+            }
+        
+            if (isMatchAfter) {
+                context.addFilterMapBefore(filterMap);
+            } else {
+                context.addFilterMap(filterMap);
+            }
+        }
+        // else error?
+        
+    }
+
+    @Override
+    public Collection<String> getServletNameMappings() {
+        Collection<String> result = new HashSet<String>();
+        
+        FilterMap[] filterMaps = context.findFilterMaps();
+        
+        for (FilterMap filterMap : filterMaps) {
+            if (filterMap.getFilterName().equals(filterDef.getFilterName())) {
+                for (String servletName : filterMap.getServletNames()) {
+                    result.add(servletName);
+                }
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public Collection<String> getUrlPatternMappings() {
+        Collection<String> result = new HashSet<String>();
+        
+        FilterMap[] filterMaps = context.findFilterMaps();
+        
+        for (FilterMap filterMap : filterMaps) {
+            if (filterMap.getFilterName().equals(filterDef.getFilterName())) {
+                for (String urlPattern : filterMap.getURLPatterns()) {
+                    result.add(urlPattern);
+                }
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public String getClassName() {
+        return filterDef.getFilterClass();
+   }
+
+    @Override
+    public String getInitParameter(String name) {
+        return filterDef.getParameterMap().get(name);
+    }
+
+    @Override
+    public Map<String, String> getInitParameters() {
+        ParameterMap<String,String> result = new ParameterMap<String,String>();
+        result.putAll(filterDef.getParameterMap());
+        result.setLocked(true);
+        return result;
+    }
+
+    @Override
+    public String getName() {
+        return filterDef.getFilterName();
+    }
+
+    @Override
+    public boolean setInitParameter(String name, String value) {
+        if (name == null || value == null) {
+            throw new IllegalArgumentException(
+                    sm.getString("applicationFilterRegistration.nullInitParam",
+                            name, value));
+        }
+        if (getInitParameter(name) != null) {
+            return false;
+        }
+        
+        filterDef.addInitParameter(name, value);
+
+        return true;
+    }
+
+    @Override
+    public Set<String> setInitParameters(Map<String, String> initParameters) {
+        
+        Set<String> conflicts = new HashSet<String>();
+        
+        for (Map.Entry<String, String> entry : initParameters.entrySet()) {
+            if (entry.getKey() == null || entry.getValue() == null) {
+                throw new IllegalArgumentException(sm.getString(
+                        "applicationFilterRegistration.nullInitParams",
+                                entry.getKey(), entry.getValue()));
+            }
+            if (getInitParameter(entry.getKey()) != null) {
+                conflicts.add(entry.getKey());
+            }
+        }
+
+        // Have to add in a separate loop since spec requires no updates at all
+        // if there is an issue
+        for (Map.Entry<String, String> entry : initParameters.entrySet()) {
+            setInitParameter(entry.getKey(), entry.getValue());
+        }
+        
+        return conflicts;
+    }
+
+    @Override
+    public void setAsyncSupported(boolean asyncSupported) {
+        filterDef.setAsyncSupported(Boolean.valueOf(asyncSupported).toString());
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationHttpRequest.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationHttpRequest.java
new file mode 100644
index 0000000..77cdd2f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationHttpRequest.java
@@ -0,0 +1,975 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpSession;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Globals;
+import org.apache.catalina.Manager;
+import org.apache.catalina.Session;
+import org.apache.catalina.util.Enumerator;
+import org.apache.catalina.util.RequestUtil;
+
+
+/**
+ * Wrapper around a <code>javax.servlet.http.HttpServletRequest</code>
+ * that transforms an application request object (which might be the original
+ * one passed to a servlet, or might be based on the 2.3
+ * <code>javax.servlet.http.HttpServletRequestWrapper</code> class)
+ * back into an internal <code>org.apache.catalina.HttpRequest</code>.
+ * <p>
+ * <strong>WARNING</strong>:  Due to Java's lack of support for multiple
+ * inheritance, all of the logic in <code>ApplicationRequest</code> is
+ * duplicated in <code>ApplicationHttpRequest</code>.  Make sure that you
+ * keep these two classes in synchronization when making changes!
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @version $Id: ApplicationHttpRequest.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+class ApplicationHttpRequest extends HttpServletRequestWrapper {
+
+
+    // ------------------------------------------------------- Static Variables
+
+
+    /**
+     * The set of attribute names that are special for request dispatchers.
+     */
+    protected static final String specials[] =
+    { RequestDispatcher.INCLUDE_REQUEST_URI,
+      RequestDispatcher.INCLUDE_CONTEXT_PATH,
+      RequestDispatcher.INCLUDE_SERVLET_PATH,
+      RequestDispatcher.INCLUDE_PATH_INFO,
+      RequestDispatcher.INCLUDE_QUERY_STRING,
+      RequestDispatcher.FORWARD_REQUEST_URI, 
+      RequestDispatcher.FORWARD_CONTEXT_PATH,
+      RequestDispatcher.FORWARD_SERVLET_PATH, 
+      RequestDispatcher.FORWARD_PATH_INFO,
+      RequestDispatcher.FORWARD_QUERY_STRING };
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new wrapped request around the specified servlet request.
+     *
+     * @param request The servlet request being wrapped
+     */
+    public ApplicationHttpRequest(HttpServletRequest request, Context context,
+                                  boolean crossContext) {
+
+        super(request);
+        this.context = context;
+        this.crossContext = crossContext;
+        setRequest(request);
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The context for this request.
+     */
+    protected Context context = null;
+
+
+    /**
+     * The context path for this request.
+     */
+    protected String contextPath = null;
+
+
+    /**
+     * If this request is cross context, since this changes session access
+     * behavior.
+     */
+    protected boolean crossContext = false;
+
+
+    /**
+     * The current dispatcher type.
+     */
+    protected DispatcherType dispatcherType = null;
+
+
+    /**
+     * Descriptive information about this implementation.
+     */
+    protected static final String info =
+        "org.apache.catalina.core.ApplicationHttpRequest/1.0";
+
+
+    /**
+     * The request parameters for this request.  This is initialized from the
+     * wrapped request, but updates are allowed.
+     */
+    protected Map<String, String[]> parameters = null;
+
+
+    /**
+     * Have the parameters for this request already been parsed?
+     */
+    private boolean parsedParams = false;
+
+
+    /**
+     * The path information for this request.
+     */
+    protected String pathInfo = null;
+
+
+    /**
+     * The query parameters for the current request.
+     */
+    private String queryParamString = null;
+
+
+    /**
+     * The query string for this request.
+     */
+    protected String queryString = null;
+
+
+    /**
+     * The current request dispatcher path.
+     */
+    protected Object requestDispatcherPath = null;
+
+
+    /**
+     * The request URI for this request.
+     */
+    protected String requestURI = null;
+
+
+    /**
+     * The servlet path for this request.
+     */
+    protected String servletPath = null;
+
+
+    /**
+     * The currently active session for this request.
+     */
+    protected Session session = null;
+
+
+    /**
+     * Special attributes.
+     */
+    protected Object[] specialAttributes = new Object[specials.length];
+    
+
+    // ------------------------------------------------- ServletRequest Methods
+
+
+    /**
+     * Override the <code>getAttribute()</code> method of the wrapped request.
+     *
+     * @param name Name of the attribute to retrieve
+     */
+    @Override
+    public Object getAttribute(String name) {
+
+        if (name.equals(Globals.DISPATCHER_TYPE_ATTR)) {
+            return dispatcherType;
+        } else if (name.equals(Globals.DISPATCHER_REQUEST_PATH_ATTR)) {
+            if ( requestDispatcherPath != null ){
+                return requestDispatcherPath.toString();
+            } else {
+                return null;   
+            }
+        }
+
+        int pos = getSpecial(name);
+        if (pos == -1) {
+            return getRequest().getAttribute(name);
+        } else {
+            if ((specialAttributes[pos] == null) 
+                && (specialAttributes[5] == null) && (pos >= 5)) {
+                // If it's a forward special attribute, and null, it means this
+                // is an include, so we check the wrapped request since 
+                // the request could have been forwarded before the include
+                return getRequest().getAttribute(name);
+            } else {
+                return specialAttributes[pos];
+            }
+        }
+
+    }
+
+
+    /**
+     * Override the <code>getAttributeNames()</code> method of the wrapped
+     * request.
+     */
+    @Override
+    public Enumeration<String> getAttributeNames() {
+        return (new AttributeNamesEnumerator());
+    }
+
+
+    /**
+     * Override the <code>removeAttribute()</code> method of the
+     * wrapped request.
+     *
+     * @param name Name of the attribute to remove
+     */
+    @Override
+    public void removeAttribute(String name) {
+
+        if (!removeSpecial(name))
+            getRequest().removeAttribute(name);
+
+    }
+
+
+    /**
+     * Override the <code>setAttribute()</code> method of the
+     * wrapped request.
+     *
+     * @param name Name of the attribute to set
+     * @param value Value of the attribute to set
+     */
+    @Override
+    public void setAttribute(String name, Object value) {
+
+        if (name.equals(Globals.DISPATCHER_TYPE_ATTR)) {
+            dispatcherType = (DispatcherType)value;
+            return;
+        } else if (name.equals(Globals.DISPATCHER_REQUEST_PATH_ATTR)) {
+            requestDispatcherPath = value;
+            return;
+        }
+
+        if (!setSpecial(name, value)) {
+            getRequest().setAttribute(name, value);
+        }
+
+    }
+
+
+    /**
+     * Return a RequestDispatcher that wraps the resource at the specified
+     * path, which may be interpreted as relative to the current request path.
+     *
+     * @param path Path of the resource to be wrapped
+     */
+    @Override
+    public RequestDispatcher getRequestDispatcher(String path) {
+
+        if (context == null)
+            return (null);
+
+        // If the path is already context-relative, just pass it through
+        if (path == null)
+            return (null);
+        else if (path.startsWith("/"))
+            return (context.getServletContext().getRequestDispatcher(path));
+
+        // Convert a request-relative path to a context-relative one
+        String servletPath = 
+            (String) getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
+        if (servletPath == null)
+            servletPath = getServletPath();
+
+        // Add the path info, if there is any
+        String pathInfo = getPathInfo();
+        String requestPath = null;
+
+        if (pathInfo == null) {
+            requestPath = servletPath;
+        } else {
+            requestPath = servletPath + pathInfo;
+        }
+
+        int pos = requestPath.lastIndexOf('/');
+        String relative = null;
+        if (pos >= 0) {
+            relative = requestPath.substring(0, pos + 1) + path;
+        } else {
+            relative = requestPath + path;
+        }
+
+        return (context.getServletContext().getRequestDispatcher(relative));
+
+    }
+
+    
+    /**
+     * Override the getDispatcherType() method of the wrapped request.
+     * 
+     */
+    @Override
+    public DispatcherType getDispatcherType() {
+        return dispatcherType;
+    }
+
+
+    // --------------------------------------------- HttpServletRequest Methods
+
+
+    /**
+     * Override the <code>getContextPath()</code> method of the wrapped
+     * request.
+     */
+    @Override
+    public String getContextPath() {
+
+        return (this.contextPath);
+
+    }
+
+
+    /**
+     * Override the <code>getParameter()</code> method of the wrapped request.
+     *
+     * @param name Name of the requested parameter
+     */
+    @Override
+    public String getParameter(String name) {
+
+        parseParameters();
+
+        Object value = parameters.get(name);
+        if (value == null)
+            return (null);
+        else if (value instanceof String[])
+            return (((String[]) value)[0]);
+        else if (value instanceof String)
+            return ((String) value);
+        else
+            return (value.toString());
+
+    }
+
+
+    /**
+     * Override the <code>getParameterMap()</code> method of the
+     * wrapped request.
+     */
+    @Override
+    public Map<String, String[]> getParameterMap() {
+
+        parseParameters();
+        return (parameters);
+
+    }
+
+
+    /**
+     * Override the <code>getParameterNames()</code> method of the
+     * wrapped request.
+     */
+    @Override
+    public Enumeration<String> getParameterNames() {
+
+        parseParameters();
+        return (new Enumerator<String>(parameters.keySet()));
+
+    }
+
+
+    /**
+     * Override the <code>getParameterValues()</code> method of the
+     * wrapped request.
+     *
+     * @param name Name of the requested parameter
+     */
+    @Override
+    public String[] getParameterValues(String name) {
+
+        parseParameters();
+        Object value = parameters.get(name);
+        if (value == null)
+            return null;
+        else if (value instanceof String[])
+            return ((String[]) value);
+        else if (value instanceof String) {
+            String values[] = new String[1];
+            values[0] = (String) value;
+            return (values);
+        } else {
+            String values[] = new String[1];
+            values[0] = value.toString();
+            return (values);
+        }
+
+    }
+
+
+    /**
+     * Override the <code>getPathInfo()</code> method of the wrapped request.
+     */
+    @Override
+    public String getPathInfo() {
+
+        return (this.pathInfo);
+
+    }
+
+
+    /**
+     * Override the <code>getQueryString()</code> method of the wrapped
+     * request.
+     */
+    @Override
+    public String getQueryString() {
+
+        return (this.queryString);
+
+    }
+
+
+    /**
+     * Override the <code>getRequestURI()</code> method of the wrapped
+     * request.
+     */
+    @Override
+    public String getRequestURI() {
+
+        return (this.requestURI);
+
+    }
+
+
+    /**
+     * Override the <code>getRequestURL()</code> method of the wrapped
+     * request.
+     */
+    @Override
+    public StringBuffer getRequestURL() {
+
+        StringBuffer url = new StringBuffer();
+        String scheme = getScheme();
+        int port = getServerPort();
+        if (port < 0)
+            port = 80; // Work around java.net.URL bug
+
+        url.append(scheme);
+        url.append("://");
+        url.append(getServerName());
+        if ((scheme.equals("http") && (port != 80))
+            || (scheme.equals("https") && (port != 443))) {
+            url.append(':');
+            url.append(port);
+        }
+        url.append(getRequestURI());
+
+        return (url);
+
+    }
+
+
+    /**
+     * Override the <code>getServletPath()</code> method of the wrapped
+     * request.
+     */
+    @Override
+    public String getServletPath() {
+
+        return (this.servletPath);
+
+    }
+
+
+    /**
+     * Return the session associated with this Request, creating one
+     * if necessary.
+     */
+    @Override
+    public HttpSession getSession() {
+        return (getSession(true));
+    }
+
+
+    /**
+     * Return the session associated with this Request, creating one
+     * if necessary and requested.
+     *
+     * @param create Create a new session if one does not exist
+     */
+    @Override
+    public HttpSession getSession(boolean create) {
+
+        if (crossContext) {
+            
+            // There cannot be a session if no context has been assigned yet
+            if (context == null)
+                return (null);
+
+            // Return the current session if it exists and is valid
+            if (session != null && session.isValid()) {
+                return (session.getSession());
+            }
+
+            HttpSession other = super.getSession(false);
+            if (create && (other == null)) {
+                // First create a session in the first context: the problem is
+                // that the top level request is the only one which can 
+                // create the cookie safely
+                other = super.getSession(true);
+            }
+            if (other != null) {
+                Session localSession = null;
+                try {
+                    localSession =
+                        context.getManager().findSession(other.getId());
+                    if (localSession != null && !localSession.isValid()) {
+                        localSession = null;
+                    }
+                } catch (IOException e) {
+                    // Ignore
+                }
+                if (localSession == null && create) {
+                    localSession = 
+                        context.getManager().createSession(other.getId());
+                }
+                if (localSession != null) {
+                    localSession.access();
+                    session = localSession;
+                    return session.getSession();
+                }
+            }
+            return null;
+
+        } else {
+            return super.getSession(create);
+        }
+
+    }
+
+
+    /**
+     * Returns true if the request specifies a JSESSIONID that is valid within
+     * the context of this ApplicationHttpRequest, false otherwise.
+     *
+     * @return true if the request specifies a JSESSIONID that is valid within
+     * the context of this ApplicationHttpRequest, false otherwise.
+     */
+    @Override
+    public boolean isRequestedSessionIdValid() {
+
+        if (crossContext) {
+
+            String requestedSessionId = getRequestedSessionId();
+            if (requestedSessionId == null)
+                return (false);
+            if (context == null)
+                return (false);
+            Manager manager = context.getManager();
+            if (manager == null)
+                return (false);
+            Session session = null;
+            try {
+                session = manager.findSession(requestedSessionId);
+            } catch (IOException e) {
+                // Ignore
+            }
+            if ((session != null) && session.isValid()) {
+                return (true);
+            } else {
+                return (false);
+            }
+
+        } else {
+            return super.isRequestedSessionIdValid();
+        }
+    }
+
+
+    // -------------------------------------------------------- Package Methods
+
+
+    /**
+     * Recycle this request
+     */
+    public void recycle() {
+        if (session != null) {
+            session.endAccess();
+        }
+    }
+
+
+    /**
+     * Return descriptive information about this implementation.
+     */
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    /**
+     * Perform a shallow copy of the specified Map, and return the result.
+     *
+     * @param orig Origin Map to be copied
+     */
+    Map<String, String[]> copyMap(Map<String, String[]> orig) {
+
+        if (orig == null)
+            return (new HashMap<String, String[]>());
+        HashMap<String, String[]> dest = new HashMap<String, String[]>();
+        
+        for (Map.Entry<String, String[]> entry : orig.entrySet()) {
+            dest.put(entry.getKey(), entry.getValue());
+        }
+
+        return (dest);
+
+    }
+
+
+    /**
+     * Set the context path for this request.
+     *
+     * @param contextPath The new context path
+     */
+    void setContextPath(String contextPath) {
+
+        this.contextPath = contextPath;
+
+    }
+
+
+    /**
+     * Set the path information for this request.
+     *
+     * @param pathInfo The new path info
+     */
+    void setPathInfo(String pathInfo) {
+
+        this.pathInfo = pathInfo;
+
+    }
+
+
+    /**
+     * Set the query string for this request.
+     *
+     * @param queryString The new query string
+     */
+    void setQueryString(String queryString) {
+
+        this.queryString = queryString;
+
+    }
+
+
+    /**
+     * Set the request that we are wrapping.
+     *
+     * @param request The new wrapped request
+     */
+    void setRequest(HttpServletRequest request) {
+
+        super.setRequest(request);
+
+        // Initialize the attributes for this request
+        dispatcherType = (DispatcherType)request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);
+        requestDispatcherPath = 
+            request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);
+
+        // Initialize the path elements for this request
+        contextPath = request.getContextPath();
+        pathInfo = request.getPathInfo();
+        queryString = request.getQueryString();
+        requestURI = request.getRequestURI();
+        servletPath = request.getServletPath();
+
+    }
+
+
+    /**
+     * Set the request URI for this request.
+     *
+     * @param requestURI The new request URI
+     */
+    void setRequestURI(String requestURI) {
+
+        this.requestURI = requestURI;
+
+    }
+
+
+    /**
+     * Set the servlet path for this request.
+     *
+     * @param servletPath The new servlet path
+     */
+    void setServletPath(String servletPath) {
+
+        this.servletPath = servletPath;
+
+    }
+
+
+    /**
+     * Parses the parameters of this request.
+     *
+     * If parameters are present in both the query string and the request
+     * content, they are merged.
+     */
+    void parseParameters() {
+
+        if (parsedParams) {
+            return;
+        }
+
+        parameters = new HashMap<String, String[]>();
+        parameters = copyMap(getRequest().getParameterMap());
+        mergeParameters();
+        parsedParams = true;
+    }
+
+
+    /**
+     * Save query parameters for this request.
+     *
+     * @param queryString The query string containing parameters for this
+     *                    request
+     */
+    void setQueryParams(String queryString) {
+        this.queryParamString = queryString;
+    }
+
+    // ------------------------------------------------------ Protected Methods
+
+    /**
+     * Is this attribute name one of the special ones that is added only for
+     * included servlets?
+     *
+     * @param name Attribute name to be tested
+     */
+    protected boolean isSpecial(String name) {
+
+        for (int i = 0; i < specials.length; i++) {
+            if (specials[i].equals(name))
+                return (true);
+        }
+        return (false);
+
+    }
+
+
+    /**
+     * Get a special attribute.
+     *
+     * @return the special attribute pos, or -1 if it is not a special 
+     *         attribute
+     */
+    protected int getSpecial(String name) {
+        for (int i = 0; i < specials.length; i++) {
+            if (specials[i].equals(name)) {
+                return (i);
+            }
+        }
+        return (-1);
+    }
+
+
+    /**
+     * Set a special attribute.
+     * 
+     * @return true if the attribute was a special attribute, false otherwise
+     */
+    protected boolean setSpecial(String name, Object value) {
+        for (int i = 0; i < specials.length; i++) {
+            if (specials[i].equals(name)) {
+                specialAttributes[i] = value;
+                return (true);
+            }
+        }
+        return (false);
+    }
+
+
+    /**
+     * Remove a special attribute.
+     * 
+     * @return true if the attribute was a special attribute, false otherwise
+     */
+    protected boolean removeSpecial(String name) {
+        for (int i = 0; i < specials.length; i++) {
+            if (specials[i].equals(name)) {
+                specialAttributes[i] = null;
+                return (true);
+            }
+        }
+        return (false);
+    }
+
+
+    /**
+     * Merge the two sets of parameter values into a single String array.
+     *
+     * @param values1 First set of values
+     * @param values2 Second set of values
+     */
+    protected String[] mergeValues(Object values1, Object values2) {
+
+        ArrayList<Object> results = new ArrayList<Object>();
+
+        if (values1 == null) {
+            // Skip - nothing to merge
+        } else if (values1 instanceof String)
+            results.add(values1);
+        else if (values1 instanceof String[]) {
+            String values[] = (String[]) values1;
+            for (int i = 0; i < values.length; i++)
+                results.add(values[i]);
+        } else
+            results.add(values1.toString());
+
+        if (values2 == null) {
+            // Skip - nothing to merge
+        } else if (values2 instanceof String)
+            results.add(values2);
+        else if (values2 instanceof String[]) {
+            String values[] = (String[]) values2;
+            for (int i = 0; i < values.length; i++)
+                results.add(values[i]);
+        } else
+            results.add(values2.toString());
+
+        String values[] = new String[results.size()];
+        return results.toArray(values);
+
+    }
+
+
+    // ------------------------------------------------------ Private Methods
+
+
+    /**
+     * Merge the parameters from the saved query parameter string (if any), and
+     * the parameters already present on this request (if any), such that the
+     * parameter values from the query string show up first if there are
+     * duplicate parameter names.
+     */
+    private void mergeParameters() {
+
+        if ((queryParamString == null) || (queryParamString.length() < 1))
+            return;
+
+        HashMap<String, String[]> queryParameters = new HashMap<String, String[]>();
+        String encoding = getCharacterEncoding();
+        if (encoding == null)
+            encoding = "ISO-8859-1";
+        RequestUtil.parseParameters(queryParameters, queryParamString,
+                encoding);
+        Iterator<String> keys = parameters.keySet().iterator();
+        while (keys.hasNext()) {
+            String key = keys.next();
+            Object value = queryParameters.get(key);
+            if (value == null) {
+                queryParameters.put(key, parameters.get(key));
+                continue;
+            }
+            queryParameters.put
+                (key, mergeValues(value, parameters.get(key)));
+        }
+        parameters = queryParameters;
+
+    }
+
+
+    // ----------------------------------- AttributeNamesEnumerator Inner Class
+
+
+    /**
+     * Utility class used to expose the special attributes as being available
+     * as request attributes.
+     */
+    protected class AttributeNamesEnumerator implements Enumeration<String> {
+
+        protected int pos = -1;
+        protected int last = -1;
+        protected Enumeration<String> parentEnumeration = null;
+        protected String next = null;
+
+        public AttributeNamesEnumerator() {
+            parentEnumeration = getRequest().getAttributeNames();
+            for (int i = 0; i < specialAttributes.length; i++) {
+                if (getAttribute(specials[i]) != null) {
+                    last = i;
+                }
+            }
+        }
+
+        @Override
+        public boolean hasMoreElements() {
+            return ((pos != last) || (next != null) 
+                    || ((next = findNext()) != null));
+        }
+
+        @Override
+        public String nextElement() {
+            if (pos != last) {
+                for (int i = pos + 1; i <= last; i++) {
+                    if (getAttribute(specials[i]) != null) {
+                        pos = i;
+                        return (specials[i]);
+                    }
+                }
+            }
+            String result = next;
+            if (next != null) {
+                next = findNext();
+            } else {
+                throw new NoSuchElementException();
+            }
+            return result;
+        }
+
+        protected String findNext() {
+            String result = null;
+            while ((result == null) && (parentEnumeration.hasMoreElements())) {
+                String current = parentEnumeration.nextElement();
+                if (!isSpecial(current)) {
+                    result = current;
+                }
+            }
+            return result;
+        }
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationHttpResponse.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationHttpResponse.java
new file mode 100644
index 0000000..ee848f0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationHttpResponse.java
@@ -0,0 +1,401 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+
+/**
+ * Wrapper around a <code>javax.servlet.http.HttpServletResponse</code>
+ * that transforms an application response object (which might be the original
+ * one passed to a servlet, or might be based on the 2.3
+ * <code>javax.servlet.http.HttpServletResponseWrapper</code> class)
+ * back into an internal <code>org.apache.catalina.HttpResponse</code>.
+ * <p>
+ * <strong>WARNING</strong>:  Due to Java's lack of support for multiple
+ * inheritance, all of the logic in <code>ApplicationResponse</code> is
+ * duplicated in <code>ApplicationHttpResponse</code>.  Make sure that you
+ * keep these two classes in synchronization when making changes!
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: ApplicationHttpResponse.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+class ApplicationHttpResponse extends HttpServletResponseWrapper {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new wrapped response around the specified servlet response.
+     *
+     * @param response The servlet response being wrapped
+     */
+    public ApplicationHttpResponse(HttpServletResponse response) {
+
+        this(response, false);
+
+    }
+
+
+    /**
+     * Construct a new wrapped response around the specified servlet response.
+     *
+     * @param response The servlet response being wrapped
+     * @param included <code>true</code> if this response is being processed
+     *  by a <code>RequestDispatcher.include()</code> call
+     */
+    public ApplicationHttpResponse(HttpServletResponse response,
+                                   boolean included) {
+
+        super(response);
+        setIncluded(included);
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Is this wrapped response the subject of an <code>include()</code>
+     * call?
+     */
+    protected boolean included = false;
+
+
+    /**
+     * Descriptive information about this implementation.
+     */
+    protected static final String info =
+        "org.apache.catalina.core.ApplicationHttpResponse/1.0";
+
+
+    // ------------------------------------------------ ServletResponse Methods
+
+
+    /**
+     * Disallow <code>reset()</code> calls on a included response.
+     *
+     * @exception IllegalStateException if the response has already
+     *  been committed
+     */
+    @Override
+    public void reset() {
+
+        // If already committed, the wrapped response will throw ISE
+        if (!included || getResponse().isCommitted())
+            getResponse().reset();
+
+    }
+
+
+    /**
+     * Disallow <code>setContentLength()</code> calls on an included response.
+     *
+     * @param len The new content length
+     */
+    @Override
+    public void setContentLength(int len) {
+
+        if (!included)
+            getResponse().setContentLength(len);
+
+    }
+
+
+    /**
+     * Disallow <code>setContentType()</code> calls on an included response.
+     *
+     * @param type The new content type
+     */
+    @Override
+    public void setContentType(String type) {
+
+        if (!included)
+            getResponse().setContentType(type);
+
+    }
+
+
+    /**
+     * Disallow <code>setLocale()</code> calls on an included response.
+     *
+     * @param loc The new locale
+     */
+    @Override
+    public void setLocale(Locale loc) {
+
+        if (!included)
+            getResponse().setLocale(loc);
+
+    }
+
+
+    /**
+     * Ignore <code>setBufferSize()</code> calls on an included response.
+     *
+     * @param size The buffer size
+     */
+    @Override
+    public void setBufferSize(int size) {
+        if (!included)
+            getResponse().setBufferSize(size);
+    }
+
+
+    // -------------------------------------------- HttpServletResponse Methods
+
+
+    /**
+     * Disallow <code>addCookie()</code> calls on an included response.
+     *
+     * @param cookie The new cookie
+     */
+    @Override
+    public void addCookie(Cookie cookie) {
+
+        if (!included)
+            ((HttpServletResponse) getResponse()).addCookie(cookie);
+
+    }
+
+
+    /**
+     * Disallow <code>addDateHeader()</code> calls on an included response.
+     *
+     * @param name The new header name
+     * @param value The new header value
+     */
+    @Override
+    public void addDateHeader(String name, long value) {
+
+        if (!included)
+            ((HttpServletResponse) getResponse()).addDateHeader(name, value);
+
+    }
+
+
+    /**
+     * Disallow <code>addHeader()</code> calls on an included response.
+     *
+     * @param name The new header name
+     * @param value The new header value
+     */
+    @Override
+    public void addHeader(String name, String value) {
+
+        if (!included)
+            ((HttpServletResponse) getResponse()).addHeader(name, value);
+
+    }
+
+
+    /**
+     * Disallow <code>addIntHeader()</code> calls on an included response.
+     *
+     * @param name The new header name
+     * @param value The new header value
+     */
+    @Override
+    public void addIntHeader(String name, int value) {
+
+        if (!included)
+            ((HttpServletResponse) getResponse()).addIntHeader(name, value);
+
+    }
+
+
+    /**
+     * Disallow <code>sendError()</code> calls on an included response.
+     *
+     * @param sc The new status code
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public void sendError(int sc) throws IOException {
+
+        if (!included)
+            ((HttpServletResponse) getResponse()).sendError(sc);
+
+    }
+
+
+    /**
+     * Disallow <code>sendError()</code> calls on an included response.
+     *
+     * @param sc The new status code
+     * @param msg The new message
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public void sendError(int sc, String msg) throws IOException {
+
+        if (!included)
+            ((HttpServletResponse) getResponse()).sendError(sc, msg);
+
+    }
+
+
+    /**
+     * Disallow <code>sendRedirect()</code> calls on an included response.
+     *
+     * @param location The new location
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public void sendRedirect(String location) throws IOException {
+
+        if (!included)
+            ((HttpServletResponse) getResponse()).sendRedirect(location);
+
+    }
+
+
+    /**
+     * Disallow <code>setDateHeader()</code> calls on an included response.
+     *
+     * @param name The new header name
+     * @param value The new header value
+     */
+    @Override
+    public void setDateHeader(String name, long value) {
+
+        if (!included)
+            ((HttpServletResponse) getResponse()).setDateHeader(name, value);
+
+    }
+
+
+    /**
+     * Disallow <code>setHeader()</code> calls on an included response.
+     *
+     * @param name The new header name
+     * @param value The new header value
+     */
+    @Override
+    public void setHeader(String name, String value) {
+
+        if (!included)
+            ((HttpServletResponse) getResponse()).setHeader(name, value);
+
+    }
+
+
+    /**
+     * Disallow <code>setIntHeader()</code> calls on an included response.
+     *
+     * @param name The new header name
+     * @param value The new header value
+     */
+    @Override
+    public void setIntHeader(String name, int value) {
+
+        if (!included)
+            ((HttpServletResponse) getResponse()).setIntHeader(name, value);
+
+    }
+
+
+    /**
+     * Disallow <code>setStatus()</code> calls on an included response.
+     *
+     * @param sc The new status code
+     */
+    @Override
+    public void setStatus(int sc) {
+
+        if (!included)
+            ((HttpServletResponse) getResponse()).setStatus(sc);
+
+    }
+
+
+    /**
+     * Disallow <code>setStatus()</code> calls on an included response.
+     *
+     * @param sc The new status code
+     * @param msg The new message
+     * @deprecated
+     */
+    @Deprecated
+    @Override
+    public void setStatus(int sc, String msg) {
+
+        if (!included)
+            ((HttpServletResponse) getResponse()).setStatus(sc, msg);
+
+    }
+
+
+    // -------------------------------------------------------- Package Methods
+
+
+    /**
+     * Return descriptive information about this implementation.
+     */
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    /**
+     * Return the included flag for this response.
+     */
+    boolean isIncluded() {
+
+        return (this.included);
+
+    }
+
+
+    /**
+     * Set the included flag for this response.
+     *
+     * @param included The new included flag
+     */
+    void setIncluded(boolean included) {
+
+        this.included = included;
+
+    }
+
+
+    /**
+     * Set the response that we are wrapping.
+     *
+     * @param response The new wrapped response
+     */
+    void setResponse(HttpServletResponse response) {
+
+        super.setResponse(response);
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationJspConfigDescriptor.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationJspConfigDescriptor.java
new file mode 100644
index 0000000..067ad3b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationJspConfigDescriptor.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.core;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import javax.servlet.descriptor.JspConfigDescriptor;
+import javax.servlet.descriptor.JspPropertyGroupDescriptor;
+import javax.servlet.descriptor.TaglibDescriptor;
+
+public class ApplicationJspConfigDescriptor implements JspConfigDescriptor {
+
+    private Collection<JspPropertyGroupDescriptor> jspPropertyGroups =
+        new HashSet<JspPropertyGroupDescriptor>();
+
+    private Collection<TaglibDescriptor> taglibs =
+        new HashSet<TaglibDescriptor>();
+
+    @Override
+    public Collection<JspPropertyGroupDescriptor> getJspPropertyGroups() {
+        return jspPropertyGroups;
+    }
+
+    @Override
+    public Collection<TaglibDescriptor> getTaglibs() {
+        return taglibs;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationJspPropertyGroupDescriptor.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationJspPropertyGroupDescriptor.java
new file mode 100644
index 0000000..f3671eb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationJspPropertyGroupDescriptor.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.catalina.core;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import javax.servlet.descriptor.JspPropertyGroupDescriptor;
+
+import org.apache.catalina.deploy.JspPropertyGroup;
+
+
+public class ApplicationJspPropertyGroupDescriptor
+        implements JspPropertyGroupDescriptor{
+
+    JspPropertyGroup jspPropertyGroup;
+
+    
+    public ApplicationJspPropertyGroupDescriptor(
+            JspPropertyGroup jspPropertyGroup) {
+        this.jspPropertyGroup = jspPropertyGroup;
+    }
+
+    
+    @Override
+    public String getBuffer() {
+        String result = null;
+        
+        if (jspPropertyGroup.getBuffer() != null) {
+            result = jspPropertyGroup.getBuffer().toString();
+        }
+        
+        return result;
+    }
+
+    
+    @Override
+    public String getDefaultContentType() {
+        String result = null;
+        
+        if (jspPropertyGroup.getDefaultContentType() != null) {
+            result = jspPropertyGroup.getDefaultContentType().toString();
+        }
+        
+        return result;
+    }
+
+    
+    @Override
+    public String getDeferredSyntaxAllowedAsLiteral() {
+        String result = null;
+        
+        if (jspPropertyGroup.getDeferredSyntax() != null) {
+            result = jspPropertyGroup.getDeferredSyntax().toString();
+        }
+        
+        return result;
+    }
+
+    
+    @Override
+    public String getElIgnored() {
+        String result = null;
+        
+        if (jspPropertyGroup.getElIgnored() != null) {
+            result = jspPropertyGroup.getElIgnored().toString();
+        }
+        
+        return result;
+    }
+
+    
+    @Override
+    public String getErrorOnUndeclaredNamespace() {
+        String result = null;
+        
+        if (jspPropertyGroup.getErrorOnUndeclaredNamespace() != null) {
+            result =
+                jspPropertyGroup.getErrorOnUndeclaredNamespace().toString();
+        }
+        
+        return result;
+    }
+
+    
+    @Override
+    public Collection<String> getIncludeCodas() {
+        return jspPropertyGroup.getIncludeCodas();
+    }
+
+    
+    @Override
+    public Collection<String> getIncludePreludes() {
+        return jspPropertyGroup.getIncludePreludes();
+    }
+
+    
+    @Override
+    public String getIsXml() {
+        String result = null;
+        
+        if (jspPropertyGroup.getIsXml() != null) {
+            result = jspPropertyGroup.getIsXml().toString();
+        }
+        
+        return result;
+    }
+
+    
+    @Override
+    public String getPageEncoding() {
+        String result = null;
+        
+        if (jspPropertyGroup.getPageEncoding() != null) {
+            result = jspPropertyGroup.getPageEncoding().toString();
+        }
+        
+        return result;
+    }
+
+    
+    @Override
+    public String getScriptingInvalid() {
+        String result = null;
+        
+        if (jspPropertyGroup.getScriptingInvalid() != null) {
+            result = jspPropertyGroup.getScriptingInvalid().toString();
+        }
+        
+        return result;
+    }
+
+    
+    @Override
+    public String getTrimDirectiveWhitespaces() {
+        String result = null;
+        
+        if (jspPropertyGroup.getTrimWhitespace() != null) {
+            result = jspPropertyGroup.getTrimWhitespace().toString();
+        }
+        
+        return result;
+    }
+
+    
+    @Override
+    public Collection<String> getUrlPatterns() {
+        Collection<String> result = new HashSet<String>();
+        
+        if (jspPropertyGroup.getUrlPattern() != null) {
+            result.add(jspPropertyGroup.getUrlPattern());
+        }
+        
+        return result;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationPart.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationPart.java
new file mode 100644
index 0000000..ac59775
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationPart.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.http.Part;
+
+import org.apache.tomcat.util.http.fileupload.FileItem;
+import org.apache.tomcat.util.http.fileupload.ParameterParser;
+import org.apache.tomcat.util.http.fileupload.disk.DiskFileItem;
+
+/**
+ * Adaptor to allow {@link FileItem} objects generated by the package renamed
+ * commons-upload to be used by the Servlet 3.0 upload API that expects
+ * {@link Part}s.
+ */
+public class ApplicationPart implements Part {
+
+    private FileItem fileItem;
+    private MultipartConfigElement mce;
+
+    public ApplicationPart(FileItem fileItem, MultipartConfigElement mce) {
+        this.fileItem = fileItem;
+        this.mce = mce;
+    }
+
+    @Override
+    public void delete() throws IOException {
+        fileItem.delete();
+    }
+
+    @Override
+    public String getContentType() {
+        return fileItem.getContentType();
+    }
+
+    @Override
+    public String getHeader(String name) {
+        if (fileItem instanceof DiskFileItem) {
+            return ((DiskFileItem) fileItem).getHeaders().getHeader(name);
+        }
+        return null;
+    }
+
+    @Override
+    public Collection<String> getHeaderNames() {
+        if (fileItem instanceof DiskFileItem) {
+            HashSet<String> headerNames = new HashSet<String>();
+            Iterator<String> iter =
+                ((DiskFileItem) fileItem).getHeaders().getHeaderNames();
+            while (iter.hasNext()) {
+                headerNames.add(iter.next());
+            }
+            return headerNames;
+        }
+        return Collections.emptyList();
+    }
+
+    @Override
+    public Collection<String> getHeaders(String name) {
+        if (fileItem instanceof DiskFileItem) {
+            HashSet<String> headers = new HashSet<String>();
+            Iterator<String> iter =
+                ((DiskFileItem) fileItem).getHeaders().getHeaders(name);
+            while (iter.hasNext()) {
+                headers.add(iter.next());
+            }
+            return headers;
+        }
+        return Collections.emptyList();
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException {
+        return fileItem.getInputStream();
+    }
+
+    @Override
+    public String getName() {
+        return fileItem.getFieldName();
+    }
+
+    @Override
+    public long getSize() {
+        return fileItem.getSize();
+    }
+
+    @Override
+    public void write(String fileName) throws IOException {
+        File file = new File(fileName);
+        if (!file.isAbsolute()) {
+            file = new File(mce.getLocation(), fileName);
+        }
+        try {
+            fileItem.write(file);
+        } catch (Exception e) {
+            throw new IOException(e);
+        }
+    }
+
+    public String getString(String encoding) throws UnsupportedEncodingException {
+        return fileItem.getString(encoding);
+    }
+
+    /*
+     * Adapted from FileUploadBase.getFileName()
+     */
+    public String getFilename() {
+        String fileName = null;
+        String cd = getHeader("Content-Disposition");
+        if (cd != null) {
+            String cdl = cd.toLowerCase(Locale.ENGLISH);
+            if (cdl.startsWith("form-data") || cdl.startsWith("attachment")) {
+                ParameterParser paramParser = new ParameterParser();
+                paramParser.setLowerCaseNames(true);
+                // Parameter parser can handle null input
+                Map<String,String> params =
+                    paramParser.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;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationRequest.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationRequest.java
new file mode 100644
index 0000000..98d46bf
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationRequest.java
@@ -0,0 +1,213 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.core;
+
+
+import java.util.Enumeration;
+import java.util.HashMap;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestWrapper;
+
+import org.apache.catalina.util.Enumerator;
+
+
+/**
+ * Wrapper around a <code>javax.servlet.ServletRequest</code>
+ * that transforms an application request object (which might be the original
+ * one passed to a servlet, or might be based on the 2.3
+ * <code>javax.servlet.ServletRequestWrapper</code> class)
+ * back into an internal <code>org.apache.catalina.Request</code>.
+ * <p>
+ * <strong>WARNING</strong>:  Due to Java's lack of support for multiple
+ * inheritance, all of the logic in <code>ApplicationRequest</code> is
+ * duplicated in <code>ApplicationHttpRequest</code>.  Make sure that you
+ * keep these two classes in synchronization when making changes!
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: ApplicationRequest.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+class ApplicationRequest extends ServletRequestWrapper {
+
+
+    // ------------------------------------------------------- Static Variables
+
+
+    /**
+     * The set of attribute names that are special for request dispatchers.
+     */
+    protected static final String specials[] =
+    { RequestDispatcher.INCLUDE_REQUEST_URI,
+      RequestDispatcher.INCLUDE_CONTEXT_PATH,
+      RequestDispatcher.INCLUDE_SERVLET_PATH,
+      RequestDispatcher.INCLUDE_PATH_INFO,
+      RequestDispatcher.INCLUDE_QUERY_STRING,
+      RequestDispatcher.FORWARD_REQUEST_URI, 
+      RequestDispatcher.FORWARD_CONTEXT_PATH,
+      RequestDispatcher.FORWARD_SERVLET_PATH, 
+      RequestDispatcher.FORWARD_PATH_INFO,
+      RequestDispatcher.FORWARD_QUERY_STRING };
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new wrapped request around the specified servlet request.
+     *
+     * @param request The servlet request being wrapped
+     */
+    public ApplicationRequest(ServletRequest request) {
+
+        super(request);
+        setRequest(request);
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The request attributes for this request.  This is initialized from the
+     * wrapped request, but updates are allowed.
+     */
+    protected HashMap<String, Object> attributes =
+        new HashMap<String, Object>();
+
+
+    // ------------------------------------------------- ServletRequest Methods
+
+
+    /**
+     * Override the <code>getAttribute()</code> method of the wrapped request.
+     *
+     * @param name Name of the attribute to retrieve
+     */
+    @Override
+    public Object getAttribute(String name) {
+
+        synchronized (attributes) {
+            return (attributes.get(name));
+        }
+
+    }
+
+
+    /**
+     * Override the <code>getAttributeNames()</code> method of the wrapped
+     * request.
+     */
+    @Override
+    public Enumeration<String> getAttributeNames() {
+
+        synchronized (attributes) {
+            return (new Enumerator<String>(attributes.keySet()));
+        }
+
+    }
+
+
+    /**
+     * Override the <code>removeAttribute()</code> method of the
+     * wrapped request.
+     *
+     * @param name Name of the attribute to remove
+     */
+    @Override
+    public void removeAttribute(String name) {
+
+        synchronized (attributes) {
+            attributes.remove(name);
+            if (!isSpecial(name))
+                getRequest().removeAttribute(name);
+        }
+
+    }
+
+
+    /**
+     * Override the <code>setAttribute()</code> method of the
+     * wrapped request.
+     *
+     * @param name Name of the attribute to set
+     * @param value Value of the attribute to set
+     */
+    @Override
+    public void setAttribute(String name, Object value) {
+
+        synchronized (attributes) {
+            attributes.put(name, value);
+            if (!isSpecial(name))
+                getRequest().setAttribute(name, value);
+        }
+
+    }
+
+
+    // ------------------------------------------ ServletRequestWrapper Methods
+
+
+    /**
+     * Set the request that we are wrapping.
+     *
+     * @param request The new wrapped request
+     */
+    @Override
+    public void setRequest(ServletRequest request) {
+
+        super.setRequest(request);
+
+        // Initialize the attributes for this request
+        synchronized (attributes) {
+            attributes.clear();
+            Enumeration<String> names = request.getAttributeNames();
+            while (names.hasMoreElements()) {
+                String name = names.nextElement();
+                Object value = request.getAttribute(name);
+                attributes.put(name, value);
+            }
+        }
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Is this attribute name one of the special ones that is added only for
+     * included servlets?
+     *
+     * @param name Attribute name to be tested
+     */
+    protected boolean isSpecial(String name) {
+
+        for (int i = 0; i < specials.length; i++) {
+            if (specials[i].equals(name))
+                return (true);
+        }
+        return (false);
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationResponse.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationResponse.java
new file mode 100644
index 0000000..cbe1120
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationResponse.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import java.util.Locale;
+
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletResponseWrapper;
+
+
+/**
+ * Wrapper around a <code>javax.servlet.ServletResponse</code>
+ * that transforms an application response object (which might be the original
+ * one passed to a servlet, or might be based on the 2.3
+ * <code>javax.servlet.ServletResponseWrapper</code> class)
+ * back into an internal <code>org.apache.catalina.Response</code>.
+ * <p>
+ * <strong>WARNING</strong>:  Due to Java's lack of support for multiple
+ * inheritance, all of the logic in <code>ApplicationResponse</code> is
+ * duplicated in <code>ApplicationHttpResponse</code>.  Make sure that you
+ * keep these two classes in synchronization when making changes!
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: ApplicationResponse.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+class ApplicationResponse extends ServletResponseWrapper {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new wrapped response around the specified servlet response.
+     *
+     * @param response The servlet response being wrapped
+     */
+    public ApplicationResponse(ServletResponse response) {
+
+        this(response, false);
+
+    }
+
+
+    /**
+     * Construct a new wrapped response around the specified servlet response.
+     *
+     * @param response The servlet response being wrapped
+     * @param included <code>true</code> if this response is being processed
+     *  by a <code>RequestDispatcher.include()</code> call
+     */
+    public ApplicationResponse(ServletResponse response, boolean included) {
+
+        super(response);
+        setIncluded(included);
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Is this wrapped response the subject of an <code>include()</code>
+     * call?
+     */
+    protected boolean included = false;
+
+
+    // ------------------------------------------------ ServletResponse Methods
+
+
+    /**
+     * Disallow <code>reset()</code> calls on a included response.
+     *
+     * @exception IllegalStateException if the response has already
+     *  been committed
+     */
+    @Override
+    public void reset() {
+
+        // If already committed, the wrapped response will throw ISE
+        if (!included || getResponse().isCommitted())
+            getResponse().reset();
+
+    }
+
+
+    /**
+     * Disallow <code>setContentLength()</code> calls on an included response.
+     *
+     * @param len The new content length
+     */
+    @Override
+    public void setContentLength(int len) {
+
+        if (!included)
+            getResponse().setContentLength(len);
+
+    }
+
+
+    /**
+     * Disallow <code>setContentType()</code> calls on an included response.
+     *
+     * @param type The new content type
+     */
+    @Override
+    public void setContentType(String type) {
+
+        if (!included)
+            getResponse().setContentType(type);
+
+    }
+
+
+    /**
+     * Ignore <code>setLocale()</code> calls on an included response.
+     *
+     * @param loc The new locale
+     */
+    @Override
+    public void setLocale(Locale loc) {
+        if (!included)
+            getResponse().setLocale(loc);
+    }
+
+
+    /**
+     * Ignore <code>setBufferSize()</code> calls on an included response.
+     *
+     * @param size The buffer size
+     */
+    @Override
+    public void setBufferSize(int size) {
+        if (!included)
+            getResponse().setBufferSize(size);
+    }
+
+
+    // ----------------------------------------- ServletResponseWrapper Methods
+
+
+    /**
+     * Set the response that we are wrapping.
+     *
+     * @param response The new wrapped response
+     */
+    @Override
+    public void setResponse(ServletResponse response) {
+
+        super.setResponse(response);
+
+    }
+
+
+    // -------------------------------------------------------- Package Methods
+
+
+    /**
+     * Return the included flag for this response.
+     */
+    boolean isIncluded() {
+
+        return (this.included);
+
+    }
+
+
+    /**
+     * Set the included flag for this response.
+     *
+     * @param included The new included flag
+     */
+    void setIncluded(boolean included) {
+
+        this.included = included;
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationServletRegistration.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationServletRegistration.java
new file mode 100644
index 0000000..c4bb9fd
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationServletRegistration.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.core;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletSecurityElement;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.util.ParameterMap;
+import org.apache.tomcat.util.res.StringManager;
+
+public class ApplicationServletRegistration
+        implements ServletRegistration.Dynamic {
+
+    /**
+     * The string manager for this package.
+     */
+    private static final StringManager sm =
+      StringManager.getManager(Constants.Package);
+    
+    private Wrapper wrapper;
+    private Context context;
+    
+    public ApplicationServletRegistration(Wrapper wrapper,
+            Context context) {
+        this.wrapper = wrapper;
+        this.context = context;
+        
+    }
+
+    @Override
+    public String getClassName() {
+        return wrapper.getServletClass();
+   }
+
+    @Override
+    public String getInitParameter(String name) {
+        return wrapper.findInitParameter(name);
+    }
+
+    @Override
+    public Map<String, String> getInitParameters() {
+        ParameterMap<String,String> result = new ParameterMap<String,String>();
+        
+        String[] parameterNames = wrapper.findInitParameters();
+        
+        for (String parameterName : parameterNames) {
+            result.put(parameterName, wrapper.findInitParameter(parameterName));
+        }
+
+        result.setLocked(true);
+        return result;
+    }
+
+    @Override
+    public String getName() {
+        return wrapper.getName();
+    }
+
+    @Override
+    public boolean setInitParameter(String name, String value) {
+        if (name == null || value == null) {
+            throw new IllegalArgumentException(
+                    sm.getString("applicationFilterRegistration.nullInitParam",
+                            name, value));
+        }
+        if (getInitParameter(name) != null) {
+            return false;
+        }
+        
+        wrapper.addInitParameter(name, value);
+
+        return true;
+    }
+
+    @Override
+    public Set<String> setInitParameters(Map<String, String> initParameters) {
+        
+        Set<String> conflicts = new HashSet<String>();
+        
+        for (Map.Entry<String, String> entry : initParameters.entrySet()) {
+            if (entry.getKey() == null || entry.getValue() == null) {
+                throw new IllegalArgumentException(sm.getString(
+                        "applicationFilterRegistration.nullInitParams",
+                                entry.getKey(), entry.getValue()));
+            }
+            if (getInitParameter(entry.getKey()) != null) {
+                conflicts.add(entry.getKey());
+            }
+        }
+
+        // Have to add in a separate loop since spec requires no updates at all
+        // if there is an issue
+        if (conflicts.isEmpty()) {
+            for (Map.Entry<String, String> entry : initParameters.entrySet()) {
+                setInitParameter(entry.getKey(), entry.getValue());
+            }
+        }
+
+        return conflicts;
+    }
+
+    @Override
+    public void setAsyncSupported(boolean asyncSupported) {
+        wrapper.setAsyncSupported(asyncSupported);
+    }
+
+    @Override
+    public void setLoadOnStartup(int loadOnStartup) {
+        wrapper.setLoadOnStartup(loadOnStartup);
+    }
+
+    @Override
+    public void setMultipartConfig(MultipartConfigElement multipartConfig) {
+        wrapper.setMultipartConfigElement(multipartConfig);
+    }
+
+    @Override
+    public void setRunAsRole(String roleName) {
+        wrapper.setRunAs(roleName);
+    }
+
+    @Override
+    public Set<String> setServletSecurity(ServletSecurityElement constraint) {
+        if (constraint == null) {
+            throw new IllegalArgumentException(sm.getString(
+                    "applicationServletRegistration.setServletSecurity.iae",
+                    getName(), context.getName()));
+        }
+        
+        if (!context.getState().equals(LifecycleState.STARTING_PREP)) {
+            throw new IllegalStateException(sm.getString(
+                    "applicationServletRegistration.setServletSecurity.ise",
+                    getName(), context.getName()));
+        }
+
+        return context.addServletSecurity(this, constraint);
+    }
+
+
+    @Override
+    public Set<String> addMapping(String... urlPatterns) {
+        if (urlPatterns == null) {
+            return Collections.emptySet();
+        }
+        
+        Set<String> conflicts = new HashSet<String>();
+        
+        for (String urlPattern : urlPatterns) {
+            if (context.findServletMapping(urlPattern) != null) {
+                conflicts.add(urlPattern);
+            }
+        }
+
+        if (!conflicts.isEmpty()) {
+            return conflicts;
+        }
+        
+        for (String urlPattern : urlPatterns) {
+            context.addServletMapping(urlPattern, wrapper.getName());
+        }
+        return Collections.emptySet();
+    }
+
+    @Override
+    public Collection<String> getMappings() {
+
+        Set<String> result = new HashSet<String>();
+        String servletName = wrapper.getName();
+        
+        String[] urlPatterns = context.findServletMappings();
+        for (String urlPattern : urlPatterns) {
+            String name = context.findServletMapping(urlPattern);
+            if (name.equals(servletName)) {
+                result.add(urlPattern);
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public String getRunAsRole() {
+        return wrapper.getRunAs();
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationSessionCookieConfig.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationSessionCookieConfig.java
new file mode 100644
index 0000000..b8d32bb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationSessionCookieConfig.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.catalina.core;
+
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.http.Cookie;
+
+import org.apache.catalina.Context;
+
+public class ApplicationSessionCookieConfig implements SessionCookieConfig {
+
+    private static final String DEFAULT_SESSION_COOKIE_NAME = "JSESSIONID";
+    private static final String DEFAULT_SESSION_PARAMETER_NAME = "jsessionid";
+    
+    private boolean httpOnly;
+    private boolean secure;
+    private int maxAge = -1;
+    private String comment;
+    private String domain;
+    private String name;
+    private String path;
+    
+    @Override
+    public String getComment() {
+        return comment;
+    }
+
+    @Override
+    public String getDomain() {
+        return domain;
+    }
+
+    @Override
+    public int getMaxAge() {
+        return maxAge;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String getPath() {
+        return path;
+    }
+
+    @Override
+    public boolean isHttpOnly() {
+        return httpOnly;
+    }
+
+    @Override
+    public boolean isSecure() {
+        return secure;
+    }
+
+    @Override
+    public void setComment(String comment) {
+        this.comment = comment;
+    }
+
+    @Override
+    public void setDomain(String domain) {
+        this.domain = domain;
+    }
+
+    @Override
+    public void setHttpOnly(boolean httpOnly) {
+        this.httpOnly = httpOnly;
+    }
+
+    @Override
+    public void setMaxAge(int maxAge) {
+        this.maxAge = maxAge;
+    }
+
+    @Override
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public void setPath(String path) {
+        this.path = path;
+    }
+
+    @Override
+    public void setSecure(boolean secure) {
+        this.secure = secure;
+    }
+
+    /**
+     * Creates a new session cookie for the given session ID
+     *
+     * @param context     The Context for the web application
+     * @param sessionId   The ID of the session for which the cookie will be
+     *                    created
+     * @param secure      Should session cookie be configured as secure
+     */
+    public static Cookie createSessionCookie(Context context,
+            String sessionId, boolean secure) {
+
+        SessionCookieConfig scc =
+            context.getServletContext().getSessionCookieConfig();
+
+        // NOTE: The priority order for session cookie configuration is:
+        //       1. Context level configuration
+        //       2. Values from SessionCookieConfig
+        //       3. Defaults
+
+        Cookie cookie = new Cookie(getSessionCookieName(context), sessionId);
+       
+        // Just apply the defaults.
+        cookie.setMaxAge(scc.getMaxAge());
+        cookie.setComment(scc.getComment());
+       
+        if (context.getSessionCookieDomain() == null) {
+            // Avoid possible NPE
+            if (scc.getDomain() != null) {
+                cookie.setDomain(scc.getDomain());
+            }
+        } else {
+            cookie.setDomain(context.getSessionCookieDomain());
+        }
+
+        // Always set secure if the request is secure
+        if (scc.isSecure() || secure) {
+            cookie.setSecure(true);
+        }
+
+        // Always set httpOnly if the context is configured for that
+        if (scc.isHttpOnly() || context.getUseHttpOnly()) {
+            cookie.setHttpOnly(true);
+        }
+       
+        String contextPath = context.getSessionCookiePath();
+        if (contextPath == null || contextPath.length() == 0) {
+            contextPath = scc.getPath();
+        }
+        if (contextPath == null || contextPath.length() == 0) {
+            contextPath = context.getEncodedPath();
+        }
+        // Handle special case of ROOT context where cookies require a path of
+        // '/' but the servlet spec uses an empty string
+        if (contextPath.length() == 0) {
+            contextPath = "/";
+        }
+        cookie.setPath(contextPath);
+
+        return cookie;
+    }
+    
+    
+    private static String getConfiguredSessionCookieName(Context context) {
+        
+        // Priority is:
+        // 1. Cookie name defined in context
+        // 2. Cookie name configured for app
+        // 3. Default defined by spec
+        if (context != null) {
+            String cookieName = context.getSessionCookieName();
+            if (cookieName != null && cookieName.length() > 0) {
+                return cookieName;
+            }
+            
+            SessionCookieConfig scc =
+                context.getServletContext().getSessionCookieConfig();
+            cookieName = scc.getName();
+            if (cookieName != null && cookieName.length() > 0) {
+                return cookieName;
+            }
+        }
+
+        return null;
+    }
+    
+    
+    /**
+     * Determine the name to use for the session cookie for the provided
+     * context.
+     * @param context
+     */
+    public static String getSessionCookieName(Context context) {
+    
+        String result = getConfiguredSessionCookieName(context);
+        
+        if (result == null) {
+            result = DEFAULT_SESSION_COOKIE_NAME; 
+        }
+        
+        return result; 
+    }
+    
+    /**
+     * Determine the name to use for the session cookie for the provided
+     * context.
+     * @param context
+     */
+    public static String getSessionUriParamName(Context context) {
+        
+        String result = getConfiguredSessionCookieName(context);
+        
+        if (result == null) {
+            result = DEFAULT_SESSION_PARAMETER_NAME; 
+        }
+        
+        return result; 
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationTaglibDescriptor.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationTaglibDescriptor.java
new file mode 100644
index 0000000..bc5f681
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ApplicationTaglibDescriptor.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.core;
+
+import javax.servlet.descriptor.TaglibDescriptor;
+
+public class ApplicationTaglibDescriptor implements TaglibDescriptor {
+
+    private String location;
+    private String uri;
+    
+    public ApplicationTaglibDescriptor(String location, String uri) {
+        this.location = location;
+        this.uri = uri;
+    }
+
+    @Override
+    public String getTaglibLocation() {
+        return location;
+    }
+
+    @Override
+    public String getTaglibURI() {
+        return uri;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((location == null) ? 0 : location.hashCode());
+        result = prime * result + ((uri == null) ? 0 : uri.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof ApplicationTaglibDescriptor)) {
+            return false;
+        }
+        ApplicationTaglibDescriptor other = (ApplicationTaglibDescriptor) obj;
+        if (location == null) {
+            if (other.location != null) {
+                return false;
+            }
+        } else if (!location.equals(other.location)) {
+            return false;
+        }
+        if (uri == null) {
+            if (other.uri != null) {
+                return false;
+            }
+        } else if (!uri.equals(other.uri)) {
+            return false;
+        }
+        return true;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/AprLifecycleListener.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/AprLifecycleListener.java
new file mode 100644
index 0000000..5cce74b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/AprLifecycleListener.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.catalina.core;
+
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+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.jni.Library;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.res.StringManager;
+
+
+
+/**
+ * Implementation of <code>LifecycleListener</code> that will init and
+ * and destroy APR.
+ *
+ * @author Remy Maucherat
+ * @author Filip Hanik
+ * @version $Id: AprLifecycleListener.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ * @since 4.1
+ */
+
+public class AprLifecycleListener
+    implements LifecycleListener {
+
+    private static final Log log = LogFactory.getLog(AprLifecycleListener.class);
+    private static boolean instanceCreated = false;
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    // ---------------------------------------------- Constants
+
+
+    protected static final int TCN_REQUIRED_MAJOR = 1;
+    protected static final int TCN_REQUIRED_MINOR = 1;
+    protected static final int TCN_REQUIRED_PATCH = 17;
+    protected static final int TCN_RECOMMENDED_MINOR = 1;
+    protected static final int TCN_RECOMMENDED_PV = 20;
+
+
+    // ---------------------------------------------- Properties
+    protected static String SSLEngine = "on"; //default on
+    protected static String SSLRandomSeed = "builtin";
+    protected static boolean sslInitialized = false;
+    protected static boolean aprInitialized = false;
+    protected static boolean sslAvailable = false;
+    protected static boolean aprAvailable = false;
+
+    protected static final Object lock = new Object();
+
+    public static boolean isAprAvailable() {
+        //https://issues.apache.org/bugzilla/show_bug.cgi?id=48613
+        if (instanceCreated) {
+            synchronized (lock) {
+                init();
+            }
+        }
+        return aprAvailable;
+    }
+
+    public AprLifecycleListener() {
+        instanceCreated = true;
+    }
+
+    // ---------------------------------------------- LifecycleListener Methods
+
+    /**
+     * Primary entry point for startup and shutdown events.
+     *
+     * @param event The event that has occurred
+     */
+    @Override
+    public void lifecycleEvent(LifecycleEvent event) {
+
+        if (Lifecycle.BEFORE_INIT_EVENT.equals(event.getType())) {
+            synchronized (lock) {
+                init();
+                if (aprAvailable) {
+                    try {
+                        initializeSSL();
+                    } catch (Throwable t) {
+                        ExceptionUtils.handleThrowable(t);
+                        log.info(sm.getString("aprListener.sslInit"));
+                    }
+                }
+            }
+        } else if (Lifecycle.AFTER_DESTROY_EVENT.equals(event.getType())) {
+            synchronized (lock) {
+                if (!aprAvailable) {
+                    return;
+                }
+                try {
+                    terminateAPR();
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    log.info(sm.getString("aprListener.aprDestroy"));
+                }
+            }
+        }
+
+    }
+
+    private static void terminateAPR()
+        throws ClassNotFoundException, NoSuchMethodException,
+               IllegalAccessException, InvocationTargetException
+    {
+        String methodName = "terminate";
+        Method method = Class.forName("org.apache.tomcat.jni.Library")
+            .getMethod(methodName, (Class [])null);
+        method.invoke(null, (Object []) null);
+        aprAvailable = false;
+        aprInitialized = false;
+        sslInitialized = false; // Well we cleaned the pool in terminate.
+    }
+
+    private static void init()
+    {
+        int major = 0;
+        int minor = 0;
+        int patch = 0;
+        int apver = 0;
+        int rqver = TCN_REQUIRED_MAJOR * 1000 + TCN_REQUIRED_MINOR * 100 + TCN_REQUIRED_PATCH;
+        int rcver = TCN_REQUIRED_MAJOR * 1000 + TCN_REQUIRED_MINOR * 100 + TCN_RECOMMENDED_PV;
+
+        if (aprInitialized) {
+            return;
+        }
+        aprInitialized = true;
+
+        try {
+            String methodName = "initialize";
+            Class<?> paramTypes[] = new Class[1];
+            paramTypes[0] = String.class;
+            Object paramValues[] = new Object[1];
+            paramValues[0] = null;
+            Class<?> clazz = Class.forName("org.apache.tomcat.jni.Library");
+            Method method = clazz.getMethod(methodName, paramTypes);
+            method.invoke(null, paramValues);
+            major = clazz.getField("TCN_MAJOR_VERSION").getInt(null);
+            minor = clazz.getField("TCN_MINOR_VERSION").getInt(null);
+            patch = clazz.getField("TCN_PATCH_VERSION").getInt(null);
+            apver = major * 1000 + minor * 100 + patch;
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.info(sm.getString("aprListener.aprInit",
+                    System.getProperty("java.library.path")));
+            return;
+        }
+        if (apver < rqver) {
+            log.error(sm.getString("aprListener.tcnInvalid", major + "."
+                    + minor + "." + patch,
+                    TCN_REQUIRED_MAJOR + "." +
+                    TCN_REQUIRED_MINOR + "." +
+                    TCN_REQUIRED_PATCH));
+            try {
+                // Terminate the APR in case the version
+                // is below required.
+                terminateAPR();
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+            }
+            return;
+        }
+        if (apver < rcver) {
+            log.info(sm.getString("aprListener.tcnVersion", major + "."
+                    + minor + "." + patch,
+                    TCN_REQUIRED_MAJOR + "." +
+                    TCN_RECOMMENDED_MINOR + "." +
+                    TCN_RECOMMENDED_PV));
+        }
+
+        log.info(sm.getString("aprListener.tcnValid", major + "."
+                    + minor + "." + patch));
+
+        // Log APR flags
+        log.info(sm.getString("aprListener.flags",
+                Boolean.valueOf(Library.APR_HAVE_IPV6),
+                Boolean.valueOf(Library.APR_HAS_SENDFILE),
+                Boolean.valueOf(Library.APR_HAS_SO_ACCEPTFILTER),
+                Boolean.valueOf(Library.APR_HAS_RANDOM)));
+        aprAvailable = true;
+    }
+
+    private static void initializeSSL()
+        throws ClassNotFoundException, NoSuchMethodException,
+               IllegalAccessException, InvocationTargetException
+    {
+
+        if ("off".equalsIgnoreCase(SSLEngine)) {
+            return;
+        }
+        if (sslInitialized) {
+             //only once per VM
+            return;
+        }
+        sslInitialized = true;
+
+        String methodName = "randSet";
+        Class<?> paramTypes[] = new Class[1];
+        paramTypes[0] = String.class;
+        Object paramValues[] = new Object[1];
+        paramValues[0] = SSLRandomSeed;
+        Class<?> clazz = Class.forName("org.apache.tomcat.jni.SSL");
+        Method method = clazz.getMethod(methodName, paramTypes);
+        method.invoke(null, paramValues);
+
+
+        methodName = "initialize";
+        paramValues[0] = "on".equalsIgnoreCase(SSLEngine)?null:SSLEngine;
+        method = clazz.getMethod(methodName, paramTypes);
+        method.invoke(null, paramValues);
+
+        sslAvailable = true;
+    }
+
+    public String getSSLEngine() {
+        return SSLEngine;
+    }
+
+    public void setSSLEngine(String SSLEngine) {
+        AprLifecycleListener.SSLEngine = SSLEngine;
+    }
+
+    public String getSSLRandomSeed() {
+        return SSLRandomSeed;
+    }
+
+    public void setSSLRandomSeed(String SSLRandomSeed) {
+        AprLifecycleListener.SSLRandomSeed = SSLRandomSeed;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/AsyncContextImpl.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/AsyncContextImpl.java
new file mode 100644
index 0000000..807b7f0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/AsyncContextImpl.java
@@ -0,0 +1,480 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.naming.NamingException;
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+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 org.apache.catalina.Context;
+import org.apache.catalina.Globals;
+import org.apache.catalina.connector.Request;
+import org.apache.coyote.ActionCode;
+import org.apache.coyote.AsyncContextCallback;
+import org.apache.coyote.RequestInfo;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.InstanceManager;
+/**
+ * 
+ * @author fhanik
+ *
+ */
+public class AsyncContextImpl implements AsyncContext, AsyncContextCallback {
+    
+    private static final Log log = LogFactory.getLog(AsyncContextImpl.class);
+    
+    private ServletRequest servletRequest = null;
+    private ServletResponse servletResponse = null;
+    private List<AsyncListenerWrapper> listeners = new ArrayList<AsyncListenerWrapper>();
+    private boolean hasOriginalRequestAndResponse = true;
+    private volatile Runnable dispatch = null;
+    private Context context = null;
+    private long timeout = -1;
+    private AsyncEvent event = null;
+    private Request request;
+    private volatile InstanceManager instanceManager;
+    
+    public AsyncContextImpl(Request request) {
+        if (log.isDebugEnabled()) {
+            logDebug("Constructor");
+        }
+        this.request = request;
+    }
+
+    @Override
+    public void complete() {
+        if (log.isDebugEnabled()) {
+            logDebug("complete   ");
+        }
+        request.getCoyoteRequest().action(ActionCode.ASYNC_COMPLETE, null);
+    }
+
+    @Override
+    public void fireOnComplete() {
+        List<AsyncListenerWrapper> listenersCopy =
+            new ArrayList<AsyncListenerWrapper>();
+        listenersCopy.addAll(listeners);
+        for (AsyncListenerWrapper listener : listenersCopy) {
+            try {
+                listener.fireOnComplete(event);
+            } catch (IOException ioe) {
+                log.warn("onComplete() failed for listener of type [" +
+                        listener.getClass().getName() + "]", ioe);
+            }
+        }
+    }
+    
+    public boolean timeout() throws IOException {
+        AtomicBoolean result = new AtomicBoolean();
+        request.getCoyoteRequest().action(ActionCode.ASYNC_TIMEOUT, result);
+        
+        if (result.get()) {
+            boolean listenerInvoked = false;
+            List<AsyncListenerWrapper> listenersCopy =
+                new ArrayList<AsyncListenerWrapper>();
+            listenersCopy.addAll(listeners);
+            for (AsyncListenerWrapper listener : listenersCopy) {
+                listener.fireOnTimeout(event);
+                listenerInvoked = true;
+            }
+            if (listenerInvoked) {
+                request.getCoyoteRequest().action(
+                        ActionCode.ASYNC_IS_TIMINGOUT, result);
+                return !result.get();
+            } else {
+                // No listeners, container calls complete
+                complete();
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void dispatch() {
+        HttpServletRequest sr = (HttpServletRequest)getRequest();
+        String path = sr.getRequestURI();
+        String cpath = sr.getContextPath();
+        if (cpath.length()>1) path = path.substring(cpath.length());
+        dispatch(path);
+    }
+
+    @Override
+    public void dispatch(String path) {
+        dispatch(request.getServletContext(),path);
+    }
+
+    @Override
+    public void dispatch(ServletContext context, String path) {
+        if (log.isDebugEnabled()) {
+            logDebug("dispatch   ");
+        }
+        if (request.getAttribute(ASYNC_REQUEST_URI)==null) {
+            request.setAttribute(ASYNC_REQUEST_URI, request.getRequestURI()+"?"+request.getQueryString());
+            request.setAttribute(ASYNC_CONTEXT_PATH, request.getContextPath());
+            request.setAttribute(ASYNC_SERVLET_PATH, request.getServletPath());
+            request.setAttribute(ASYNC_QUERY_STRING, request.getQueryString());
+        }
+        final RequestDispatcher requestDispatcher = context.getRequestDispatcher(path);
+        final HttpServletRequest servletRequest = (HttpServletRequest)getRequest();
+        final HttpServletResponse servletResponse = (HttpServletResponse)getResponse();
+        Runnable run = new Runnable() {
+            @Override
+            public void run() {
+                request.getCoyoteRequest().action(ActionCode.ASYNC_DISPATCHED, null);
+                DispatcherType type = (DispatcherType)request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);
+                try {
+                    //piggy back on the request dispatcher to ensure that filters etc get called.
+                    //TODO SERVLET3 - async should this be include/forward or a new dispatch type
+                    //javadoc suggests include with the type of DispatcherType.ASYNC
+                    request.setAttribute(Globals.DISPATCHER_TYPE_ATTR, DispatcherType.ASYNC);
+                    requestDispatcher.include(servletRequest, servletResponse);
+                }catch (Exception x) {
+                    //log.error("Async.dispatch",x);
+                    throw new RuntimeException(x);
+                }finally {
+                    request.setAttribute(Globals.DISPATCHER_TYPE_ATTR, type);
+                }
+            }
+        };
+        
+        this.dispatch = run;
+        this.request.getCoyoteRequest().action(ActionCode.ASYNC_DISPATCH, null);
+    }
+
+    @Override
+    public ServletRequest getRequest() {
+        return servletRequest;
+    }
+
+    @Override
+    public ServletResponse getResponse() {
+        return servletResponse;
+    }
+
+    @Override
+    public void start(final Runnable run) {
+        if (log.isDebugEnabled()) {
+            logDebug("start      ");
+        }
+
+        Runnable wrapper = new RunnableWrapper(run, context);
+        this.request.getCoyoteRequest().action(ActionCode.ASYNC_RUN, wrapper);
+    }
+    
+    @Override
+    public void addListener(AsyncListener listener) {
+        AsyncListenerWrapper wrapper = new AsyncListenerWrapper();
+        wrapper.setListener(listener);
+        listeners.add(wrapper);
+    }
+
+    @Override
+    public void addListener(AsyncListener listener, ServletRequest servletRequest,
+            ServletResponse servletResponse) {
+        AsyncListenerWrapper wrapper = new AsyncListenerWrapper();
+        wrapper.setListener(listener);
+        listeners.add(wrapper);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T extends AsyncListener> T createListener(Class<T> clazz)
+            throws ServletException {
+        T listener = null;
+        try {
+             listener = (T) getInstanceManager().newInstance(clazz.getName(),
+                     clazz.getClassLoader());
+        } catch (InstantiationException e) {
+            ServletException se = new ServletException(e);
+            throw se;
+        } catch (IllegalAccessException e) {
+            ServletException se = new ServletException(e);
+            throw se;
+        } catch (InvocationTargetException e) {
+            ServletException se = new ServletException(e);
+            throw se;
+        } catch (NamingException e) {
+            ServletException se = new ServletException(e);
+            throw se;
+        } catch (ClassNotFoundException e) {
+            ServletException se = new ServletException(e);
+            throw se;
+        }
+        return listener;
+    }
+    
+    public void recycle() {
+        if (log.isDebugEnabled()) {
+            logDebug("recycle    ");
+        }
+        servletRequest = null;
+        servletResponse = null;
+        hasOriginalRequestAndResponse = true;
+        context = null;
+        timeout = -1;
+        event = null;
+    }
+
+    public boolean isStarted() {
+        AtomicBoolean result = new AtomicBoolean(false);
+        request.getCoyoteRequest().action(
+                ActionCode.ASYNC_IS_STARTED, result);
+        return result.get();
+    }
+
+    public void setStarted(Context context, ServletRequest request,
+            ServletResponse response, boolean originalRequestResponse) {
+        
+        this.request.getCoyoteRequest().action(
+                ActionCode.ASYNC_START, this);
+
+        this.context = context;
+        this.servletRequest = request;
+        this.servletResponse = response;
+        this.hasOriginalRequestAndResponse = originalRequestResponse;
+        this.event = new AsyncEvent(this, request, response);
+        
+        List<AsyncListenerWrapper> listenersCopy =
+            new ArrayList<AsyncListenerWrapper>();
+        listenersCopy.addAll(listeners);
+        for (AsyncListenerWrapper listener : listenersCopy) {
+            try {
+                listener.fireOnStartAsync(event);
+            } catch (IOException ioe) {
+                log.warn("onStartAsync() failed for listener of type [" +
+                        listener.getClass().getName() + "]", ioe);
+            }
+        }
+        listeners.clear();
+    }
+
+    @Override
+    public boolean hasOriginalRequestAndResponse() {
+        return hasOriginalRequestAndResponse;
+    }
+
+    protected void doInternalDispatch() throws ServletException, IOException {
+        if (log.isDebugEnabled()) {
+            logDebug("intDispatch");
+        }
+        try {
+            dispatch.run();
+        } catch (RuntimeException x) {
+            // doInternalComplete(true);
+            if (x.getCause() instanceof ServletException) {
+                throw (ServletException)x.getCause();
+            }
+            if (x.getCause() instanceof IOException) {
+                throw (IOException)x.getCause();
+            }
+            throw new ServletException(x);
+        }
+    }
+
+    
+    @Override
+    public long getTimeout() {
+        return timeout;
+    }
+
+
+    @Override
+    public void setTimeout(long timeout) {
+        this.timeout = timeout;
+        request.getCoyoteRequest().action(ActionCode.ASYNC_SETTIMEOUT,
+                Long.valueOf(timeout));
+    }
+
+
+    public void setErrorState(Throwable t) {
+        if (t!=null) request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
+        request.getCoyoteRequest().action(ActionCode.ASYNC_ERROR, null);
+        AsyncEvent errorEvent = new AsyncEvent(event.getAsyncContext(),
+                event.getSuppliedRequest(), event.getSuppliedResponse(), t);
+        List<AsyncListenerWrapper> listenersCopy =
+            new ArrayList<AsyncListenerWrapper>();
+        listenersCopy.addAll(listeners);
+        for (AsyncListenerWrapper listener : listenersCopy) {
+            try {
+                listener.fireOnError(errorEvent);
+            } catch (IOException ioe) {
+                log.warn("onStartAsync() failed for listener of type [" +
+                        listener.getClass().getName() + "]", ioe);
+            }
+        }
+    }
+
+    
+    private void logDebug(String method) {
+        String rHashCode;
+        String crHashCode;
+        String rpHashCode;
+        String stage;
+        StringBuilder uri = new StringBuilder();
+        if (request == null) {
+            rHashCode = "null";
+            crHashCode = "null";
+            rpHashCode = "null";
+            stage = "-";
+            uri.append("N/A");
+        } else {
+            rHashCode = Integer.toHexString(request.hashCode());
+            org.apache.coyote.Request coyoteRequest = request.getCoyoteRequest();
+            if (coyoteRequest == null) {
+                crHashCode = "null";
+                rpHashCode = "null";
+                stage = "-";
+            } else {
+                crHashCode = Integer.toHexString(coyoteRequest.hashCode());
+                RequestInfo rp = coyoteRequest.getRequestProcessor();
+                if (rp == null) {
+                    rpHashCode = "null";
+                    stage = "-";
+                } else {
+                    rpHashCode = Integer.toHexString(rp.hashCode());
+                    stage = Integer.toString(rp.getStage());
+                }
+            }
+            uri.append(request.getRequestURI());
+            if (request.getQueryString() != null) {
+                uri.append('?');
+                uri.append(request.getQueryString());
+            }
+        }
+        String threadName = Thread.currentThread().getName();
+        int len = threadName.length();
+        if (len > 20) {
+            threadName = threadName.substring(len - 20, len);
+        }
+        String msg = String.format(
+                "Req: %1$8s  CReq: %2$8s  RP: %3$8s  Stage: %4$s  " +
+                "Thread: %5$20s  State: %6$20s  Method: %7$11s  URI: %8$s",
+                rHashCode, crHashCode, rpHashCode, stage,
+                threadName, "N/A", method, uri);
+        if (log.isTraceEnabled()) {
+            log.trace(msg, new DebugException());
+        } else {
+            log.debug(msg);
+        }
+    }
+
+    private InstanceManager getInstanceManager() {
+        if (instanceManager == null) {
+            if (context instanceof StandardContext) {
+                instanceManager = ((StandardContext)context).getInstanceManager();
+            } else {
+                instanceManager = new DefaultInstanceManager(null,
+                        new HashMap<String, Map<String, String>>(),
+                        context,
+                        getClass().getClassLoader()); 
+            }
+        }
+        return instanceManager;
+    }
+
+    private static class DebugException extends Exception {
+        private static final long serialVersionUID = 1L;
+    }
+    
+    private static class RunnableWrapper implements Runnable {
+
+        private Runnable wrapped = null;
+        private Context context = null;
+        
+        public RunnableWrapper(Runnable wrapped, Context ctxt) {
+            this.wrapped = wrapped;
+            this.context = ctxt;
+        }
+
+        @Override
+        public void run() {
+            ClassLoader oldCL;
+            if (Globals.IS_SECURITY_ENABLED) {
+                PrivilegedAction<ClassLoader> pa = new PrivilegedGetTccl();
+                oldCL = AccessController.doPrivileged(pa);
+            } else {
+                oldCL = Thread.currentThread().getContextClassLoader();
+            }
+            
+            try {
+                if (Globals.IS_SECURITY_ENABLED) {
+                    PrivilegedAction<Void> pa = new PrivilegedSetTccl(
+                            context.getLoader().getClassLoader());
+                    AccessController.doPrivileged(pa);
+                } else {
+                    Thread.currentThread().setContextClassLoader
+                            (context.getLoader().getClassLoader());
+                }                wrapped.run();
+            } finally {
+                if (Globals.IS_SECURITY_ENABLED) {
+                    PrivilegedAction<Void> pa = new PrivilegedSetTccl(
+                            oldCL);
+                    AccessController.doPrivileged(pa);
+                } else {
+                    Thread.currentThread().setContextClassLoader(oldCL);
+                }
+            }
+        }
+        
+    }
+
+
+    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;
+        }
+    }
+
+    private static class PrivilegedGetTccl
+            implements PrivilegedAction<ClassLoader> {
+
+        @Override
+        public ClassLoader run() {
+            return Thread.currentThread().getContextClassLoader();
+        }
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/AsyncListenerWrapper.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/AsyncListenerWrapper.java
new file mode 100644
index 0000000..9c274ed
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/AsyncListenerWrapper.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.core;
+
+import java.io.IOException;
+
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+
+/**
+ * TODO SERVLET3 - async 
+ * @author fhanik
+ *
+ */
+public class AsyncListenerWrapper {
+
+    private AsyncListener listener = null;
+    
+    
+    public void fireOnStartAsync(AsyncEvent event) throws IOException {
+        listener.onStartAsync(event);
+    }
+
+    
+    public void fireOnComplete(AsyncEvent event) throws IOException {
+        listener.onComplete(event);
+    }
+
+
+    public void fireOnTimeout(AsyncEvent event) throws IOException {
+        listener.onTimeout(event);
+    }
+
+    
+    public void fireOnError(AsyncEvent event) throws IOException {
+        listener.onError(event);
+    }
+
+
+    public AsyncListener getListener() {
+        return listener;
+    }
+
+    
+    public void setListener(AsyncListener listener) {
+        this.listener = listener;
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/Constants.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/Constants.java
new file mode 100644
index 0000000..25d86b4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/Constants.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.core;
+
+
+public class Constants {
+
+    public static final String Package = "org.apache.catalina.core";
+    public static final int MAJOR_VERSION = 3;
+    public static final int MINOR_VERSION = 0;
+
+    public static final String JSP_SERVLET_CLASS =
+        "org.apache.jasper.servlet.JspServlet";
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/ContainerBase.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ContainerBase.java
new file mode 100644
index 0000000..a2e6200
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ContainerBase.java
@@ -0,0 +1,1406 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.IOException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+
+import javax.management.ObjectName;
+import javax.naming.directory.DirContext;
+import javax.servlet.ServletException;
+
+import org.apache.catalina.AccessLog;
+import org.apache.catalina.CatalinaFactory;
+import org.apache.catalina.Cluster;
+import org.apache.catalina.Container;
+import org.apache.catalina.ContainerEvent;
+import org.apache.catalina.ContainerListener;
+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.Manager;
+import org.apache.catalina.Pipeline;
+import org.apache.catalina.Realm;
+import org.apache.catalina.Valve;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.mbeans.MBeanUtils;
+import org.apache.catalina.util.LifecycleMBeanBase;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.naming.resources.ProxyDirContext;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Abstract implementation of the <b>Container</b> interface, providing common
+ * functionality required by nearly every implementation.  Classes extending
+ * this base class must implement <code>getInfo()</code>, and may implement
+ * a replacement for <code>invoke()</code>.
+ * <p>
+ * All subclasses of this abstract base class will include support for a
+ * Pipeline object that defines the processing to be performed for each request
+ * received by the <code>invoke()</code> method of this class, utilizing the
+ * "Chain of Responsibility" design pattern.  A subclass should encapsulate its
+ * own processing functionality as a <code>Valve</code>, and configure this
+ * Valve into the pipeline by calling <code>setBasic()</code>.
+ * <p>
+ * This implementation fires property change events, per the JavaBeans design
+ * pattern, for changes in singleton properties.  In addition, it fires the
+ * following <code>ContainerEvent</code> events to listeners who register
+ * themselves with <code>addContainerListener()</code>:
+ * <table border=1>
+ *   <tr>
+ *     <th>Type</th>
+ *     <th>Data</th>
+ *     <th>Description</th>
+ *   </tr>
+ *   <tr>
+ *     <td align=center><code>addChild</code></td>
+ *     <td align=center><code>Container</code></td>
+ *     <td>Child container added to this Container.</td>
+ *   </tr>
+ *   <tr>
+ *     <td align=center><code>addValve</code></td>
+ *     <td align=center><code>Valve</code></td>
+ *     <td>Valve added to this Container.</td>
+ *   </tr>
+ *   <tr>
+ *     <td align=center><code>removeChild</code></td>
+ *     <td align=center><code>Container</code></td>
+ *     <td>Child container removed from this Container.</td>
+ *   </tr>
+ *   <tr>
+ *     <td align=center><code>removeValve</code></td>
+ *     <td align=center><code>Valve</code></td>
+ *     <td>Valve removed from this Container.</td>
+ *   </tr>
+ *   <tr>
+ *     <td align=center><code>start</code></td>
+ *     <td align=center><code>null</code></td>
+ *     <td>Container was started.</td>
+ *   </tr>
+ *   <tr>
+ *     <td align=center><code>stop</code></td>
+ *     <td align=center><code>null</code></td>
+ *     <td>Container was stopped.</td>
+ *   </tr>
+ * </table>
+ * Subclasses that fire additional events should document them in the
+ * class comments of the implementation class.
+ * 
+ * TODO: Review synchronisation around background processing. See bug 47024. 
+ * 
+ * @author Craig R. McClanahan
+ */
+public abstract class ContainerBase extends LifecycleMBeanBase
+        implements Container {
+
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( ContainerBase.class );
+
+    /**
+     * Perform addChild with the permissions of this class.
+     * addChild can be called with the XML parser on the stack,
+     * this allows the XML parser to have fewer privileges than
+     * Tomcat.
+     */
+    protected class PrivilegedAddChild
+        implements PrivilegedAction<Void> {
+
+        private Container child;
+
+        PrivilegedAddChild(Container child) {
+            this.child = child;
+        }
+
+        @Override
+        public Void run() {
+            addChildInternal(child);
+            return null;
+        }
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The child Containers belonging to this Container, keyed by name.
+     */
+    protected HashMap<String, Container> children =
+        new HashMap<String, Container>();
+
+
+    /**
+     * The processor delay for this component.
+     */
+    protected int backgroundProcessorDelay = -1;
+
+
+    /**
+     * The container event listeners for this Container.
+     */
+    protected ArrayList<ContainerListener> listeners = new ArrayList<ContainerListener>();
+
+
+    /**
+     * The Loader implementation with which this Container is associated.
+     */
+    protected Loader loader = null;
+
+
+    /**
+     * The Logger implementation with which this Container is associated.
+     */
+    protected Log logger = null;
+
+
+    /**
+     * Associated logger name.
+     */
+    protected String logName = null;
+    
+
+    /**
+     * The Manager implementation with which this Container is associated.
+     */
+    protected Manager manager = null;
+
+
+    /**
+     * The cluster with which this Container is associated.
+     */
+    protected Cluster cluster = null;
+
+    
+    /**
+     * The human-readable name of this Container.
+     */
+    protected String name = null;
+
+
+    /**
+     * The parent Container to which this Container is a child.
+     */
+    protected Container parent = null;
+
+
+    /**
+     * The parent class loader to be configured when we install a Loader.
+     */
+    protected ClassLoader parentClassLoader = null;
+
+
+    /**
+     * The Pipeline object with which this Container is associated.
+     */
+    protected Pipeline pipeline =
+        CatalinaFactory.getFactory().createPipeline(this);
+
+
+    /**
+     * The Realm with which this Container is associated.
+     */
+    protected Realm realm = null;
+
+
+    /**
+     * The resources DirContext object with which this Container is associated.
+     */
+    protected DirContext resources = null;
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
+     * Will children be started automatically when they are added.
+     */
+    protected boolean startChildren = true;
+
+    /**
+     * The property change support for this component.
+     */
+    protected PropertyChangeSupport support = new PropertyChangeSupport(this);
+
+
+    /**
+     * The background thread.
+     */
+    private Thread thread = null;
+
+
+    /**
+     * The background thread completion semaphore.
+     */
+    private volatile boolean threadDone = false;
+
+
+    /**
+     * The access log to use for requests normally handled by this container
+     * that have been handled earlier in the processing chain.
+     */
+    protected volatile AccessLog accessLog = null;
+    private volatile boolean accessLogScanComplete = false;
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Get the delay between the invocation of the backgroundProcess method on
+     * this container and its children. Child containers will not be invoked
+     * if their delay value is not negative (which would mean they are using 
+     * their own thread). Setting this to a positive value will cause 
+     * a thread to be spawn. After waiting the specified amount of time, 
+     * the thread will invoke the executePeriodic method on this container 
+     * and all its children.
+     */
+    @Override
+    public int getBackgroundProcessorDelay() {
+        return backgroundProcessorDelay;
+    }
+
+
+    /**
+     * Set the delay between the invocation of the execute method on this
+     * container and its children.
+     * 
+     * @param delay The delay in seconds between the invocation of 
+     *              backgroundProcess methods
+     */
+    @Override
+    public void setBackgroundProcessorDelay(int delay) {
+        backgroundProcessorDelay = delay;
+    }
+
+
+    /**
+     * Return descriptive information about this Container implementation and
+     * the corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    @Override
+    public String getInfo() {
+        return this.getClass().getName();
+    }
+
+
+    /**
+     * Return the Loader with which this Container is associated.  If there is
+     * no associated Loader, return the Loader associated with our parent
+     * Container (if any); otherwise, return <code>null</code>.
+     */
+    @Override
+    public Loader getLoader() {
+
+        if (loader != null)
+            return (loader);
+        if (parent != null)
+            return (parent.getLoader());
+        return (null);
+
+    }
+
+
+    /**
+     * Set the Loader with which this Container is associated.
+     *
+     * @param loader The newly associated loader
+     */
+    @Override
+    public synchronized void setLoader(Loader loader) {
+
+        // Change components if necessary
+        Loader oldLoader = this.loader;
+        if (oldLoader == loader)
+            return;
+        this.loader = loader;
+
+        // Stop the old component if necessary
+        if (getState().isAvailable() && (oldLoader != null) &&
+            (oldLoader instanceof Lifecycle)) {
+            try {
+                ((Lifecycle) oldLoader).stop();
+            } catch (LifecycleException e) {
+                log.error("ContainerBase.setLoader: stop: ", e);
+            }
+        }
+
+        // Start the new component if necessary
+        if (loader != null)
+            loader.setContainer(this);
+        if (getState().isAvailable() && (loader != null) &&
+            (loader instanceof Lifecycle)) {
+            try {
+                ((Lifecycle) loader).start();
+            } catch (LifecycleException e) {
+                log.error("ContainerBase.setLoader: start: ", e);
+            }
+        }
+
+        // Report this property change to interested listeners
+        support.firePropertyChange("loader", oldLoader, this.loader);
+
+    }
+
+
+    /**
+     * Return the Logger for this Container.
+     */
+    @Override
+    public Log getLogger() {
+
+        if (logger != null)
+            return (logger);
+        logger = LogFactory.getLog(logName());
+        return (logger);
+
+    }
+
+
+    /**
+     * Return the Manager with which this Container is associated.  If there is
+     * no associated Manager, return the Manager associated with our parent
+     * Container (if any); otherwise return <code>null</code>.
+     */
+    @Override
+    public Manager getManager() {
+
+        if (manager != null)
+            return (manager);
+        if (parent != null)
+            return (parent.getManager());
+        return (null);
+
+    }
+
+
+    /**
+     * Set the Manager with which this Container is associated.
+     *
+     * @param manager The newly associated Manager
+     */
+    @Override
+    public synchronized void setManager(Manager manager) {
+
+        // Change components if necessary
+        Manager oldManager = this.manager;
+        if (oldManager == manager)
+            return;
+        this.manager = manager;
+
+        // Stop the old component if necessary
+        if (getState().isAvailable() && (oldManager != null) &&
+            (oldManager instanceof Lifecycle)) {
+            try {
+                ((Lifecycle) oldManager).stop();
+            } catch (LifecycleException e) {
+                log.error("ContainerBase.setManager: stop: ", e);
+            }
+        }
+
+        // Start the new component if necessary
+        if (manager != null)
+            manager.setContainer(this);
+        if (getState().isAvailable() && (manager != null) &&
+            (manager instanceof Lifecycle)) {
+            try {
+                ((Lifecycle) manager).start();
+            } catch (LifecycleException e) {
+                log.error("ContainerBase.setManager: start: ", e);
+            }
+        }
+
+        // Report this property change to interested listeners
+        support.firePropertyChange("manager", oldManager, this.manager);
+
+    }
+
+
+    /**
+     * Return an object which may be utilized for mapping to this component.
+     */
+    @Override
+    public Object getMappingObject() {
+        return this;
+    }
+
+
+    /**
+     * Return the Cluster with which this Container is associated.  If there is
+     * no associated Cluster, return the Cluster associated with our parent
+     * Container (if any); otherwise return <code>null</code>.
+     */
+    @Override
+    public Cluster getCluster() {
+        if (cluster != null)
+            return (cluster);
+
+        if (parent != null)
+            return (parent.getCluster());
+
+        return (null);
+    }
+
+
+    /**
+     * Set the Cluster with which this Container is associated.
+     *
+     * @param cluster The newly associated Cluster
+     */
+    @Override
+    public synchronized void setCluster(Cluster cluster) {
+        // Change components if necessary
+        Cluster oldCluster = this.cluster;
+        if (oldCluster == cluster)
+            return;
+        this.cluster = cluster;
+
+        // Stop the old component if necessary
+        if (getState().isAvailable() && (oldCluster != null) &&
+            (oldCluster instanceof Lifecycle)) {
+            try {
+                ((Lifecycle) oldCluster).stop();
+            } catch (LifecycleException e) {
+                log.error("ContainerBase.setCluster: stop: ", e);
+            }
+        }
+
+        // Start the new component if necessary
+        if (cluster != null)
+            cluster.setContainer(this);
+
+        if (getState().isAvailable() && (cluster != null) &&
+            (cluster instanceof Lifecycle)) {
+            try {
+                ((Lifecycle) cluster).start();
+            } catch (LifecycleException e) {
+                log.error("ContainerBase.setCluster: start: ", e);
+            }
+        }
+
+        // Report this property change to interested listeners
+        support.firePropertyChange("cluster", oldCluster, this.cluster);
+    }
+
+
+    /**
+     * Return a name string (suitable for use by humans) that describes this
+     * Container.  Within the set of child containers belonging to a particular
+     * parent, Container names must be unique.
+     */
+    @Override
+    public String getName() {
+
+        return (name);
+
+    }
+
+
+    /**
+     * Set a name string (suitable for use by humans) that describes this
+     * Container.  Within the set of child containers belonging to a particular
+     * parent, Container names must be unique.
+     *
+     * @param name New name of this container
+     *
+     * @exception IllegalStateException if this Container has already been
+     *  added to the children of a parent Container (after which the name
+     *  may not be changed)
+     */
+    @Override
+    public void setName(String name) {
+
+        String oldName = this.name;
+        this.name = name;
+        support.firePropertyChange("name", oldName, this.name);
+    }
+
+
+    /**
+     * Return if children of this container will be started automatically when
+     * they are added to this container.
+     */
+    public boolean getStartChildren() {
+
+        return (startChildren);
+
+    }
+
+
+    /**
+     * Set if children of this container will be started automatically when
+     * they are added to this container.
+     *
+     * @param startChildren New value of the startChildren flag
+     */
+    public void setStartChildren(boolean startChildren) {
+
+        boolean oldStartChildren = this.startChildren;
+        this.startChildren = startChildren;
+        support.firePropertyChange("startChildren", oldStartChildren, this.startChildren);
+    }
+
+
+    /**
+     * Return the Container for which this Container is a child, if there is
+     * one.  If there is no defined parent, return <code>null</code>.
+     */
+    @Override
+    public Container getParent() {
+
+        return (parent);
+
+    }
+
+
+    /**
+     * Set the parent Container to which this Container is being added as a
+     * child.  This Container may refuse to become attached to the specified
+     * Container by throwing an exception.
+     *
+     * @param container Container to which this Container is being added
+     *  as a child
+     *
+     * @exception IllegalArgumentException if this Container refuses to become
+     *  attached to the specified Container
+     */
+    @Override
+    public void setParent(Container container) {
+
+        Container oldParent = this.parent;
+        this.parent = container;
+        support.firePropertyChange("parent", oldParent, this.parent);
+
+    }
+
+
+    /**
+     * Return the parent class loader (if any) for this web application.
+     * This call is meaningful only <strong>after</strong> a Loader has
+     * been configured.
+     */
+    @Override
+    public ClassLoader getParentClassLoader() {
+        if (parentClassLoader != null)
+            return (parentClassLoader);
+        if (parent != null) {
+            return (parent.getParentClassLoader());
+        }
+        return (ClassLoader.getSystemClassLoader());
+
+    }
+
+
+    /**
+     * Set the parent class loader (if any) for this web application.
+     * This call is meaningful only <strong>before</strong> a Loader has
+     * been configured, and the specified value (if non-null) should be
+     * passed as an argument to the class loader constructor.
+     *
+     *
+     * @param parent The new parent class loader
+     */
+    @Override
+    public void setParentClassLoader(ClassLoader parent) {
+        ClassLoader oldParentClassLoader = this.parentClassLoader;
+        this.parentClassLoader = parent;
+        support.firePropertyChange("parentClassLoader", oldParentClassLoader,
+                                   this.parentClassLoader);
+
+    }
+
+
+    /**
+     * Return the Pipeline object that manages the Valves associated with
+     * this Container.
+     */
+    @Override
+    public Pipeline getPipeline() {
+
+        return (this.pipeline);
+
+    }
+
+
+    /**
+     * Return the Realm with which this Container is associated.  If there is
+     * no associated Realm, return the Realm associated with our parent
+     * Container (if any); otherwise return <code>null</code>.
+     */
+    @Override
+    public Realm getRealm() {
+
+        if (realm != null)
+            return (realm);
+        if (parent != null)
+            return (parent.getRealm());
+        return (null);
+
+    }
+
+
+    /**
+     * Set the Realm with which this Container is associated.
+     *
+     * @param realm The newly associated Realm
+     */
+    @Override
+    public synchronized void setRealm(Realm realm) {
+
+        // Change components if necessary
+        Realm oldRealm = this.realm;
+        if (oldRealm == realm)
+            return;
+        this.realm = realm;
+
+        // Stop the old component if necessary
+        if (getState().isAvailable() && (oldRealm != null) &&
+            (oldRealm instanceof Lifecycle)) {
+            try {
+                ((Lifecycle) oldRealm).stop();
+            } catch (LifecycleException e) {
+                log.error("ContainerBase.setRealm: stop: ", e);
+            }
+        }
+
+        // Start the new component if necessary
+        if (realm != null)
+            realm.setContainer(this);
+        if (getState().isAvailable() && (realm != null) &&
+            (realm instanceof Lifecycle)) {
+            try {
+                ((Lifecycle) realm).start();
+            } catch (LifecycleException e) {
+                log.error("ContainerBase.setRealm: start: ", e);
+            }
+        }
+
+        // Report this property change to interested listeners
+        support.firePropertyChange("realm", oldRealm, this.realm);
+
+    }
+
+
+    /**
+      * Return the resources DirContext object with which this Container is
+      * associated.  If there is no associated resources object, return the
+      * resources associated with our parent Container (if any); otherwise
+      * return <code>null</code>.
+     */
+    @Override
+    public DirContext getResources() {
+        if (resources != null)
+            return (resources);
+        if (parent != null)
+            return (parent.getResources());
+        return (null);
+
+    }
+
+
+    /**
+     * Set the resources DirContext object with which this Container is
+     * associated.
+     *
+     * @param resources The newly associated DirContext
+     */
+    @Override
+    public synchronized void setResources(DirContext resources) {
+        // Called from StandardContext.setResources()
+        //              <- StandardContext.start() 
+        //              <- ContainerBase.addChildInternal() 
+
+        // Change components if necessary
+        DirContext oldResources = this.resources;
+        if (oldResources == resources)
+            return;
+        Hashtable<String, String> env = new Hashtable<String, String>();
+        if (getParent() != null)
+            env.put(ProxyDirContext.HOST, getParent().getName());
+        env.put(ProxyDirContext.CONTEXT, getName());
+        this.resources = new ProxyDirContext(env, resources);
+        // Report this property change to interested listeners
+        support.firePropertyChange("resources", oldResources, this.resources);
+
+    }
+
+
+    // ------------------------------------------------------ Container Methods
+
+
+    /**
+     * Add a new child Container to those associated with this Container,
+     * if supported.  Prior to adding this Container to the set of children,
+     * the child's <code>setParent()</code> method must be called, with this
+     * Container as an argument.  This method may thrown an
+     * <code>IllegalArgumentException</code> if this Container chooses not
+     * to be attached to the specified Container, in which case it is not added
+     *
+     * @param child New child Container to be added
+     *
+     * @exception IllegalArgumentException if this exception is thrown by
+     *  the <code>setParent()</code> method of the child Container
+     * @exception IllegalArgumentException if the new child does not have
+     *  a name unique from that of existing children of this Container
+     * @exception IllegalStateException if this Container does not support
+     *  child Containers
+     */
+    @Override
+    public void addChild(Container child) {
+        if (Globals.IS_SECURITY_ENABLED) {
+            PrivilegedAction<Void> dp =
+                new PrivilegedAddChild(child);
+            AccessController.doPrivileged(dp);
+        } else {
+            addChildInternal(child);
+        }
+    }
+
+    private void addChildInternal(Container child) {
+
+        if( log.isDebugEnabled() )
+            log.debug("Add child " + child + " " + this);
+        synchronized(children) {
+            if (children.get(child.getName()) != null)
+                throw new IllegalArgumentException("addChild:  Child name '" +
+                                                   child.getName() +
+                                                   "' is not unique");
+            child.setParent(this);  // May throw IAE
+            children.put(child.getName(), child);
+        }
+
+        // Start child
+        // Don't do this inside sync block - start can be a slow process and
+        // locking the children object can cause problems elsewhere
+        if ((getState().isAvailable() ||
+                LifecycleState.STARTING_PREP.equals(getState())) &&
+                startChildren) {
+            boolean success = false;
+            try {
+                child.start();
+                success = true;
+            } catch (LifecycleException e) {
+                log.error("ContainerBase.addChild: start: ", e);
+                throw new IllegalStateException
+                    ("ContainerBase.addChild: start: " + e);
+            } finally {
+                if (!success) {
+                    synchronized (children) {
+                        children.remove(child.getName());
+                    }
+                }
+            }
+        }
+
+        fireContainerEvent(ADD_CHILD_EVENT, child);
+    }
+
+
+    /**
+     * Add a container event listener to this component.
+     *
+     * @param listener The listener to add
+     */
+    @Override
+    public void addContainerListener(ContainerListener listener) {
+
+        synchronized (listeners) {
+            listeners.add(listener);
+        }
+
+    }
+
+
+    /**
+     * Add a property change listener to this component.
+     *
+     * @param listener The listener to add
+     */
+    @Override
+    public void addPropertyChangeListener(PropertyChangeListener listener) {
+
+        support.addPropertyChangeListener(listener);
+
+    }
+
+
+    /**
+     * Return the child Container, associated with this Container, with
+     * the specified name (if any); otherwise, return <code>null</code>
+     *
+     * @param name Name of the child Container to be retrieved
+     */
+    @Override
+    public Container findChild(String name) {
+
+        if (name == null)
+            return (null);
+        synchronized (children) {
+            return children.get(name);
+        }
+
+    }
+
+
+    /**
+     * Return the set of children Containers associated with this Container.
+     * If this Container has no children, a zero-length array is returned.
+     */
+    @Override
+    public Container[] findChildren() {
+
+        synchronized (children) {
+            Container results[] = new Container[children.size()];
+            return children.values().toArray(results);
+        }
+
+    }
+
+
+    /**
+     * Return the set of container listeners associated with this Container.
+     * If this Container has no registered container listeners, a zero-length
+     * array is returned.
+     */
+    @Override
+    public ContainerListener[] findContainerListeners() {
+
+        synchronized (listeners) {
+            ContainerListener[] results = 
+                new ContainerListener[listeners.size()];
+            return listeners.toArray(results);
+        }
+
+    }
+
+
+    /**
+     * Process the specified Request, to produce the corresponding Response,
+     * by invoking the first Valve in our pipeline (if any), or the basic
+     * Valve otherwise.
+     *
+     * @param request Request to be processed
+     * @param response Response to be produced
+     *
+     * @exception IllegalStateException if neither a pipeline or a basic
+     *  Valve have been configured for this Container
+     * @exception IOException if an input/output error occurred while
+     *  processing
+     * @exception ServletException if a ServletException was thrown
+     *  while processing this request
+     */
+    @Override
+    public void invoke(Request request, Response response)
+        throws IOException, ServletException {
+
+        pipeline.getFirst().invoke(request, response);
+
+    }
+
+
+    /**
+     * Remove an existing child Container from association with this parent
+     * Container.
+     *
+     * @param child Existing child Container to be removed
+     */
+    @Override
+    public void removeChild(Container child) {
+
+        if (child == null) {
+            return;
+        }
+        
+        synchronized(children) {
+            if (children.get(child.getName()) == null)
+                return;
+            children.remove(child.getName());
+        }
+        
+        try {
+            if (child.getState().isAvailable()) {
+                child.stop();
+            }
+        } catch (LifecycleException e) {
+            log.error("ContainerBase.removeChild: stop: ", e);
+        }
+        
+        fireContainerEvent(REMOVE_CHILD_EVENT, child);
+        
+        try {
+            // child.destroy() may have already been called which would have
+            // triggered this call. If that is the case, no need to destroy the
+            // child again.
+            if (!LifecycleState.DESTROYING.equals(child.getState())) {
+                child.destroy();
+            }
+        } catch (LifecycleException e) {
+            log.error("ContainerBase.removeChild: destroy: ", e);
+        }
+
+    }
+
+
+    /**
+     * Remove a container event listener from this component.
+     *
+     * @param listener The listener to remove
+     */
+    @Override
+    public void removeContainerListener(ContainerListener listener) {
+
+        synchronized (listeners) {
+            listeners.remove(listener);
+        }
+
+    }
+
+
+    /**
+     * Remove a property change listener from this component.
+     *
+     * @param listener The listener to remove
+     */
+    @Override
+    public void removePropertyChangeListener(PropertyChangeListener listener) {
+
+        support.removePropertyChangeListener(listener);
+
+    }
+
+
+    /**
+     * 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 {
+
+        // Start our subordinate components, if any
+        if ((loader != null) && (loader instanceof Lifecycle))
+            ((Lifecycle) loader).start();
+        logger = null;
+        getLogger();
+        if ((logger != null) && (logger instanceof Lifecycle))
+            ((Lifecycle) logger).start();
+        if ((manager != null) && (manager instanceof Lifecycle))
+            ((Lifecycle) manager).start();
+        if ((cluster != null) && (cluster instanceof Lifecycle))
+            ((Lifecycle) cluster).start();
+        if ((realm != null) && (realm instanceof Lifecycle))
+            ((Lifecycle) realm).start();
+        if ((resources != null) && (resources instanceof Lifecycle))
+            ((Lifecycle) resources).start();
+
+        // Start our child containers, if any
+        Container children[] = findChildren();
+        for (int i = 0; i < children.length; i++) {
+            children[i].start();
+        }
+
+        // Start the Valves in our pipeline (including the basic), if any
+        if (pipeline instanceof Lifecycle)
+            ((Lifecycle) pipeline).start();
+
+
+        setState(LifecycleState.STARTING);
+
+        // Start our thread
+        threadStart();
+
+    }
+
+
+    /**
+     * 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 {
+
+        // Stop our thread
+        threadStop();
+
+        setState(LifecycleState.STOPPING);
+
+        // Stop the Valves in our pipeline (including the basic), if any
+        if (pipeline instanceof Lifecycle) {
+            ((Lifecycle) pipeline).stop();
+        }
+
+        // Stop our child containers, if any
+        Container children[] = findChildren();
+        for (int i = 0; i < children.length; i++) {
+            children[i].stop();
+        }
+
+        // Stop our subordinate components, if any
+        if ((resources != null) && (resources instanceof Lifecycle)) {
+            ((Lifecycle) resources).stop();
+        }
+        if ((realm != null) && (realm instanceof Lifecycle)) {
+            ((Lifecycle) realm).stop();
+        }
+        if ((cluster != null) && (cluster instanceof Lifecycle)) {
+            ((Lifecycle) cluster).stop();
+        }
+        if ((manager != null) && (manager instanceof Lifecycle)) {
+            ((Lifecycle) manager).stop();
+        }
+        if ((logger != null) && (logger instanceof Lifecycle)) {
+            ((Lifecycle) logger).stop();
+        }
+        if ((loader != null) && (loader instanceof Lifecycle)) {
+            ((Lifecycle) loader).stop();
+        }
+    }
+
+    @Override
+    protected void destroyInternal() throws LifecycleException {
+
+        // Stop the Valves in our pipeline (including the basic), if any
+        if (pipeline instanceof Lifecycle) {
+            ((Lifecycle) pipeline).destroy();
+        }
+
+        // Remove children now this container is being destroyed
+        for (Container child : findChildren()) {
+            removeChild(child);
+        }
+
+        // Required if the child is destroyed directly.
+        if (parent != null) {
+            parent.removeChild(this);
+        }
+
+        super.destroyInternal();
+    }
+
+    
+    /**
+     * Check this container for an access log and if none is found, look to the
+     * parent. If there is no parent and still none is found, use the NoOp
+     * access log.
+     */
+    @Override
+    public void logAccess(Request request, Response response, long time,
+            boolean useDefault) {
+        
+        boolean logged = false;
+        
+        if (getAccessLog() != null) {
+            getAccessLog().log(request, response, time);
+            logged = true;
+        }
+        
+        if (getParent() != null) {
+            // No need to use default logger once request/response has been logged
+            // once
+            getParent().logAccess(request, response, time, (useDefault && !logged));
+        }
+    }
+
+    @Override
+    public AccessLog getAccessLog() {
+        
+        if (accessLogScanComplete) {
+            return accessLog;
+        }
+        
+        Valve valves[] = getPipeline().getValves();
+        for (Valve valve : valves) {
+            if (valve instanceof AccessLog) {
+                accessLog = (AccessLog) valve;
+                break;
+            }
+        }
+        accessLogScanComplete = true;
+        return accessLog;
+    }
+
+    // ------------------------------------------------------- Pipeline Methods
+
+
+    /**
+     * Convenience method, intended for use by the digester to simplify the
+     * process of adding Valves to containers. See
+     * {@link Pipeline#addValve(Valve)} for full details. Components other than
+     * the digester should use {@link #getPipeline()}.{@link #addValve(Valve)} in case a
+     * future implementation provides an alternative method for the digester to
+     * use.
+     *
+     * @param valve Valve to be added
+     *
+     * @exception IllegalArgumentException if this Container refused to
+     *  accept the specified Valve
+     * @exception IllegalArgumentException if the specified Valve refuses to be
+     *  associated with this Container
+     * @exception IllegalStateException if the specified Valve is already
+     *  associated with a different Container
+     */
+    public synchronized void addValve(Valve valve) {
+
+        pipeline.addValve(valve);
+    }
+
+
+    /**
+     * 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 (!getState().isAvailable())
+            return;
+
+        if (cluster != null) {
+            try {
+                cluster.backgroundProcess();
+            } catch (Exception e) {
+                log.warn(sm.getString("containerBase.backgroundProcess.cluster", cluster), e);                
+            }
+        }
+        if (loader != null) {
+            try {
+                loader.backgroundProcess();
+            } catch (Exception e) {
+                log.warn(sm.getString("containerBase.backgroundProcess.loader", loader), e);                
+            }
+        }
+        if (manager != null) {
+            try {
+                manager.backgroundProcess();
+            } catch (Exception e) {
+                log.warn(sm.getString("containerBase.backgroundProcess.manager", manager), e);                
+            }
+        }
+        if (realm != null) {
+            try {
+                realm.backgroundProcess();
+            } catch (Exception e) {
+                log.warn(sm.getString("containerBase.backgroundProcess.realm", realm), e);                
+            }
+        }
+        Valve current = pipeline.getFirst();
+        while (current != null) {
+            try {
+                current.backgroundProcess();
+            } catch (Exception e) {
+                log.warn(sm.getString("containerBase.backgroundProcess.valve", current), e);                
+            }
+            current = current.getNext();
+        }
+        fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Notify all container event listeners that a particular event has
+     * occurred for this Container.  The default implementation performs
+     * this notification synchronously using the calling thread.
+     *
+     * @param type Event type
+     * @param data Event data
+     */
+    @Override
+    public void fireContainerEvent(String type, Object data) {
+
+        if (listeners.size() < 1)
+            return;
+        ContainerEvent event = new ContainerEvent(this, type, data);
+        ContainerListener list[] = new ContainerListener[0];
+        synchronized (listeners) {
+            list = listeners.toArray(list);
+        }
+        for (int i = 0; i < list.length; i++)
+            list[i].containerEvent(event);
+
+    }
+
+
+    /**
+     * Return the abbreviated name of this container for logging messages
+     */
+    protected String logName() {
+
+        if (logName != null) {
+            return logName;
+        }
+        String loggerName = null;
+        Container current = this;
+        while (current != null) {
+            String name = current.getName();
+            if ((name == null) || (name.equals(""))) {
+                name = "/";
+            } else if (name.startsWith("##")) {
+                name = "/" + name;
+            }
+            loggerName = "[" + name + "]" 
+                + ((loggerName != null) ? ("." + loggerName) : "");
+            current = current.getParent();
+        }
+        logName = ContainerBase.class.getName() + "." + loggerName;
+        return logName;
+        
+    }
+
+    
+    // -------------------- JMX and Registration  --------------------
+
+    @Override
+    protected String getDomainInternal() {
+        return MBeanUtils.getDomain(this);
+    }
+
+    public ObjectName[] getChildren() {
+        ObjectName result[]=new ObjectName[children.size()];
+        Iterator<Container> it=children.values().iterator();
+        int i=0;
+        while( it.hasNext() ) {
+            Object next=it.next();
+            if( next instanceof ContainerBase ) {
+                result[i++]=((ContainerBase)next).getObjectName();
+            }
+        }
+        return result;
+    }
+
+    
+    // -------------------- Background Thread --------------------
+
+    /**
+     * Start the background thread that will periodically check for
+     * session timeouts.
+     */
+    protected void threadStart() {
+
+        if (thread != null)
+            return;
+        if (backgroundProcessorDelay <= 0)
+            return;
+
+        threadDone = false;
+        String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
+        thread = new Thread(new ContainerBackgroundProcessor(), threadName);
+        thread.setDaemon(true);
+        thread.start();
+
+    }
+
+
+    /**
+     * Stop the background thread that is periodically checking for
+     * session timeouts.
+     */
+    protected void threadStop() {
+
+        if (thread == null)
+            return;
+
+        threadDone = true;
+        thread.interrupt();
+        try {
+            thread.join();
+        } catch (InterruptedException e) {
+            // Ignore
+        }
+
+        thread = null;
+
+    }
+
+
+    // -------------------------------------- ContainerExecuteDelay Inner Class
+
+
+    /**
+     * Private thread class to invoke the backgroundProcess method 
+     * of this container and its children after a fixed delay.
+     */
+    protected class ContainerBackgroundProcessor implements Runnable {
+
+        @Override
+        public void run() {
+            while (!threadDone) {
+                try {
+                    Thread.sleep(backgroundProcessorDelay * 1000L);
+                } catch (InterruptedException e) {
+                    // Ignore
+                }
+                if (!threadDone) {
+                    Container parent = (Container) getMappingObject();
+                    ClassLoader cl = 
+                        Thread.currentThread().getContextClassLoader();
+                    if (parent.getLoader() != null) {
+                        cl = parent.getLoader().getClassLoader();
+                    }
+                    processChildren(parent, cl);
+                }
+            }
+        }
+
+        protected void processChildren(Container container, ClassLoader cl) {
+            try {
+                if (container.getLoader() != null) {
+                    Thread.currentThread().setContextClassLoader
+                        (container.getLoader().getClassLoader());
+                }
+                container.backgroundProcess();
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                log.error("Exception invoking periodic operation: ", t);
+            } finally {
+                Thread.currentThread().setContextClassLoader(cl);
+            }
+            Container[] children = container.findChildren();
+            for (int i = 0; i < children.length; i++) {
+                if (children[i].getBackgroundProcessorDelay() <= 0) {
+                    processChildren(children[i], cl);
+                }
+            }
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/DefaultInstanceManager.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/DefaultInstanceManager.java
new file mode 100644
index 0000000..1810df0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/DefaultInstanceManager.java
@@ -0,0 +1,532 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.annotation.Resource;
+import javax.ejb.EJB;
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.persistence.PersistenceContext;
+import javax.persistence.PersistenceUnit;
+import javax.servlet.Filter;
+import javax.servlet.Servlet;
+import javax.xml.ws.WebServiceRef;
+
+import org.apache.catalina.ContainerServlet;
+import org.apache.catalina.Globals;
+import org.apache.catalina.security.SecurityUtil;
+import org.apache.tomcat.InstanceManager;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * @version $Id: DefaultInstanceManager.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+public class DefaultInstanceManager implements InstanceManager {
+
+    private final Context context;
+    private final Map<String, Map<String, String>> injectionMap;
+    protected final ClassLoader classLoader;
+    protected final ClassLoader containerClassLoader;
+    protected boolean privileged;
+    protected boolean ignoreAnnotations;
+    private Properties restrictedFilters = new Properties();
+    private Properties restrictedListeners = new Properties();
+    private Properties restrictedServlets = new Properties();
+
+    public DefaultInstanceManager(Context context, Map<String, Map<String, String>> injectionMap, org.apache.catalina.Context catalinaContext, ClassLoader containerClassLoader) {
+        classLoader = catalinaContext.getLoader().getClassLoader();
+        privileged = catalinaContext.getPrivileged();
+        this.containerClassLoader = containerClassLoader;
+        ignoreAnnotations = catalinaContext.getIgnoreAnnotations();
+        StringManager sm = StringManager.getManager(Constants.Package);
+        try {
+            InputStream is =
+                this.getClass().getClassLoader().getResourceAsStream
+                    ("org/apache/catalina/core/RestrictedServlets.properties");
+            if (is != null) {
+                restrictedServlets.load(is);
+            } else {
+                catalinaContext.getLogger().error(sm.getString("defaultInstanceManager.restrictedServletsResource"));
+            }
+        } catch (IOException e) {
+            catalinaContext.getLogger().error(sm.getString("defaultInstanceManager.restrictedServletsResource"), e);
+        }
+
+        try {
+            InputStream is =
+                    this.getClass().getClassLoader().getResourceAsStream
+                            ("org/apache/catalina/core/RestrictedListeners.properties");
+            if (is != null) {
+                restrictedListeners.load(is);
+            } else {
+                catalinaContext.getLogger().error(sm.getString("defaultInstanceManager.restrictedListenersResources"));
+            }
+        } catch (IOException e) {
+            catalinaContext.getLogger().error(sm.getString("defaultInstanceManager.restrictedListenersResources"), e);
+        }
+        try {
+            InputStream is =
+                    this.getClass().getClassLoader().getResourceAsStream
+                            ("org/apache/catalina/core/RestrictedFilters.properties");
+            if (is != null) {
+                restrictedFilters.load(is);
+            } else {
+                catalinaContext.getLogger().error(sm.getString("defaultInstanceManager.restrictedFiltersResources"));
+            }
+        } catch (IOException e) {
+            catalinaContext.getLogger().error(sm.getString("defaultInstanceManager.restrictedServletsResources"), e);
+        }
+        this.context = context;
+        this.injectionMap = injectionMap;
+    }
+
+    @Override
+    public Object newInstance(String className) throws IllegalAccessException, InvocationTargetException, NamingException, InstantiationException, ClassNotFoundException {
+        Class<?> clazz = loadClassMaybePrivileged(className, classLoader);
+        return newInstance(clazz.newInstance(), clazz);
+    }
+
+    @Override
+    public Object newInstance(final String className, final ClassLoader classLoader) throws IllegalAccessException, NamingException, InvocationTargetException, InstantiationException, ClassNotFoundException {
+        Class<?> clazz = classLoader.loadClass(className);
+        return newInstance(clazz.newInstance(), clazz);
+    }
+
+    @Override
+    public void newInstance(Object o) 
+            throws IllegalAccessException, InvocationTargetException, NamingException {
+        newInstance(o, o.getClass());
+    }
+
+    private Object newInstance(Object instance, Class<?> clazz) throws IllegalAccessException, InvocationTargetException, NamingException {
+        if (!ignoreAnnotations) {
+            Map<String, String> injections = injectionMap.get(clazz.getName());
+            processAnnotations(instance, injections);
+            postConstruct(instance, clazz);
+        }
+        return instance;
+    }
+
+    @Override
+    public void destroyInstance(Object instance) throws IllegalAccessException, InvocationTargetException {
+        if (!ignoreAnnotations) {
+            preDestroy(instance, instance.getClass());
+        }
+    }
+
+    /**
+     * Call postConstruct method on the specified instance recursively from deepest superclass to actual class.
+     *
+     * @param instance object to call postconstruct methods on
+     * @param clazz    (super) class to examine for postConstruct annotation.
+     * @throws IllegalAccessException if postConstruct method is inaccessible.
+     * @throws java.lang.reflect.InvocationTargetException
+     *                                if call fails
+     */
+    protected void postConstruct(Object instance, final Class<?> clazz)
+            throws IllegalAccessException, InvocationTargetException {
+        Class<?> superClass = clazz.getSuperclass();
+        if (superClass != Object.class) {
+            postConstruct(instance, superClass);
+        }
+
+        Method[] methods = null;
+        if (Globals.IS_SECURITY_ENABLED) {
+            methods = AccessController.doPrivileged(
+                    new PrivilegedAction<Method[]>(){
+                @Override
+                public Method[] run(){
+                    return clazz.getDeclaredMethods();
+                }
+            });
+        } else {
+            methods = clazz.getDeclaredMethods();
+        }
+        Method postConstruct = null;
+        for (Method method : methods) {
+            if (method.isAnnotationPresent(PostConstruct.class)) {
+                if ((postConstruct != null)
+                        || (method.getParameterTypes().length != 0)
+                        || (Modifier.isStatic(method.getModifiers()))
+                        || (method.getExceptionTypes().length > 0)
+                        || (!method.getReturnType().getName().equals("void"))) {
+                    throw new IllegalArgumentException("Invalid PostConstruct annotation");
+                }
+                postConstruct = method;
+            }
+        }
+
+        // At the end the postconstruct annotated
+        // method is invoked
+        if (postConstruct != null) {
+            boolean accessibility = postConstruct.isAccessible();
+            postConstruct.setAccessible(true);
+            postConstruct.invoke(instance);
+            postConstruct.setAccessible(accessibility);
+        }
+
+    }
+
+
+    /**
+     * Call preDestroy method on the specified instance recursively from deepest superclass to actual class.
+     *
+     * @param instance object to call preDestroy methods on
+     * @param clazz    (super) class to examine for preDestroy annotation.
+     * @throws IllegalAccessException if preDestroy method is inaccessible.
+     * @throws java.lang.reflect.InvocationTargetException
+     *                                if call fails
+     */
+    protected void preDestroy(Object instance, final Class<?> clazz)
+            throws IllegalAccessException, InvocationTargetException {
+        Class<?> superClass = clazz.getSuperclass();
+        if (superClass != Object.class) {
+            preDestroy(instance, superClass);
+        }
+
+        Method[] methods;
+        if (Globals.IS_SECURITY_ENABLED) {
+            methods = AccessController.doPrivileged(
+                    new PrivilegedAction<Method[]>(){
+                @Override
+                public Method[] run(){
+                    return clazz.getDeclaredMethods();
+                }
+            });
+        } else {
+            methods = clazz.getDeclaredMethods();
+        }
+        Method preDestroy = null;
+        for (Method method : methods) {
+            if (method.isAnnotationPresent(PreDestroy.class)) {
+                if ((method.getParameterTypes().length != 0)
+                        || (Modifier.isStatic(method.getModifiers()))
+                        || (method.getExceptionTypes().length > 0)
+                        || (!method.getReturnType().getName().equals("void"))) {
+                    throw new IllegalArgumentException("Invalid PreDestroy annotation");
+                }
+                preDestroy = method;
+                break;
+            }
+        }
+
+        // At the end the postconstruct annotated
+        // method is invoked
+        if (preDestroy != null) {
+            boolean accessibility = preDestroy.isAccessible();
+            preDestroy.setAccessible(true);
+            preDestroy.invoke(instance);
+            preDestroy.setAccessible(accessibility);
+        }
+
+    }
+
+
+    /**
+     * Inject resources in specified instance.
+     *
+     * @param instance   instance to inject into
+     * @param injections map of injections for this class from xml deployment descriptor
+     * @throws IllegalAccessException       if injection target is inaccessible
+     * @throws javax.naming.NamingException if value cannot be looked up in jndi
+     * @throws java.lang.reflect.InvocationTargetException
+     *                                      if injection fails
+     */
+    protected void processAnnotations(Object instance, Map<String, String> injections)
+            throws IllegalAccessException, InvocationTargetException, NamingException {
+
+        if (context == null) {
+            // No resource injection
+            return;
+        }
+
+        Class<?> clazz = instance.getClass();
+        
+        while (clazz != null) {
+            // Initialize fields annotations
+            Field[] fields = null;
+            if (Globals.IS_SECURITY_ENABLED) {
+                final Class<?> clazz2 = clazz;
+                fields = AccessController.doPrivileged(
+                        new PrivilegedAction<Field[]>(){
+                    @Override
+                    public Field[] run(){
+                        return clazz2.getDeclaredFields();
+                    }
+                });
+            } else {
+                fields = clazz.getDeclaredFields();
+            }
+            for (Field field : fields) {
+                if (injections != null && injections.containsKey(field.getName())) {
+                    lookupFieldResource(context, instance, field,
+                            injections.get(field.getName()), clazz);
+                } else if (field.isAnnotationPresent(Resource.class)) {
+                    Resource annotation = field.getAnnotation(Resource.class);
+                    lookupFieldResource(context, instance, field,
+                            annotation.name(), clazz);
+                } else if (field.isAnnotationPresent(EJB.class)) {
+                    EJB annotation = field.getAnnotation(EJB.class);
+                    lookupFieldResource(context, instance, field,
+                            annotation.name(), clazz);
+                } else if (field.isAnnotationPresent(WebServiceRef.class)) {
+                    WebServiceRef annotation =
+                            field.getAnnotation(WebServiceRef.class);
+                    lookupFieldResource(context, instance, field,
+                            annotation.name(), clazz);
+                } else if (field.isAnnotationPresent(PersistenceContext.class)) {
+                    PersistenceContext annotation =
+                            field.getAnnotation(PersistenceContext.class);
+                    lookupFieldResource(context, instance, field,
+                            annotation.name(), clazz);
+                } else if (field.isAnnotationPresent(PersistenceUnit.class)) {
+                    PersistenceUnit annotation =
+                            field.getAnnotation(PersistenceUnit.class);
+                    lookupFieldResource(context, instance, field,
+                            annotation.name(), clazz);
+                }
+            }
+    
+            // Initialize methods annotations
+            Method[] methods = null;
+            if (Globals.IS_SECURITY_ENABLED) {
+                final Class<?> clazz2 = clazz;
+                methods = AccessController.doPrivileged(
+                        new PrivilegedAction<Method[]>(){
+                    @Override
+                    public Method[] run(){
+                        return clazz2.getDeclaredMethods();
+                    }
+                });
+            } else {
+                methods = clazz.getDeclaredMethods();
+            }
+            for (Method method : methods) {
+                String methodName = method.getName();
+                if (injections != null && methodName.startsWith("set") && methodName.length() > 3) {
+                    String fieldName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
+                    if (injections.containsKey(fieldName)) {
+                        lookupMethodResource(context, instance, method,
+                                injections.get(fieldName), clazz);
+                        break;
+                    }
+                }
+                if (method.isAnnotationPresent(Resource.class)) {
+                    Resource annotation = method.getAnnotation(Resource.class);
+                    lookupMethodResource(context, instance, method,
+                            annotation.name(), clazz);
+                } else if (method.isAnnotationPresent(EJB.class)) {
+                    EJB annotation = method.getAnnotation(EJB.class);
+                    lookupMethodResource(context, instance, method,
+                            annotation.name(), clazz);
+                } else if (method.isAnnotationPresent(WebServiceRef.class)) {
+                    WebServiceRef annotation =
+                            method.getAnnotation(WebServiceRef.class);
+                    lookupMethodResource(context, instance, method,
+                            annotation.name(), clazz);
+                } else if (method.isAnnotationPresent(PersistenceContext.class)) {
+                    PersistenceContext annotation =
+                            method.getAnnotation(PersistenceContext.class);
+                    lookupMethodResource(context, instance, method,
+                            annotation.name(), clazz);
+                } else if (method.isAnnotationPresent(PersistenceUnit.class)) {
+                    PersistenceUnit annotation =
+                            method.getAnnotation(PersistenceUnit.class);
+                    lookupMethodResource(context, instance, method,
+                            annotation.name(), clazz);
+                }
+            }
+            clazz = clazz.getSuperclass();
+        }
+
+    }
+
+
+    protected Class<?> loadClassMaybePrivileged(final String className, final ClassLoader classLoader) throws ClassNotFoundException {
+        Class<?> clazz;
+        if (SecurityUtil.isPackageProtectionEnabled()) {
+            try {
+                clazz = AccessController.doPrivileged(new PrivilegedExceptionAction<Class<?>>() {
+
+                    @Override
+                    public Class<?> run() throws Exception {
+                        return loadClass(className, classLoader);
+                    }
+                });
+            } catch (PrivilegedActionException e) {
+                Throwable t = e.getCause();
+                if (t instanceof ClassNotFoundException) {
+                    throw (ClassNotFoundException) t;
+                }
+                throw new RuntimeException(t);
+            }
+        } else {
+            clazz = loadClass(className, classLoader);
+        }
+        checkAccess(clazz);
+        return clazz;
+    }
+
+    protected Class<?> loadClass(String className, ClassLoader classLoader) throws ClassNotFoundException {
+        if (className.startsWith("org.apache.catalina")) {
+            return containerClassLoader.loadClass(className);
+        }
+        try {
+            Class<?> clazz = containerClassLoader.loadClass(className);
+            if (ContainerServlet.class.isAssignableFrom(clazz)) {
+                return clazz;
+            }
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+        }
+        return classLoader.loadClass(className);
+    }
+
+    private void checkAccess(Class<?> clazz) {
+        if (privileged) return;
+        if (Filter.class.isAssignableFrom(clazz)) {
+            checkAccess(clazz, restrictedFilters);
+        } else if (Servlet.class.isAssignableFrom(clazz)) {
+            checkAccess(clazz, restrictedServlets);
+        } else {
+            checkAccess(clazz, restrictedListeners);
+        }
+    }
+
+    private void checkAccess(Class<?> clazz, Properties restricted) {
+        while (clazz != null) {
+            if ("restricted".equals(restricted.getProperty(clazz.getName()))) {
+                throw new SecurityException("Restricted class" + clazz);
+            }
+            clazz = clazz.getSuperclass();
+        }
+
+    }
+
+    /**
+     * Inject resources in specified field.
+     *
+     * @param context  jndi context to extract value from
+     * @param instance object to inject into
+     * @param field    field target for injection
+     * @param name     jndi name value is bound under
+     * @param clazz    class annotation is defined in
+     * @throws IllegalAccessException       if field is inaccessible
+     * @throws javax.naming.NamingException if value is not accessible in naming context
+     */
+    protected static void lookupFieldResource(Context context,
+            Object instance, Field field, String name, Class<?> clazz)
+            throws NamingException, IllegalAccessException {
+
+        Object lookedupResource;
+        boolean accessibility;
+
+        String normalizedName = normalize(name);
+
+        if ((normalizedName != null) && (normalizedName.length() > 0)) {
+            lookedupResource = context.lookup(normalizedName);
+        } else {
+            lookedupResource =
+                context.lookup(clazz.getName() + "/" + field.getName());
+        }
+
+        accessibility = field.isAccessible();
+        field.setAccessible(true);
+        field.set(instance, lookedupResource);
+        field.setAccessible(accessibility);
+    }
+
+    /**
+     * Inject resources in specified method.
+     *
+     * @param context  jndi context to extract value from
+     * @param instance object to inject into
+     * @param method   field target for injection
+     * @param name     jndi name value is bound under
+     * @param clazz    class annotation is defined in
+     * @throws IllegalAccessException       if method is inaccessible
+     * @throws javax.naming.NamingException if value is not accessible in naming context
+     * @throws java.lang.reflect.InvocationTargetException
+     *                                      if setter call fails
+     */
+    protected static void lookupMethodResource(Context context,
+            Object instance, Method method, String name, Class<?> clazz)
+            throws NamingException, IllegalAccessException, InvocationTargetException {
+
+        if (!method.getName().startsWith("set")
+                || method.getName().length() < 4
+                || method.getParameterTypes().length != 1
+                || !method.getReturnType().getName().equals("void")) {
+            throw new IllegalArgumentException("Invalid method resource injection annotation");
+        }
+
+        Object lookedupResource;
+        boolean accessibility;
+
+        String normalizedName = normalize(name);
+
+        if ((normalizedName != null) && (normalizedName.length() > 0)) {
+            lookedupResource = context.lookup(normalizedName);
+        } else {
+            lookedupResource = context.lookup(
+                    clazz.getName() + "/" + getName(method));
+        }
+
+        accessibility = method.isAccessible();
+        method.setAccessible(true);
+        method.invoke(instance, lookedupResource);
+        method.setAccessible(accessibility);
+    }
+
+    public static String getName(Method setter) {
+        StringBuilder name = new StringBuilder(setter.getName());
+
+        // remove 'set'
+        name.delete(0, 3);
+
+        // lowercase first char
+        name.setCharAt(0, Character.toLowerCase(name.charAt(0)));
+
+        return name.toString();
+    }
+    
+    private static String normalize(String jndiName){
+        if(jndiName != null && jndiName.startsWith("java:comp/env/")){
+            return jndiName.substring(14);
+        }
+        return jndiName;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/JasperListener.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/JasperListener.java
new file mode 100644
index 0000000..a4e5d40
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/JasperListener.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.core;
+
+
+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.ExceptionUtils;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * This listener is designed to initialize Jasper before any web applications are
+ * started.
+ *
+ * @author Remy Maucherat
+ * @version $Id: JasperListener.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ * @since 4.1
+ */
+
+public class JasperListener
+    implements LifecycleListener {
+
+    private static final Log log = LogFactory.getLog(JasperListener.class);
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    // ---------------------------------------------- LifecycleListener Methods
+
+
+    /**
+     * Primary entry point for startup and shutdown events.
+     *
+     * @param event The event that has occurred
+     */
+    @Override
+    public void lifecycleEvent(LifecycleEvent event) {
+
+        if (Lifecycle.BEFORE_INIT_EVENT.equals(event.getType())) {
+            try {
+                // Set JSP factory
+                Class.forName("org.apache.jasper.compiler.JspRuntimeContext",
+                              true,
+                              this.getClass().getClassLoader());
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                // Should not occur, obviously
+                log.warn("Couldn't initialize Jasper", t);
+            }
+            // Another possibility is to do directly:
+            // JspFactory.setDefaultFactory(new JspFactoryImpl());
+        }
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/JreMemoryLeakPreventionListener.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/JreMemoryLeakPreventionListener.java
new file mode 100644
index 0000000..e646c8f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/JreMemoryLeakPreventionListener.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.core;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+
+import javax.imageio.ImageIO;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+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;
+
+/**
+ * Provide a workaround for known places where the Java Runtime environment can
+ * cause a memory leak or lock files.
+ * <p>
+ * Memory leaks occur when JRE code uses
+ * the context class loader to load a singleton as this will cause a memory leak
+ * if a web application class loader happens to be the context class loader at
+ * the time. The work-around is to initialise these singletons when Tomcat's
+ * common class loader is the context class loader.
+ * <p>
+ * Locked files usually occur when a resource inside a JAR is accessed without
+ * first disabling Jar URL connection caching. The workaround is to disable this
+ * caching by default. 
+ */
+public class JreMemoryLeakPreventionListener implements LifecycleListener {
+
+    private static final Log log =
+        LogFactory.getLog(JreMemoryLeakPreventionListener.class);
+    private static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    /**
+     * Protect against the memory leak caused when the first call to
+     * <code>sun.awt.AppContext.getAppContext()</code> is triggered by a web
+     * application. Defaults to <code>true</code>.
+     */
+    private boolean appContextProtection = true;
+    public boolean isAppContextProtection() { return appContextProtection; }
+    public void setAppContextProtection(boolean appContextProtection) {
+        this.appContextProtection = appContextProtection;
+    }
+    
+    /**
+     * Protect against the memory leak caused when the first call to
+     * <code>sun.misc.GC.requestLatency(long)</code> is triggered by a web
+     * application. This first call will start a GC Daemon thread with the
+     * thread's context class loader configured to be the web application class
+     * loader. Defaults to <code>true</code>.
+     */
+    private boolean gcDaemonProtection = true;
+    public boolean isGcDaemonProtection() { return gcDaemonProtection; }
+    public void setGcDaemonProtection(boolean gcDaemonProtection) {
+        this.gcDaemonProtection = gcDaemonProtection;
+    }
+
+     /**
+      * Protect against the memory leak caused when the first call to
+      * <code>javax.security.auth.Policy</code> is triggered by a web
+      * application. This first call populate a static variable with a reference
+      * to the context class loader. Defaults to <code>true</code>.
+      */
+     private boolean securityPolicyProtection = true;
+     public boolean isSecurityPolicyProtection() {
+         return securityPolicyProtection;
+     }
+     public void setSecurityPolicyProtection(boolean securityPolicyProtection) {
+         this.securityPolicyProtection = securityPolicyProtection;
+     }
+     
+    /**
+     * Protects against the memory leak caused when the first call to
+     * <code>javax.security.auth.login.Configuration</code> is triggered by a
+     * web application. This first call populate a static variable with a
+     * reference to the context class loader. Defaults to <code>true</code>.
+     */
+    private boolean securityLoginConfigurationProtection = true;
+    public boolean isSecurityLoginConfigurationProtection() {
+        return securityLoginConfigurationProtection;
+    }
+    public void setSecurityLoginConfigurationProtection(
+            boolean securityLoginConfigurationProtection) {
+        this.securityLoginConfigurationProtection = securityLoginConfigurationProtection;
+    }
+
+     /**
+     * Protect against the memory leak, when the initialization of the
+     * Java Cryptography Architecture is triggered by initializing
+     * a MessageDigest during web application deployment.
+     * This will occasionally start a Token Poller thread with the thread's
+     * context class loader equal to the web application class loader.
+     * Instead we initialize JCA early.
+     * Defaults to <code>true</code>.
+     */
+    private boolean tokenPollerProtection = true;
+    public boolean isTokenPollerProtection() { return tokenPollerProtection; }
+    public void setTokenPollerProtection(boolean tokenPollerProtection) {
+        this.tokenPollerProtection = tokenPollerProtection;
+    }
+
+    /**
+     * Protect against resources being read for JAR files and, as a side-effect,
+     * the JAR file becoming locked. Note this disables caching for all
+     * {@link URLConnection}s, regardless of type. Defaults to
+     * <code>true</code>.
+     */
+    private boolean urlCacheProtection = true;
+    public boolean isUrlCacheProtection() { return urlCacheProtection; }
+    public void setUrlCacheProtection(boolean urlCacheProtection) {
+        this.urlCacheProtection = urlCacheProtection;
+    }
+
+    /**
+     * XML parsing can pin a web application class loader in memory. This is
+     * particularly nasty as profilers (at least YourKit and Eclipse MAT) don't
+     * identify any GC roots related to this.
+     * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6916498">
+     * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6916498</a>
+     */
+    private boolean xmlParsingProtection = true;
+    public boolean isXmlParsingProtection() { return xmlParsingProtection; }
+    public void setXmlParsingProtection(boolean xmlParsingProtection) {
+        this.xmlParsingProtection = xmlParsingProtection;
+    }
+    
+    /**
+     * <code>com.sun.jndi.ldap.LdapPoolManager</code> class spawns a thread when it
+     * is initialized if the system property
+     * <code>com.sun.jndi.ldap.connect.pool.timeout</code> is greater than 0.
+     * That thread inherits the context class loader of the current thread, so
+     * that there may be a web application class loader leak if the web app
+     * is the first to use <code>LdapPoolManager</code>. 
+     */
+    private boolean ldapPoolProtection = true;
+    public boolean isLdapPoolProtection() { return ldapPoolProtection; }
+    public void setLdapPoolProtection(boolean ldapPoolProtection) {
+        this.ldapPoolProtection = ldapPoolProtection;
+    }
+    
+    @Override
+    public void lifecycleEvent(LifecycleEvent event) {
+        // Initialise these classes when Tomcat starts
+        if (Lifecycle.BEFORE_INIT_EVENT.equals(event.getType())) {
+
+            ClassLoader loader = Thread.currentThread().getContextClassLoader();
+
+            try
+            {
+                // Use the system classloader as the victim for all this
+                // ClassLoader pinning we're about to do.
+                Thread.currentThread().setContextClassLoader(
+                        ClassLoader.getSystemClassLoader());
+
+                /*
+                 * Several components end up calling:
+                 * sun.awt.AppContext.getAppContext()
+                 * 
+                 * Those libraries / components known to trigger memory leaks
+                 * due to eventual calls to getAppContext() are:
+                 * - Google Web Toolkit via its use of javax.imageio
+                 * - Tomcat via its use of java.beans.Introspector.flushCaches()
+                 *   in 1.6.0_15 onwards
+                 * - others TBD
+                 */
+                
+                // Trigger a call to sun.awt.AppContext.getAppContext(). This
+                // will pin the system class loader in memory but that shouldn't
+                // be an issue.
+                if (appContextProtection) {
+                    ImageIO.getCacheDirectory();
+                }
+                
+                /*
+                 * Several components end up calling:
+                 * sun.misc.GC.requestLatency(long)
+                 * 
+                 * Those libraries / components known to trigger memory leaks
+                 * due to eventual calls to requestLatency(long) are:
+                 * - javax.management.remote.rmi.RMIConnectorServer.start()
+                 */
+                if (gcDaemonProtection) {
+                    try {
+                        Class<?> clazz = Class.forName("sun.misc.GC");
+                        Method method = clazz.getDeclaredMethod(
+                                "requestLatency",
+                                new Class[] {long.class});
+                        method.invoke(null, Long.valueOf(3600000));
+                    } catch (ClassNotFoundException e) {
+                        if (System.getProperty("java.vendor").startsWith(
+                                "Sun")) {
+                            log.error(sm.getString(
+                                    "jreLeakListener.gcDaemonFail"), e);
+                        } else {
+                            log.debug(sm.getString(
+                                    "jreLeakListener.gcDaemonFail"), e);
+                        }
+                    } catch (SecurityException e) {
+                        log.error(sm.getString("jreLeakListener.gcDaemonFail"),
+                                e);
+                    } catch (NoSuchMethodException e) {
+                        log.error(sm.getString("jreLeakListener.gcDaemonFail"),
+                                e);
+                    } catch (IllegalArgumentException e) {
+                        log.error(sm.getString("jreLeakListener.gcDaemonFail"),
+                                e);
+                    } catch (IllegalAccessException e) {
+                        log.error(sm.getString("jreLeakListener.gcDaemonFail"),
+                                e);
+                    } catch (InvocationTargetException e) {
+                        log.error(sm.getString("jreLeakListener.gcDaemonFail"),
+                                e);
+                    }
+                }
+    
+                /*
+                 * Calling getPolicy retains a static reference to the context 
+                 * class loader.
+                 */
+                if (securityPolicyProtection) {
+                    try {
+                        // Policy.getPolicy();
+                        Class<?> policyClass = Class
+                                .forName("javax.security.auth.Policy");
+                        Method method = policyClass.getMethod("getPolicy");
+                        method.invoke(null);
+                    } catch(ClassNotFoundException e) {
+                        // Ignore. The class is deprecated.
+                    } catch(SecurityException e) {
+                        // Ignore. Don't need call to getPolicy() to be
+                        // successful, just need to trigger static initializer.
+                    } catch (NoSuchMethodException e) {
+                        log.warn(sm.getString("jreLeakListener.authPolicyFail"),
+                                e);
+                    } catch (IllegalArgumentException e) {
+                        log.warn(sm.getString("jreLeakListener.authPolicyFail"),
+                                e);
+                    } catch (IllegalAccessException e) {
+                        log.warn(sm.getString("jreLeakListener.authPolicyFail"),
+                                e);
+                    } catch (InvocationTargetException e) {
+                        log.warn(sm.getString("jreLeakListener.authPolicyFail"),
+                                e);
+                    }
+                }
+    
+                
+                /*
+                 * Initializing javax.security.auth.login.Configuration retains a static reference to the context 
+                 * class loader.
+                 */
+                if (securityLoginConfigurationProtection) {
+                    try {
+                        Class.forName("javax.security.auth.login.Configuration", true, ClassLoader.getSystemClassLoader());
+                    } catch(ClassNotFoundException e) {
+                        // Ignore
+                    }
+                }
+
+                /*
+                 * Creating a MessageDigest during web application startup
+                 * initializes the Java Cryptography Architecture. Under certain
+                 * conditions this starts a Token poller thread with TCCL equal
+                 * to the web application class loader.
+                 * 
+                 * Instead we initialize JCA right now.
+                 */
+                if (tokenPollerProtection) {
+                    java.security.Security.getProviders();
+                }
+                
+                /*
+                 * Several components end up opening JarURLConnections without
+                 * first disabling caching. This effectively locks the file.
+                 * Whilst more noticeable and harder to ignore on Windows, it
+                 * affects all operating systems.
+                 * 
+                 * Those libraries/components known to trigger this issue
+                 * include:
+                 * - log4j versions 1.2.15 and earlier
+                 * - javax.xml.bind.JAXBContext.newInstance()
+                 */
+                
+                // Set the default URL caching policy to not to cache
+                if (urlCacheProtection) {
+                    try {
+                        // Doesn't matter that this JAR doesn't exist - just as
+                        // long as the URL is well-formed
+                        URL url = new URL("jar:file://dummy.jar!/");
+                        URLConnection uConn = url.openConnection();
+                        uConn.setDefaultUseCaches(false);
+                    } catch (MalformedURLException e) {
+                        log.error(sm.getString(
+                                "jreLeakListener.jarUrlConnCacheFail"), e);
+                    } catch (IOException e) {
+                        log.error(sm.getString(
+                                "jreLeakListener.jarUrlConnCacheFail"), e);
+                    }
+                }
+                
+                /*
+                 * Haven't got to the root of what is going on with this leak
+                 * but if a web app is the first to make the calls below the web
+                 * application class loader will be pinned in memory.
+                 */
+                if (xmlParsingProtection) {
+                    DocumentBuilderFactory factory =
+                        DocumentBuilderFactory.newInstance();
+                    try {
+                        factory.newDocumentBuilder();
+                    } catch (ParserConfigurationException e) {
+                        log.error(sm.getString("jreLeakListener.xmlParseFail"),
+                                e);
+                    }
+                }
+                
+                if (ldapPoolProtection) {
+                    try {
+                        Class.forName("com.sun.jndi.ldap.LdapPoolManager");
+                    } catch (ClassNotFoundException e) {
+                        if (System.getProperty("java.vendor").startsWith(
+                                "Sun")) {
+                            log.error(sm.getString(
+                                    "jreLeakListener.ldapPoolManagerFail"), e);
+                        } else {
+                            log.debug(sm.getString(
+                                    "jreLeakListener.ldapPoolManagerFail"), e);
+                        }
+                    }
+                }
+
+            } finally {
+                Thread.currentThread().setContextClassLoader(loader);
+            }
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/core/LocalStrings.properties
new file mode 100644
index 0000000..39e8cab
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/LocalStrings.properties
@@ -0,0 +1,252 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+applicationContext.addFilter.ise=Filters can not be added to context {0} as the context has been initialised
+applicationContext.addListener.iae.cnfe=Unable to create an instance of type [{0}]
+applicationContext.addListener.iae.wrongType=The type specified [{0}] is not one of the expected listener types
+applicationContext.addListener.iae.sclNotAllowed=Once the first ServletContextListener has been called, no more ServletContextListeners may be added.
+applicationContext.addListener.ise=Listeners can not be added to context {0} as the context has been initialised
+applicationContext.addRole.ise=Roles can not be added to context {0} as the context has been initialised
+applicationContext.addServlet.ise=Servlets can not be added to context {0} as the context has been initialised
+applicationContext.attributeEvent=Exception thrown by attributes event listener
+applicationContext.mapping.error=Error during mapping
+applicationContext.requestDispatcher.iae=Path {0} does not start with a "/" character
+applicationContext.resourcePaths.iae=Path {0} does not start with a "/" character
+applicationContext.role.iae=An individual role to declare for context [{0}] may not be null nor the empty string
+applicationContext.roles.iae=Array of roles to declare for context [{0}] cannot be null
+applicationContext.setAttribute.namenull=Name cannot be null
+applicationContext.addSessionCookieConfig.ise=Session Cookie configuration cannot be set for context {0} as the context has been initialised
+applicationContext.setSessionTracking.ise=The session tracking modes for context {0} cannot be set whilst the context is running
+applicationContext.setSessionTracking.iae.invalid=The session tracking mode {0} requested for context {1} is not supported by that context
+applicationContext.setSessionTracking.iae.ssl=The session tracking modes requested for context {1} included SSL and at least one other mode. SSL may not be configured with other modes.
+applicationContext.lookup.error=Failed to locate resource [{0}] in context [{1}]
+applicationDispatcher.allocateException=Allocate exception for servlet {0}
+applicationDispatcher.deallocateException=Deallocate exception for servlet {0}
+applicationDispatcher.forward.ise=Cannot forward after response has been committed
+applicationDispatcher.forward.throw=Forwarded resource threw an exception
+applicationDispatcher.include.throw=Included resource threw an exception
+applicationDispatcher.isUnavailable=Servlet {0} is currently unavailable
+applicationDispatcher.serviceException=Servlet.service() for servlet {0} threw exception
+applicationDispatcher.specViolation.request=Original SevletRequest or wrapped original ServletRequest not passed to RequestDispatcher in violation of SRV.8.2 and SRV.14.2.5.1
+applicationDispatcher.specViolation.response=Original SevletResponse or wrapped original ServletResponse not passed to RequestDispatcher in violation of SRV.8.2 and SRV.14.2.5.1
+applicationFilterConfig.jmxRegisterFail=JMX registration failed for filter of type [{0}] and name [{1}]
+applicationFilterConfig.jmxUnregister=JMX de-registration complete for filter of type [{0}] and name [{1}]
+applicationFilterConfig.jmxUnregisterFail=JMX de-registration failed for filter of type [{0}] and name [{1}]
+applicationFilterRegistration.nullInitParam=Unable to set initialisation parameter for filter due to null name and/or value. Name [{0}], Value [{1}]
+applicationFilterRegistration.nullInitParams=Unable to set initialisation parameters for filter due to null name and/or value. Name [{0}], Value [{1}]
+applicationRequest.badParent=Cannot locate parent Request implementation
+applicationRequest.badRequest=Request is not a javax.servlet.ServletRequestWrapper
+applicationResponse.badParent=Cannot locate parent Response implementation
+applicationResponse.badResponse=Response is not a javax.servlet.ServletResponseWrapper
+applicationServletRegistration.setServletSecurity.iae=Null constraint specified for servlet [{0}] deployed to context with name [{1}]
+applicationServletRegistration.setServletSecurity.ise=Security constraints can't be added to servlet [{0}] deployed to context with name [{1}] as the context has already been initialised
+aprListener.aprInit=The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: {0}
+aprListener.tcnInvalid=An incompatible version {0} of the APR based Apache Tomcat Native library is installed, while Tomcat requires version {1} 
+aprListener.tcnVersion=An older version {0} of the APR based Apache Tomcat Native library is installed, while Tomcat recommends a minimum version of {1}
+aprListener.aprDestroy=Failed shutdown of APR based Apache Tomcat Native library
+aprListener.sslInit=Failed to initialize the SSLEngine.
+aprListener.tcnValid=Loaded APR based Apache Tomcat Native library {0}.
+aprListener.flags=APR capabilities: IPv6 [{0}], sendfile [{1}], accept filters [{2}], random [{3}].
+containerBase.alreadyStarted=Container {0} has already been started
+containerBase.notConfigured=No basic Valve has been configured
+containerBase.notStarted=Container {0} has not been started
+containerBase.backgroundProcess.cluster=Exception processing cluster {0} background process
+containerBase.backgroundProcess.loader=Exception processing loader {0} background process
+containerBase.backgroundProcess.manager=Exception processing manager {0} background process
+containerBase.backgroundProcess.realm=Exception processing realm {0} background process
+containerBase.backgroundProcess.valve=Exception processing valve {0} background process
+fastEngineMapper.alreadyStarted=FastEngineMapper {0} has already been started
+fastEngineMapper.notStarted=FastEngineMapper {0} has not yet been started
+filterChain.filter=Filter execution threw an exception
+filterChain.servlet=Servlet execution threw an exception
+httpContextMapper.container=This container is not a StandardContext
+httpEngineMapper.container=This container is not a StandardEngine
+httpHostMapper.container=This container is not a StandardHost
+interceptorValve.alreadyStarted=InterceptorValve has already been started
+interceptorValve.notStarted=InterceptorValve has not yet been started
+jreLeakListener.keepAliveFail=Failed to trigger creation of the sun.net.www.http.HttpClient class during Tomcat start to prevent possible memory leaks. This is expected on non-Sun JVMs.
+jreLeakListener.gcDaemonFail=Failed to trigger creation of the GC Daemon thread during Tomcat start to prevent possible memory leaks. This is expected on non-Sun JVMs.
+jreLeakListener.jarUrlConnCacheFail=Failed to disable Jar URL connection caching by default
+jreLeakListener.xmlParseFail=Error whilst attempting to prevent memory leaks during XML parsing
+jreLeakListener.authPolicyFail=Error whilst attempting to prevent memory leak in javax.security.auth.Policy class
+jreLeakListener.ldapPoolManagerFail=Failed to trigger creation of the com.sun.jndi.ldap.LdapPoolManager class during Tomcat start to prevent possible memory leaks. This is expected on non-Sun JVMs.
+naming.wsdlFailed=Failed to find wsdl file: {0}
+naming.bindFailed=Failed to bind object: {0}
+naming.jmxRegistrationFailed=Failed to register in JMX: {0}
+naming.unbindFailed=Failed to unbind object: {0}
+naming.invalidEnvEntryType=Environment entry {0} has an invalid type
+naming.invalidEnvEntryValue=Environment entry {0} has an invalid value
+naming.namingContextCreationFailed=Creation of the naming context failed: {0}
+standardContext.invalidWrapperClass={0} is not a subclass of StandardWrapper
+standardContext.alreadyStarted=Context has already been started
+standardContext.applicationListener=Error configuring application listener of class {0}
+standardContext.applicationSkipped=Skipped installing application listeners due to previous error(s)
+standardContext.badRequest=Invalid request path ({0}).
+standardContext.cluster.noManager=No manager found. Checking if cluster manager should be used. Cluster configured: [{0}], Application distributable: [{1}]
+standardContext.crlfinurl=The URL pattern "{0}" contains a CR or LF and so can never be matched.
+standardContext.duplicateListener=The listener "{0}" is already configured for this context. The duplicate definition has been ignored.
+standardContext.errorPage.error=Error page location {0} must start with a ''/''
+standardContext.errorPage.required=ErrorPage cannot be null
+standardContext.errorPage.warning=WARNING: Error page location {0} must start with a ''/'' in Servlet 2.4
+standardContext.filterMap.either=Filter mapping must specify either a <url-pattern> or a <servlet-name>
+standardContext.filterMap.name=Filter mapping specifies an unknown filter name {0}
+standardContext.filterMap.pattern=Invalid <url-pattern> {0} in filter mapping
+standardContext.filterStart=Exception starting filter {0}
+standardContext.filterStartFailed=Failed to start application Filters successfully
+standardContext.requestListener.requestInit=Exception sending request initialized lifecycle event to listener instance of class {0}
+standardContext.requestListener.requestDestroy=Exception sending request destroyed lifecycle event to listener instance of class {0}
+standardContext.requestListenerStartFailed=Failed to start request listener valve successfully
+standardContext.requestListenerConfig.added=Added request listener Valve
+standardContext.requestListenerConfig.error=Exception adding request listener Valve: {0}
+standardContext.isUnavailable=This application is not currently available
+standardContext.listenerStart=Exception sending context initialized event to listener instance of class {0}
+standardContext.listenerStartFailed=Failed to start application Listeners successfully
+standardContext.listenerStop=Exception sending context destroyed event to listener instance of class {0}
+standardContext.loginConfig.errorPage=Form error page {0} must start with a ''/'
+standardContext.loginConfig.errorWarning=WARNING: Form error page {0} must start with a ''/'' in Servlet 2.4
+standardContext.loginConfig.loginPage=Form login page {0} must start with a ''/'
+standardContext.loginConfig.loginWarning=WARNING: Form login page {0} must start with a ''/'' in Servlet 2.4
+standardContext.loginConfig.required=LoginConfig cannot be null
+standardContext.manager=Configured a manager of class [{0}]
+standardContext.mappingError=MAPPING configuration error for relative URI {0}
+standardContext.namingResource.init.fail=Failed to init new naming resources
+standardContext.namingResource.destroy.fail=Failed to destroy old naming resources
+standardContext.noResourceJar=Resource JARs are not supported. The JAR found at [{0}] will not be used to provide static content for context with name [{1}]
+standardContext.notFound=The requested resource ({0}) is not available.
+standardContext.notReloadable=Reloading is disabled on this Context
+standardContext.notStarted=Context with name [{0}] has not yet been started
+standardContext.notWrapper=Child of a Context must be a Wrapper
+standardContext.parameter.duplicate=Duplicate context initialization parameter {0}
+standardContext.parameter.required=Both parameter name and parameter value are required
+standardContext.pathInvalid=A context path must either be an empty string or start with a ''/''. The path [{0}] does not meet these criteria and has been changed to [{1}] 
+standardContext.reloadingCompleted=Reloading Context with name [{0}] is completed
+standardContext.reloadingFailed=Reloading this Context failed due to previous errors
+standardContext.reloadingStarted=Reloading Context with name [{0}] has started
+standardContext.resourcesStart=Error starting static Resources
+standardContext.securityConstraint.mixHttpMethod=It is not permitted to mix <http-method> and <http-method-omission> in the same web resource collection
+standardContext.securityConstraint.pattern=Invalid <url-pattern> {0} in security constraint
+standardContext.servletMap.name=Servlet mapping specifies an unknown servlet name {0}
+standardContext.servletMap.pattern=Invalid <url-pattern> {0} in servlet mapping
+standardContext.startCleanup=Exception during cleanup after start failed
+standardContext.startFailed=Context [{0}] startup failed due to previous errors
+standardContext.startingContext=Exception starting Context with name [{0}]
+standardContext.startingLoader=Exception starting Loader
+standardContext.startingManager=Exception starting Manager
+standardContext.startingWrapper=Exception starting Wrapper for servlet {0}
+standardContext.stoppingContext=Exception stopping Context with name [{0}]
+standardContext.stoppingLoader=Exception stopping Loader
+standardContext.stoppingManager=Exception stopping Manager
+standardContext.stoppingWrapper=Exception stopping Wrapper for servlet {0}
+standardContext.urlDecode=Cannot URL decode request path {0}
+standardContext.urlPattern.patternWarning=WARNING: URL pattern {0} must start with a ''/'' in Servlet 2.4
+standardContext.urlValidate=Cannot validate URL decoded request path {0}
+standardContext.workPath=Exception obtaining work path for context [{0}]
+standardContext.workCreateException=Failed to determine absolute work directory from directory [{0}] and CATALINA_HOME [{1}] for context [{2}]
+standardContext.workCreateFail=Failed to create work directory [{0}] for context [{1}]
+standardEngine.alreadyStarted=Engine has already been started
+standardEngine.jvmRouteFail=Failed to set Engine's jvmRoute attribute from system property
+standardEngine.mappingError=MAPPING configuration error for server name {0}
+standardEngine.noHost=No Host matches server name {0}
+standardEngine.noHostHeader=HTTP/1.1 request with no Host: header
+standardEngine.notHost=Child of an Engine must be a Host
+standardEngine.notParent=Engine cannot have a parent Container
+standardEngine.notStarted=Engine has not yet been started
+standardEngine.unfoundHost=Virtual host {0} not found
+standardEngine.unknownHost=No server host specified in this request
+standardEngine.unregister.mbeans.failed=Error in destroy() for mbean file {0}
+standardHost.accessBase=Cannot access document base directory {0}
+standardHost.alreadyStarted=Host has already been started
+standardHost.appBase=Application base directory {0} does not exist
+standardHost.clientAbort=Remote Client Aborted Request, IOException: {0}
+standardHost.configRequired=URL to configuration file is required
+standardHost.configNotAllowed=Use of configuration file is not allowed
+standardHost.installBase=Only web applications in the Host web application directory can be installed
+standardHost.installing=Installing web application at context path {0} from URL {1}
+standardHost.installingWAR=Installing web application from URL {0}
+standardHost.installingXML=Processing Context configuration file URL {0}
+standardHost.installError=Error deploying application at context path {0}
+standardHost.invalidErrorReportValveClass=Couldn''t load specified error report valve class: {0}
+standardHost.docBase=Document base directory {0} already exists
+standardHost.mappingError=MAPPING configuration error for request URI {0}
+standardHost.noContext=No Context configured to process this request
+standardHost.noHost=No Host configured to process this request
+standardHost.notContext=Child of a Host must be a Context
+standardHost.notStarted=Host has not yet been started
+standardHost.nullName=Host name is required
+standardHost.pathFormat=Invalid context path: {0}
+standardHost.pathMatch=Context path {0} must match the directory or WAR file name: {1}
+standardHost.pathMissing=Context path {0} is not currently in use
+standardHost.pathRequired=Context path is required
+standardHost.pathUsed=Context path {0} is already in use
+standardHost.removing=Removing web application at context path {0}
+standardHost.removeError=Error removing application at context path {0}
+standardHost.start=Starting web application at context path {0}
+standardHost.stop=Stopping web application at context path {0}
+standardHost.unfoundContext=Cannot find context for request URI {0}
+standardHost.warRequired=URL to web application archive is required
+standardHost.warURL=Invalid URL for web application archive: {0}
+standardServer.onameFail=MBean name specified for Server [{0}] is not valid
+standardServer.shutdownViaPort=A valid shutdown command was received via the shutdown port. Stopping the Server instance.
+standardService.connector.initFailed=Failed to initialize connector [{0}]
+standardService.connector.destroyFailed=Failed to destroy connector [{0}]
+standardService.connector.pauseFailed=Failed to pause connector [{0}]
+standardService.connector.startFailed=Failed to start connector [{0}]
+standardService.connector.stopFailed=Failed to stop connector [{0}]
+standardService.initialize.failed=Service initializing at {0} failed
+standardService.onameFail=MBean name specified for Service [{0}] is not valid
+standardService.register.failed=Error registering Service at domain {0}
+standardService.start.name=Starting service {0}
+standardService.stop.name=Stopping service {0}
+standardThreadExecutor.onameFail=MBean name specified for Thread Executor [{0}] is not valid
+standardWrapper.allocate=Error allocating a servlet instance
+standardWrapper.allocateException=Allocate exception for servlet {0}
+standardWrapper.containerServlet=Loading container servlet {0}
+standardWrapper.createFilters=Create filters exception for servlet {0}
+standardWrapper.deallocateException=Deallocate exception for servlet {0}
+standardWrapper.destroyException=Servlet.destroy() for servlet {0} threw exception
+standardWrapper.exception0=Tomcat Exception Report
+standardWrapper.exception1=A Servlet Exception Has Occurred
+standardWrapper.exception2=Exception Report:
+standardWrapper.exception3=Root Cause:
+standardWrapper.initException=Servlet.init() for servlet {0} threw exception
+standardWrapper.instantiate=Error instantiating servlet class {0}
+standardWrapper.isUnavailable=Servlet {0} is currently unavailable
+standardWrapper.jasperLoader=Using Jasper classloader for servlet {0}
+standardWrapper.jspFile.format=JSP file {0} does not start with a ''/'' character
+standardWrapper.loadException=Servlet {0} threw load() exception
+standardWrapper.missingClass=Wrapper cannot find servlet class {0} or a class it depends on
+standardWrapper.missingLoader=Wrapper cannot find Loader for servlet {0}
+standardWrapper.notChild=Wrapper container may not have child containers
+standardWrapper.notClass=No servlet class has been specified for servlet {0}
+standardWrapper.notContext=Parent container of a Wrapper must be a Context
+standardWrapper.notFound=Servlet {0} is not available
+standardWrapper.notServlet=Class {0} is not a Servlet
+standardWrapper.releaseFilters=Release filters exception for servlet {0}
+standardWrapper.serviceException=Servlet.service() for servlet [{0}] in context with path [{1}] threw exception
+standardWrapper.serviceExceptionRoot=Servlet.service() for servlet [{0}] in context with path [{1}] threw exception [{2}] with root cause
+standardWrapper.statusHeader=HTTP Status {0} - {1}
+standardWrapper.statusTitle=Tomcat Error Report
+standardWrapper.unavailable=Marking servlet {0} as unavailable
+standardWrapper.unloadException=Servlet {0} threw unload() exception
+standardWrapper.unloading=Cannot allocate servlet {0} because it is being unloaded
+standardWrapper.waiting=Waiting for {0} instance(s) to be deallocated
+threadLocalLeakPreventionListener.lifecycleEvent.error=Exception processing lifecycle event {0}
+threadLocalLeakPreventionListener.containerEvent.error=Exception processing container event {0}
+
+defaultInstanceManager.restrictedServletsResource=Restricted servlets property file not found
+defaultInstanceManager.privilegedServlet=Servlet of class {0} is privileged and cannot be loaded by this web application
+defaultInstanceManager.restrictedFiltersResource=Restricted filters property file not found
+defaultInstanceManager.privilegedFilter=Filter of class {0} is privileged and cannot be loaded by this web application
+defaultInstanceManager.restrictedListenersResources="Restricted listeners property file not found
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/core/LocalStrings_es.properties
new file mode 100644
index 0000000..a71ed02
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/LocalStrings_es.properties
@@ -0,0 +1,194 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+applicationContext.attributeEvent = Excepci\u00f3n lanzada por escuchador de eventos de atributos
+applicationContext.mapping.error = Error durante mapeo
+applicationContext.requestDispatcher.iae = La Trayectoria {0} no comienza con car\u00e1cter "/"
+applicationContext.resourcePaths.iae = La Trayectoria {0} no comienza con car\u00e1cter "/"
+applicationContext.setAttribute.namenull = El nombre no puede ser nulo
+applicationDispatcher.allocateException = Excepci\u00f3n de reserva de espacio para servlet {0}
+applicationDispatcher.deallocateException = Excepci\u00f3n de recuperaci\u00f3n de espacio para servlet {0}
+applicationDispatcher.forward.ise = No puedo reenviar despu\u00e9s de que la respuesta se haya llevado a cabo.
+applicationDispatcher.forward.throw = El recurso reenviado lanz\u00f3 un excepci\u00f3n
+applicationDispatcher.include.throw = El recurso inclu\u00eddo lanz\u00f3 una excepci\u00f3n
+applicationDispatcher.isUnavailable = El Servlet {0} no est\u00e1 disponible en este momento
+applicationDispatcher.serviceException = El Servlet.service() para servlet {0} lanz\u00f3 una excepci\u00f3n
+applicationDispatcher.specViolation.request = SevletRequest original o ServletRequest original arropado no pas\u00f3 a RequestDispatcher en violaci\u00f3n de SRV.8.2 y SRV.14.2.5.1
+applicationDispatcher.specViolation.response = SevletResponse original o ServletResponse original arropado no pas\u00f3 a RequestDispatcher en violaci\u00f3n de SRV.8.2 y SRV.14.2.5.1
+applicationRequest.badParent = No puedo localizar la implementaci\u00f3n de Requerimiento padre
+applicationRequest.badRequest = El requerimiento no es un javax.servlet.ServletRequestWrapper
+applicationResponse.badParent = No puedo localizar implementaci\u00f3n de Respuesta padre
+applicationResponse.badResponse = La Respuesta no es un javax.servlet.ServletResponseWrapper
+aprListener.aprInit = La biblioteca nativa de Apache Tomcat basada en ARP que permite un rendimiento \u00f3ptimo en entornos de desarrollo no ha sido hallada en java.library.path\: {0}
+aprListener.tcnInvalid = Se encuentra instalada una versi\u00f3n incompatible {0} de la biblioteca nativa APR de Apache Tomcat, mientras que Tomcat necesita la versi\u00f3n {1}
+aprListener.tcnVersion = Se encuentra instalada una versi\u00f3n muy vieja {0} de la biblioteca nativa APR de Apache Tomcat, mientras que Tomcat recomienda una versi\u00f3n mayor de {1}
+aprListener.aprDestroy = No pude apagar la biblioteca nativa de Apache Tomcat
+aprListener.sslInit = No pude inicializar el SSLEngine (Motor SSL)
+aprListener.tcnValid = Cargada la biblioteca nativa APR de Apache Tomcat {0}
+aprListener.flags = Capacidades APR\: IPv6 [{0}], enviar fichero [{1}], aceptar filtros [{2}], aleatorio [{3}].
+containerBase.alreadyStarted = Ya ha sido arrancado el Contenedor {0}
+containerBase.notConfigured = No se ha configurado V\u00e1lvula b\u00e1sica
+containerBase.notStarted = No se ha arrancado el Contenedor {0}
+containerBase.backgroundProcess.cluster = Excepci\u00f3n al procesar cl\u00faster {0} de proceso en segundo plano
+containerBase.backgroundProcess.loader = Excepci\u00f3n al procesar cargador {0} de proceso en segundo plano
+containerBase.backgroundProcess.manager = Excepci\u00f3n al procesar gestor {0} de proceso en segundo plano
+containerBase.backgroundProcess.realm = Excepci\u00f3n al procesar reino {0} de proceso en segundo plano
+containerBase.backgroundProcess.valve = Excepci\u00f3n al procesar v\u00e1lvula {0} de proceso en segundo plano
+fastEngineMapper.alreadyStarted = Ya se ha arrancado el FastEngineMapper {0}
+fastEngineMapper.notStarted = No se ha arrancado a\u00fan el FastEngineMapper {0}
+filterChain.filter = La ejecuci\u00f3n del Filtro lanz\u00f3 una excepci\u00f3n
+filterChain.servlet = La ejecuci\u00f3n del Servlet lanz\u00f3 una excepci\u00f3n
+httpContextMapper.container = Este Contenedor no es un StandardContext
+httpEngineMapper.container = Este Contenedor no es un StandardEngine
+httpHostMapper.container = Esta Contenedor no es una StandardHost
+interceptorValve.alreadyStarted = Ya ha sido arrancada la InterceptorValve
+interceptorValve.notStarted = A\u00fan no ha sido arrancada la InterceptorValve
+naming.wsdlFailed = No pude hallar fichero wsdl\: {0}
+naming.bindFailed = No pude cambiar (bind) objeto\: {0}
+naming.jmxRegistrationFailed = No pude registrar en JMX\: {0}
+naming.unbindFailed = No pude descambiar (unbind) objecto\: {0}
+naming.invalidEnvEntryType = La entrada de Entorno {0} tiene un tipo inv\u00e1lido
+naming.invalidEnvEntryValue = La entrada de Entorno {0} tiene un valor inv\u00e1lido
+naming.namingContextCreationFailed = Fall\u00f3 la creaci\u00f3n del contexto de nombres (naming)\: {0}
+standardContext.invalidWrapperClass = {0} no es una subclase de StandardWrapper
+standardContext.alreadyStarted = Ya se ha arrancado el Contexto
+standardContext.applicationListener = Error configurando escuchador de aplicaci\u00f3n de clase {0}
+standardContext.applicationSkipped = Se ha saltado la instalaci\u00f3n de escuchadores de aplicaci\u00f3n debido a error(es) previo(s)
+standardContext.badRequest = Trayectoria de requerimiento inv\u00e1lida ({0}).
+standardContext.crlfinurl = El modelo URL "{0}" contiene un CR o LR y por ello nunca coincidir\u00e1.
+standardContext.duplicateListener = El escuchador "{0}" ya est\u00e1 configurado para este contexto. La definici\u00f3n duplicada ha sido ignorada.
+standardContext.errorPage.error = La localizaci\u00f3n de la p\u00e1gina de error 0} debe de comenzar con ''/''
+standardContext.errorPage.required = ErrorPage no puede ser nulo
+standardContext.errorPage.warning = AVISO\: La localizaci\u00f3n de la p\u00e1gina de error {0} debe de comenzar con ''/'' en Servlet 2.4
+standardContext.filterMap.either = El mapeo de filtro debe de especificar o un <url-pattern> o un <servlet-name>
+standardContext.filterMap.name = El mapeo de filtro especifica un nombre desconocido de filtro {0}
+standardContext.filterMap.pattern = <url-pattern> {0} inv\u00e1lido en mapeo de filtro
+standardContext.filterStart = Excepci\u00f3n arrancando filtro {0}
+standardContext.filterStartFailed = No pude arrancar Filtros de aplicaci\u00f3n con \u00e9xito
+standardContext.requestListener.requestInit = Una excepci\u00f3n durante el env\u00edo de requerimiento ha iniciado un evento de ciclo de vida (lifecycle event) para la instancia de clase a la escucha (listener) {0}
+standardContext.requestListener.requestDestroy = Una excepci\u00f3n durante el env\u00edo de requerimiento ha destru\u00eddo un evento de ciclo de vida (lifecycle event) para la instancia de clase a la escucha (listener) {0}
+standardContext.requestListenerStartFailed = No pude arrancar v\u00e1lvula de escuchador de requerimiento con exito
+standardContext.requestListenerConfig.added = A\u00f1adida V\u00e1lvula de escuchador de requerimiento
+standardContext.requestListenerConfig.error = Excepci\u00f3n a\u00f1adiendo V\u00e1lvula de escuchador de requerimiento\: {0}
+standardContext.isUnavailable = Esta aplicaci\u00f3n no est\u00e1 disponible en este momento
+standardContext.listenerStart = Excepci\u00f3n enviando evento inicializado de contexto a instancia de escuchador de clase {0}
+standardContext.listenerStartFailed = No pude arrancar Escuchadores de aplicaci\u00f3n con \u00e9xito
+standardContext.listenerStop = Excepci\u00f3n enviando evento de contexto destru\u00eddo a instancia de escuchador de clase {0}
+standardContext.loginConfig.errorPage = La P\u00e1gina de error de Formulario {0} debe de comenzar con ''/'
+standardContext.loginConfig.errorWarning = AVISO\: La p\u00e1gina de error de Formulario {0} debe de comenzar con ''/'' en Servlet 2.4
+standardContext.loginConfig.loginPage = La p\u00e1gina de login de Formulario {0} debe de comenzar con ''/'
+standardContext.loginConfig.loginWarning = AVISO\: La p\u00e1gina de login de Formulario {0} debe de comenzar con ''/'' en Servlet 2.4
+standardContext.loginConfig.required = LoginConfig no puede ser nula
+standardContext.mappingError = Error de configuraci\u00f3n de MAPEO para URI relativa {0}
+standardContext.notFound = El recurso requerido ({0}) no se encuentra disponible
+standardContext.notReloadable = Est\u00e1 desactivada la recarga en este Contexto
+standardContext.notStarted = A\u00fan no se ha arrancado el Contexto [{0}]
+standardContext.notWrapper = El Hijo de un Contexto debe de ser un Arropador (Wrapper)
+standardContext.parameter.duplicate = Duplicado par\u00e1metro de inicializaci\u00f3n de contexto [{0}]
+standardContext.parameter.required = Es necesario poner nombre de par\u00e1metro y valor de par\u00e1metro
+standardContext.reloadingCompleted = Se ha completado la Regarga de este Contexto
+standardContext.reloadingFailed = Fall\u00f3 la recarga de este Contexto debido a errores previos
+standardContext.reloadingStarted = Ha comenzado la recarga de Contexto [{0}]
+standardContext.resourcesStart = Error arrancando Recursos est\u00e1ticos
+standardContext.securityConstraint.pattern = <url-pattern> {0} inv\u00e1lida en restricci\u00f3n de seguridad
+standardContext.servletMap.name = El mapeo de Servlet especifica un nombre de servlet desconocido {0}
+standardContext.servletMap.pattern = <url-pattern> {0} inv\u00e1lida en mapeo de servlet
+standardContext.startCleanup = Excepci\u00f3n durante la limpieza tras no poder arrancar
+standardContext.startFailed = Fall\u00f3 en arranque del Contexto [{0}] debido a errores previos
+standardContext.startingLoader = Excepci\u00f3n arrancando Cargador
+standardContext.startingManager = Excepci\u00f3n arrancando Gestor
+standardContext.startingWrapper = Excepci\u00f3n arrancando Arropador (Wrapper) para servlet {0}
+standardContext.stoppingContext = Excepci\u00f3n parando Context [{0}]
+standardContext.stoppingLoader = Excepci\u00f3n parando Cargador
+standardContext.stoppingManager = Excepci\u00f3n parando Gestor
+standardContext.stoppingWrapper = Excepci\u00f3n parando Arropador (Wrapper) para servlet {0}
+standardContext.urlDecode = No puedo decodificar URL de trayectoria de requerimiento {0}
+standardContext.urlPattern.patternWarning = AVISO\: el patr\u00f3n URL {0} debe de comenzar con ''/'' en Servlet 2.4
+standardContext.urlValidate = No puedo validar trayectoria de requerimiento de URL decodificada {0}
+standardEngine.alreadyStarted = Ya ha sido arrancado el Motor
+standardEngine.mappingError = Error de configuraci\u00f3n de MAPEO para nombre de servidor {0}
+standardEngine.noHost = No hay M\u00e1quina que coincida con nombre de servidor {0}
+standardEngine.noHostHeader = Requerimiento HTTP/1.1 sin M\u00e1quina\: cabecera
+standardEngine.notHost = El Hijo de un Motor debe de ser un M\u00e1quina
+standardEngine.notParent = El Motor no puede tener un Contenedor padre
+standardEngine.notStarted = A\u00fan no se ha arrancado el Motor
+standardEngine.unfoundHost = M\u00e1quina virtual {0} no hallada
+standardEngine.unknownHost = No se ha especificado m\u00e1quina servidora en este requerimiento
+standardEngine.unregister.mbeans.failed = Error al destruir (destroy()) para fichero mbean {0}
+standardHost.accessBase = No puedo acceder a directorio base de documento {0}
+standardHost.alreadyStarted = Ya ha sido arrancada la M\u00e1quina
+standardHost.appBase = No existe el directorio base de aplicaci\u00f3n {0}
+standardHost.clientAbort = El Cliente Remoto Abort\u00f3 el Requerimiento, IOException\: {0}
+standardHost.configRequired = Es necesario poner la URL a archivo de configuraci\u00f3n
+standardHost.configNotAllowed = No se permite el uso del archivo de configuraci\u00f3n
+standardHost.installBase = S\u00f3lo se pueden instalar aplicaciones web en el directorio de aplicaciones web de M\u00e1quina
+standardHost.installing = Instalando aplicaciones web en trayectoria de contexto {0} desde URL {1}
+standardHost.installingWAR = Instalando aplicaci\u00f3n web desde URL {0}
+standardHost.installingXML = Procesando URL de archivo de configuraci\u00f3n de Contexto {0}
+standardHost.installError = Error desplegando aplicaci\u00f3n en trayectoria de contexto {0}
+standardHost.invalidErrorReportValveClass = No pude cargar clase especifiada de v\u00e1lvula de informe de error\: {0}
+standardHost.docBase = Ya existe el directorio base de documento {0}
+standardHost.mappingError = Error de configuraci\u00f3n de MAPEO para URI de requerimiento {0}
+standardHost.noContext = No se ha configurado Contexto para procesar este requerimiento
+standardHost.noHost = No se ha configurado M\u00e1quina para procesar este requerimiento
+standardHost.notContext = El Hijo de una M\u00e1quina debe de ser un Contexto
+standardHost.notStarted = A\u00fan no se ha arrancado la M\u00e1quina
+standardHost.nullName = Es necesario poner el nombre de M\u00e1quina
+standardHost.pathFormat = Trayectoria de contexto inv\u00e1lida\: {0}
+standardHost.pathMatch = La trayectoria de Contexto {0} debe de coincidir con el nombre de directorio o de archivo WAR\: {1}
+standardHost.pathMissing = La trayectoria de Contexto {0} no est\u00e1 en uso en este momento
+standardHost.pathRequired = Es necesario poner la trayectoria de Contexto
+standardHost.pathUsed = Ya est\u00e1 en uso la trayectoria de Contexto {0}
+standardHost.removing = Quitando aplicaci\u00f3n web en trayectoria de contexto {0}
+standardHost.removeError = Error quitando aplicaci\u00f3n en trayectoria de contexto {0}
+standardHost.start = Arrancando aplicaci\u00f3n web en trayectoria de contexto {0}
+standardHost.stop = Parando aplicaci\u00f3n web en trayectoria de contexto {0}
+standardHost.unfoundContext = No puedo hallar contexto para URI de requerimiento {0}
+standardHost.warRequired = Es necesario poner la URL a archivo de aplicaci\u00f3n web
+standardHost.warURL = URL inv\u00e1lida para archivo de aplicaci\u00f3n web\: {0}
+standardService.initialize.failed = Servicio inicializando en {0} fall\u00f3
+standardService.register.failed = Error registrando servicio en dominio {0}
+standardService.start.name = Arrancando servicio {0}
+standardService.stop.name = Parando servicio {0}
+standardWrapper.allocate = Error reservando espacio para una instancia de servlet
+standardWrapper.allocateException = Excepci\u00f3n de reserva de espacio para servlet {0}
+standardWrapper.containerServlet = Cargando servlet de contenedor {0}
+standardWrapper.createFilters = Excepci\u00f3n de creaci\u00f3n de filtros para servlet {0}
+standardWrapper.deallocateException = Excepci\u00f3n de recuperaci\u00f3n de espacio para servlet {0}
+standardWrapper.destroyException = Servlet.destroy() para servlet {0} lanz\u00f3 excepci\u00f3n
+standardWrapper.exception0 = Informe de Excepci\u00f3n de Tomcat
+standardWrapper.exception1 = Ha tenido lugar una Excepci\u00f3n de Servlet
+standardWrapper.exception2 = Informe de Excepci\u00f3n\:
+standardWrapper.exception3 = Causa Ra\u00edz\:
+standardWrapper.initException = Servlet.init() para servlet {0} lanz\u00f3 excepci\u00f3n
+standardWrapper.instantiate = Error instanciando clase de servlet {0}
+standardWrapper.isUnavailable = El Servlet {0} no est\u00e1 disponible en este momento
+standardWrapper.jasperLoader = Usando cargador de clases (classloader) de Jasper para servlet {0}
+standardWrapper.jspFile.format = El archivo JSP {0} no comienza con car\u00e1cter ''/''
+standardWrapper.loadException = El Servlet {0} lanz\u00f3 excepci\u00f3n de load()
+standardWrapper.missingClass = El Arropador (Wrapper) no puede hallar clase de servlet {0} o una clase de la que depende
+standardWrapper.missingLoader = El Arropador (Wrapper) no puede hallar Cargador para servlet {0}
+standardWrapper.notChild = El contenedor de Arropador (Wrapper) no puede tener contenedores hijo
+standardWrapper.notClass = No se ha especificado clase de servlet para servlet {0}
+standardWrapper.notContext = El contenedor padre para un Arropador (Wrapper) debe de ser un Contexto
+standardWrapper.notFound = No est\u00e1 disponible el Servlet {0}
+standardWrapper.notServlet = La Clase {0} no es un Servlet
+standardWrapper.releaseFilters = Excepci\u00f3n de Liberaci\u00f3n de filtros para servlet {0}
+standardWrapper.serviceException = Servlet.service() para servlet {0} lanz\u00f3 excepci\u00f3n
+standardWrapper.statusHeader = HTTP Estado {0} - {1}
+standardWrapper.statusTitle = Informe de Error de Tomcat
+standardWrapper.unavailable = Marcando el servlet {0} como no disponible
+standardWrapper.unloadException = El Servlet {0} lanz\u00f3 excepci\u00f3n unload()
+standardWrapper.unloading = No puedo reservar espacio para servlet {0} porque est\u00e1 siendo descargado
+standardWrapper.waiting = Esperando por {0} instancia(s) para recuperar su espacio reservado
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/core/LocalStrings_fr.properties
new file mode 100644
index 0000000..bd1c3fa
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/LocalStrings_fr.properties
@@ -0,0 +1,167 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+applicationContext.attributeEvent=Exception lanc\u00e9e par l''\u00e9couteur (listener) d''\u00e9v\u00e8nement attributs
+applicationContext.requestDispatcher.iae=Le chemin {0} ne commence pas par le caract\u00e8re "/"
+applicationContext.setAttribute.namenull=le nom ne peut \u00eatre nul
+applicationDispatcher.allocateException=Exception d''allocation pour la servlet {0}
+applicationDispatcher.deallocateException=Exception de d\u00e9sallocation pour la servlet {0}
+applicationDispatcher.forward.ise=Impossible d''utiliser faire-suivre (forward) apr\u00e8s que la r\u00e9ponse ait \u00e9t\u00e9 envoy\u00e9e
+applicationDispatcher.forward.throw=La ressource faire-suivre (forwarded) a lanc\u00e9 une exception
+applicationDispatcher.include.throw=La ressource incluse (included) a lanc\u00e9 une exception
+applicationDispatcher.isUnavailable=La servlet {0} est actuellement indisponible
+applicationDispatcher.serviceException="Servlet.service()" pour la servlet {0} a lanc\u00e9 une exception
+applicationRequest.badParent=Impossible de trouver l''impl\u00e9mentation requ\u00eate parente (parent request)
+applicationRequest.badRequest=La requ\u00eate n''est pas une "javax.servlet.ServletRequestWrapper"
+applicationResponse.badParent=Impossible de trouver une impl\u00e9mentation r\u00e9ponse parente (parent response)
+applicationResponse.badResponse=La r\u00e9ponse n''est pas une "javax.servlet.ServletResponseWrapper"
+containerBase.alreadyStarted=Le conteneur {0} a d\u00e9j\u00e0 \u00e9t\u00e9 d\u00e9marr\u00e9
+containerBase.notConfigured=Aucune Valve basique (basic valve) n''a \u00e9t\u00e9 configur\u00e9e
+containerBase.notStarted=Le conteneur {0} n''a pas \u00e9t\u00e9 d\u00e9marr\u00e9
+fastEngineMapper.alreadyStarted=le "FastEngineMapper" {0} a d\u00e9j\u00e0 \u00e9t\u00e9 d\u00e9marr\u00e9
+fastEngineMapper.notStarted=Le "FastEngineMapper" {0} n''a pas encore \u00e9t\u00e9 d\u00e9marr\u00e9
+filterChain.filter=L''ex\u00e9cution du filtre (Filter) a lanc\u00e9 une exception
+filterChain.servlet=L''ex\u00e9cution de la servlet a lanc\u00e9 une exception
+httpContextMapper.container=Ce conteneur n''est pas un "StandardContext"
+httpEngineMapper.container=Ce conteneur n''est pas un "StandardEngine"
+httpHostMapper.container=Ce conteneur n''est pas un "StandardHost"
+interceptorValve.alreadyStarted=La valve d''interception (InterceptorValve) a d\u00e9j\u00e0 \u00e9t\u00e9 d\u00e9marr\u00e9e
+interceptorValve.notStarted=La valve d''interception (InterceptorValve) n''a pas encore \u00e9t\u00e9 d\u00e9marr\u00e9e
+naming.bindFailed=Echec lors du liage \u00e0 l''objet: {0}
+naming.unbindFailed=Echec lors du d\u00e9liage \u00e0 l''objet : {0}
+naming.invalidEnvEntryType=L''entr\u00e9e environnement {0} a un type invalide
+naming.invalidEnvEntryValue=L''entr\u00e9e environnement {0} a une valeur invalide
+naming.namingContextCreationFailed=La cr\u00e9ation du contexte de nommage (naming context) a \u00e9chou\u00e9 : {0}
+standardContext.alreadyStarted=Le contexte a d\u00e9j\u00e0 \u00e9t\u00e9 d\u00e9marr\u00e9
+standardContext.applicationListener=Erreur lors de la configuration de la classe d''\u00e9coute de l''application (application listener) {0}
+standardContext.applicationSkipped=L''installation des \u00e9couteurs (listeners) de l''application a \u00e9t\u00e9 saut\u00e9e suite aux erreurs pr\u00e9c\u00e9dentes
+standardContext.badRequest=Chemin de requ\u00eate invalide ({0}).
+standardContext.errorPage.error=La position de la page d''erreur (ErrorPage) {0} doit commencer par un ''/'
+standardContext.errorPage.required=La page d''erreur (ErrorPage) ne peut \u00eatre nulle
+standardContext.errorPage.warning=ATTENTION: La position de la page d''erreur (ErrorPage) {0} doit commencer par un ''/'' dans l''API Servlet 2.4
+standardContext.filterMap.either=L''association de filtre (filter mapping) doit indiquer soit une <url-pattern> soit une <servlet-name>
+standardContext.filterMap.name=L''association de filtre (filter mapping) indique un nom de filtre inconnu {0}
+standardContext.filterMap.pattern=<url-pattern> {0} invalide dans l''association de filtre (filter mapping)
+standardContext.filterStart=Exception au d\u00e9marrage du filtre {0}
+standardContext.filterStartFailed=Echec du d\u00e9marrage des filtres d''application
+standardContext.requestListenerStartFailed=Echec d\u00e9marrage des Valves d''\u00e9coute
+standardContext.requestListenerConfig.added=Ajout de la valve d''\u00e9coute
+standardContext.requestListenerConfig.error=Exception lors de l''ajout de la valve d''\u00e9coute de requ\u00eate: {0}
+standardContext.isUnavailable=Cette application n''est pas disponible actuellement
+standardContext.listenerStart=Exception lors de l''envoi de l''\u00e9v\u00e8nement contexte initialis\u00e9 (context initialized) \u00e0 l''instance de classe d''\u00e9coute (listener) {0}
+standardContext.listenerStartFailed=Echec du d\u00e9marrage des \u00e9couteurs (listeners) d''application
+standardContext.listenerStop=Exception lors de l''envoi de l''\u00e9v\u00e8nement contexte d\u00e9truit (context destroyed) \u00e0 l''instance de classe d''\u00e9coute {0}
+standardContext.loginConfig.errorPage=La forme de page d''erreur (form error page) {0} doit commencer par un ''/''
+standardContext.loginConfig.errorWarning=ATTENTION: La forme de page d''erreur (form error page) {0} doit commencer par un ''/'' dans l''API Servlet 2.4
+standardContext.loginConfig.loginPage=La forme de page de connexion (form login page) {0} doit commencer par un ''/''
+standardContext.loginConfig.loginWarning=ATTENTION: La forme de page de connexion (form login page) {0} doit commencer par un ''/'' dans l''API Servlet 2.4
+standardContext.loginConfig.required="LoginConfig" ne peut \u00eatre nul
+standardContext.mappingError=Erreur dans la configuration d''association (mapping configuration) pour l''URI relative {0}
+standardContext.notFound=La ressource demand\u00e9e ({0}) n''est pas disponible.
+standardContext.notReloadable=Le rechargement est d\u00e9sactiv\u00e9 pour ce contexte
+standardContext.notStarted=Le contexte [{0}] n''a pas encore \u00e9t\u00e9 d\u00e9marr\u00e9
+standardContext.notWrapper=Le fils du contexte (child of context) doit \u00eatre un enrobeur (wrapper)
+standardContext.parameter.duplicate=Param\u00e8tre d''initialisation de contexte dupliqu\u00e9 {0}
+standardContext.parameter.required=Le nom de param\u00e8tre ainsi que la valeur du param\u00e8tre sont requis
+standardContext.reloadingCompleted=Le rechargement de ce contexte est termin\u00e9
+standardContext.reloadingFailed=Le rechargement de ce contexte a \u00e9chou\u00e9 suite \u00e0 une erreur pr\u00e9c\u00e9dente
+standardContext.reloadingStarted=Le rechargement du contexte [{0}] a d\u00e9marr\u00e9
+standardContext.requestListener.requestInit=Une exception lors de l''envoi de requ\u00eate a initi\u00e9 un \u00e9v\u00e8nement cycle de vie (lifecycle event) pour l''instance de classe \u00e0 l''\u00e9coute (listener) {0}
+standardContext.requestListener.requestDestroy=Une exception lors de l''envoi de requ\u00eate a d\u00e9truit un \u00e9v\u00e8nement cycle de vie (lifecycle event) pour l''instance de classe \u00e0 l''\u00e9coute (listener) {0}
+standardContext.securityConstraint.pattern=<url-pattern> {0} invalide d''apr\u00e8s les contraintes de s\u00e9curit\u00e9 (security constraint)
+standardContext.servletMap.name=L''association de servlet (servlet mapping) indique un nom de servlet inconnu {0}
+standardContext.servletMap.pattern=<url-pattern> {0} invalide dans l''association de servlet (servlet mapping)
+standardContext.startCleanup=Exception lors du nettoyage apr\u00e8s que le d\u00e9marrage ait \u00e9chou\u00e9
+standardContext.startFailed=Erreur de d\u00e9marrage du contexte [{0}] suite aux erreurs pr\u00e9c\u00e9dentes
+standardContext.startingContext=Exception lors du d\u00e9marrage du contexte [{0}]
+standardContext.startingLoader=Exception an d\u00e9marrage du "Loader"
+standardContext.startingManager=Exception an d\u00e9marrage du "Manager"
+standardContext.startingWrapper=Exception an d\u00e9marrage de l''enrobeur (wrapper) de la servlet {0}
+standardContext.stoppingContext=Exception \u00e0 l''arr\u00eat du Context [{0}]
+standardContext.stoppingLoader=Exception \u00e0 l''arr\u00eat du "Loader"
+standardContext.stoppingManager=Exception \u00e0 l''arr\u00eat du "Manager"
+standardContext.stoppingWrapper=Exception \u00e0 l''arr\u00eat de l''enrobeur (wrapper) de la servlet {0}
+standardContext.resourcesStart=Erreur lors du d\u00e9marrage des ressources statiques
+standardContext.urlDecode=Impossible de d\u00e9coder le chemin de requ\u00eate encod\u00e9 dans l''URL {0}
+standardContext.urlPattern.patternWarning=ATTENTION: Le mod\u00e8le (pattern) URL {0} doit commencer par un ''/'' dans l''API Servlet 2.4
+standardContext.urlValidate=Impossible de valider le chemin de requ\u00eate encod\u00e9 dans l''URL {0}
+standardEngine.alreadyStarted=Le moteur a d\u00e9j\u00e0 \u00e9t\u00e9 d\u00e9marr\u00e9
+standardEngine.mappingError=Erreur de configuration d''association (mapping configuration) pour le serveur {0}
+standardEngine.noHost=Aucune h\u00f4te (host) ne correspond au nom de serveur {0}
+standardEngine.noHostHeader=requ\u00eate HTTP/1.1 sans ent\u00eate Host:
+standardEngine.notHost=Le fils d''un moteur (child of an Engine) doit \u00eatre un h\u00f4te
+standardEngine.notParent=Un moteur (engine) ne peut avoir de conteneur parent (container)
+standardEngine.notStarted=Le moteur n''a pas encore \u00e9t\u00e9 d\u00e9marr\u00e9
+standardEngine.unfoundHost=L''h\u00f4te virtuel (virtual host) {0} est introuvable
+standardEngine.unknownHost=Aucun serveur h\u00f4te n''est indiqu\u00e9 pour cette requ\u00eate
+standardHost.accessBase=Impossible d''acc\u00e9der le r\u00e9pertoire "document base" {0}
+standardHost.alreadyStarted=L''h\u00f4te a d\u00e9j\u00e0 \u00e9t\u00e9 d\u00e9marr\u00e9
+standardHost.appBase=Le r\u00e9pertoire de base de l''application {0} n''existe pas
+standardHost.configRequired=Une URL vers le fichier de configuration est obligatoire
+standardHost.configNotAllowed=L''utilisation d''un fichier de configuration n''est pas autoris\u00e9e
+standardHost.installing=Installation d''une application pour le chemin de contexte {0} depuis l''URL {1}
+standardHost.installingWAR=Installation d''une application depuis l''URL {0}
+standardHost.installError=Erreur lors du d\u00e9ploiement de l''application pour le chemin de contexte {0}
+standardHost.invalidErrorReportValveClass=Impossible de charger la classe valve de rapport d''erreur: {0}
+standardHost.docBase=Le r\u00e9pertoire "document base" {0} existe d\u00e9j\u00e0
+standardHost.mappingError=Erreur d''association de configuration (mapping configuration) pour l''URI demand\u00e9e {0}
+standardHost.noContext=Aucun contexte n''est configur\u00e9 pour traiter cette requ\u00eate
+standardHost.noHost=Aucun h\u00f4te n''est configur\u00e9 pour traiter cette requ\u00eate
+standardHost.notContext=Le fils d''un h\u00f4te (child of a Host) doit \u00eatre un contexte
+standardHost.notStarted=l''h\u00f4te n''a pas encore \u00e9t\u00e9 d\u00e9marr\u00e9
+standardHost.nullName=Le nom d''h\u00f4te est requis
+standardHost.pathFormat=Chemin de contexte invalide: {0}
+standardHost.pathMissing=Le chemin de contexte {0} n''est pas utilis\u00e9 actuellement
+standardHost.pathRequired=Le chemin de contexte est requis
+standardHost.pathUsed=Le chemin de contexte {0} est d\u00e9j\u00e0 utilis\u00e9
+standardHost.removing=Retrait de l''application web pour le chemin de contexte {0}
+standardHost.removeError=Erreur lors du retrait de l''application web pour le chemin de contexte {0}
+standardHost.start=D\u00e9marrage de l''application web pour le chemin de contexte {0}
+standardHost.stop=Arr\u00eat de l''application web pour le chemin de contexte {0}
+standardHost.unfoundContext=Impossible de trouver un contexte pour l''URI {0} demand\u00e9e
+standardHost.warRequired=Une URL vers l''archive d''application web (war) est n\u00e9cessaire
+standardHost.warURL=URL vers l''archive d''application web (war) invalide: {0}
+standardService.start.name=D\u00e9marrage du service {0}
+standardService.stop.name=Arr\u00eat du service {0}
+standardWrapper.allocate=Erreur d''allocation \u00e0 une instance de servlet
+standardWrapper.allocateException=Exception lors de l''allocation pour la servlet {0}
+standardWrapper.containerServlet=Chargement du conteneur (container) de servlet {0}
+standardWrapper.createFilters=Exception \u00e0 la cr\u00e9ation de filtres pour la servlet {0}
+standardWrapper.deallocateException=Exception \u00e0 la d\u00e9sallocation pour la servlet {0}
+standardWrapper.destroyException="Servlet.destroy()" de la servlet {0} a g\u00e9n\u00e9r\u00e9 une exception
+standardWrapper.exception0=Rapport d''exception Tomcat
+standardWrapper.exception1=Une exception Servlet s''est produite
+standardWrapper.exception2=Rapport d''exception:
+standardWrapper.exception3=Cause m\u00e8re:
+standardWrapper.initException="Servlet.init()" pour la servlet {0} a g\u00e9n\u00e9r\u00e9 une exception
+standardWrapper.instantiate=Erreur \u00e0 l''instantiation de la classe servlet {0}
+standardWrapper.isUnavailable=La servlet {0} est actuellement indisponible
+standardWrapper.jasperLoader=Utilisation du chargeur de classe Jasper (classloader) pour la servlet {0}
+standardWrapper.jspFile.format=Le fichier JSP {0} ne commence par par un caract\u00e8re ''/''
+standardWrapper.loadException=La servlet {0} a g\u00e9n\u00e9r\u00e9 une exception "load()"
+standardWrapper.missingClass=L''enrobeur (wrapper) ne peut trouver la classe servlet {0} ou une classe dont elle d\u00e9pend
+standardWrapper.missingLoader=L''enrobeur (wrapper) ne peut trouver de chargeur (loader) pour la servlet {0}
+standardWrapper.notChild=L''enrobeur de conteneur (wrapper container) peut ne pas avoir de conteneurs fils
+standardWrapper.notClass=Aucune classe servlet n''a \u00e9t\u00e9 sp\u00e9cifi\u00e9e pour la servlet {0}
+standardWrapper.notContext=Le conteneur parent d''un enrobeur (wrapper) doit \u00eatre un contexte
+standardWrapper.notFound=Servlet {0} n''est pas disponible.
+standardWrapper.notServlet=La classe {0} n''est pas une servlet
+standardWrapper.releaseFilters=Exception des filtres de sortie (release filters) pour la servlet {0}
+standardWrapper.serviceException="Servlet.service()" pour la servlet {0} a g\u00e9n\u00e9r\u00e9 une exception
+standardWrapper.statusHeader=Etat HTTP {0} - {1}
+standardWrapper.statusTitle=Rapport d''erreur Tomcat
+standardWrapper.unavailable=La servlet {0} est marqu\u00e9 comme indisponible
+standardWrapper.unloadException=La servlet {0} a g\u00e9n\u00e9r\u00e9 une exception "unload()"
+standardWrapper.unloading=Impossible d''allouer la servlet {0} car elle a \u00e9t\u00e9 d\u00e9charg\u00e9e
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/LocalStrings_ja.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/core/LocalStrings_ja.properties
new file mode 100644
index 0000000..92e7e6e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/LocalStrings_ja.properties
@@ -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.
+
+applicationContext.attributeEvent=\u5c5e\u6027\u30a4\u30d9\u30f3\u30c8\u30ea\u30b9\u30ca\u306b\u3088\u3063\u3066\u4f8b\u5916\u304c\u6295\u3052\u3089\u308c\u307e\u3057\u305f
+applicationContext.mapping.error=\u30de\u30c3\u30d4\u30f3\u30b0\u4e2d\u306e\u30a8\u30e9\u30fc\u3067\u3059
+applicationContext.requestDispatcher.iae=\u30d1\u30b9 {0} \u304c"/"\u6587\u5b57\u3067\u59cb\u307e\u308a\u307e\u305b\u3093
+applicationContext.setAttribute.namenull=name\u304cnull\u3067\u306f\u3044\u3051\u307e\u305b\u3093
+applicationDispatcher.allocateException=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8 {0} \u306b\u4f8b\u5916\u3092\u5272\u308a\u5f53\u3066\u307e\u3059
+applicationDispatcher.deallocateException=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8 {0} \u306e\u4f8b\u5916\u3092\u89e3\u9664\u3057\u307e\u3059
+applicationDispatcher.forward.ise=\u30ec\u30b9\u30dd\u30f3\u30b9\u3092\u30b3\u30df\u30c3\u30c8\u3057\u305f\u5f8c\u3067\u30d5\u30a9\u30ef\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093
+applicationDispatcher.forward.throw=\u30d5\u30a9\u30ef\u30fc\u30c9\u3057\u305f\u30ea\u30bd\u30fc\u30b9\u304c\u4f8b\u5916\u3092\u6295\u3052\u307e\u3057\u305f
+applicationDispatcher.include.throw=\u30a4\u30f3\u30af\u30eb\u30fc\u30c9\u3057\u305f\u30ea\u30bd\u30fc\u30b9\u304c\u4f8b\u5916\u3092\u6295\u3052\u307e\u3057\u305f
+applicationDispatcher.isUnavailable=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8 {0} \u306f\u73fe\u5728\u5229\u7528\u3067\u304d\u307e\u305b\u3093
+applicationDispatcher.serviceException=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8 {0} \u306eServlet.service()\u304c\u4f8b\u5916\u3092\u6295\u3052\u307e\u3057\u305f
+applicationRequest.badParent=\u89aa\u306e\u30ea\u30af\u30a8\u30b9\u30c8\u5b9f\u88c5\u3092\u914d\u7f6e\u3067\u304d\u307e\u305b\u3093
+applicationRequest.badRequest=\u30ea\u30af\u30a8\u30b9\u30c8\u304cjavax.servlet.ServletRequestWrapper\u3067\u306f\u3042\u308a\u307e\u305b\u3093
+applicationResponse.badParent=\u89aa\u306e\u30ec\u30b9\u30dd\u30f3\u30b9\u5b9f\u88c5\u3092\u914d\u7f6e\u3067\u304d\u307e\u305b\u3093
+applicationResponse.badResponse=\u30ec\u30b9\u30dd\u30f3\u30b9\u304cjavax.servlet.ServletResponseWrapper\u3067\u306f\u3042\u308a\u307e\u305b\u3093
+containerBase.alreadyStarted=\u30b3\u30f3\u30c6\u30ca {0} \u306f\u65e2\u306b\u8d77\u52d5\u3055\u308c\u3066\u3044\u307e\u3059
+containerBase.notConfigured=\u57fa\u672c\u30d0\u30eb\u30d6\u304c\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+containerBase.notStarted=\u30b3\u30f3\u30c6\u30ca {0} \u306f\u307e\u3060\u8d77\u52d5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+fastEngineMapper.alreadyStarted=FastEngineMapper {0} \u306f\u65e2\u306b\u8d77\u52d5\u3055\u308c\u3066\u3044\u307e\u3059
+fastEngineMapper.notStarted=FastEngineMapper {0} \u306f\u307e\u3060\u8d77\u52d5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+filterChain.filter=\u30d5\u30a3\u30eb\u30bf\u306e\u5b9f\u884c\u306b\u3088\u308a\u4f8b\u5916\u3092\u6295\u3052\u307e\u3057\u305f
+filterChain.servlet=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u306e\u5b9f\u884c\u306b\u3088\u308a\u4f8b\u5916\u3092\u6295\u3052\u307e\u3057\u305f
+httpContextMapper.container=\u3053\u306e\u30b3\u30f3\u30c6\u30ca\u306fStandardContext\u3067\u306f\u3042\u308a\u307e\u305b\u3093
+httpEngineMapper.container=\u3053\u306e\u30b3\u30f3\u30c6\u30ca\u306fStandardEngine\u3067\u306f\u3042\u308a\u307e\u305b\u3093
+httpHostMapper.container=\u3053\u306e\u30b3\u30f3\u30c6\u30ca\u306fStandardHost\u3067\u306f\u3042\u308a\u307e\u305b\u3093
+interceptorValve.alreadyStarted=InterceptorValve\u306f\u65e2\u306b\u8d77\u52d5\u3055\u308c\u3066\u3044\u307e\u3059
+interceptorValve.notStarted=InterceptorValve\u306f\u307e\u3060\u8d77\u52d5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+naming.bindFailed=\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u306e\u30d0\u30a4\u30f3\u30c9\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {0}
+naming.unbindFailed=\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u306e\u30a2\u30f3\u30d0\u30a4\u30f3\u30c9\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {0}
+naming.invalidEnvEntryType=\u74b0\u5883\u30a8\u30f3\u30c8\u30ea {0} \u306f\u7121\u52b9\u306a\u578b\u3092\u6301\u3063\u3066\u3044\u307e\u3059
+naming.invalidEnvEntryValue=\u74b0\u5883\u30a8\u30f3\u30c8\u30ea {0} \u306f\u7121\u52b9\u306a\u5024\u3092\u6301\u3063\u3066\u3044\u307e\u3059
+naming.namingContextCreationFailed=\u540d\u524d\u4ed8\u304d\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306e\u751f\u6210\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {0}
+standardContext.alreadyStarted=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306f\u65e2\u306b\u8d77\u52d5\u3055\u308c\u3066\u3044\u307e\u3059
+standardContext.applicationListener=\u30af\u30e9\u30b9 {0} \u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30ea\u30b9\u30ca\u306e\u8a2d\u5b9a\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
+standardContext.applicationSkipped=\u524d\u306e\u30a8\u30e9\u30fc\u306e\u305f\u3081\u306b\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30ea\u30b9\u30ca\u306e\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u30b9\u30ad\u30c3\u30d7\u3057\u307e\u3059
+standardContext.badRequest=\u7121\u52b9\u306a\u30ea\u30af\u30a8\u30b9\u30c8\u30d1\u30b9\u3067\u3059 ({0})\u3002
+standardContext.errorPage.error=\u30a8\u30e9\u30fc\u30da\u30fc\u30b8\u306e\u4f4d\u7f6e {0} \u306f''/''\u3067\u59cb\u307e\u3089\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+standardContext.errorPage.required=ErrorPage\u304cnull\u3067\u306f\u3044\u3051\u307e\u305b\u3093
+standardContext.errorPage.warning=\u8b66\u544a: Servlet 2.4\u3067\u306f\u30a8\u30e9\u30fc\u30da\u30fc\u30b8\u306e\u4f4d\u7f6e {0} \u306f''/''\u3067\u59cb\u307e\u3089\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+standardContext.filterMap.either=\u30d5\u30a3\u30eb\u30bf\u30de\u30c3\u30d4\u30f3\u30b0\u306f<url-pattern>\u53c8\u306f<servlet-name>\u306e\u3069\u3061\u3089\u304b\u3092\u6307\u5b9a\u3057\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+standardContext.filterMap.name=\u30d5\u30a3\u30eb\u30bf\u30de\u30c3\u30d4\u30f3\u30b0\u306f\u672a\u77e5\u306e\u30d5\u30a3\u30eb\u30bf\u540d {0} \u3092\u6307\u5b9a\u3057\u307e\u3057\u305f
+standardContext.filterMap.pattern=\u30d5\u30a3\u30eb\u30bf\u30de\u30c3\u30d4\u30f3\u30b0\u4e2d\u306b\u7121\u52b9\u306a <url-pattern> {0} \u304c\u3042\u308a\u307e\u3059
+standardContext.filterStart=\u30d5\u30a3\u30eb\u30bf {0} \u306e\u8d77\u52d5\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+standardContext.filterStartFailed=\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30d5\u30a3\u30eb\u30bf\u306e\u8d77\u52d5\u306b\u5931\u6557\u3057\u307e\u3057\u305f
+standardContext.requestListenerStartFailed=\u30ea\u30af\u30a8\u30b9\u30c8\u30ea\u30b9\u30ca\u30d0\u30eb\u30d6\u306e\u8d77\u52d5\u306b\u5931\u6557\u3057\u307e\u3057\u305f
+standardContext.requestListenerConfig.added=\u30ea\u30af\u30a8\u30b9\u30c8\u30ea\u30b9\u30ca\u30d0\u30eb\u30d6\u3092\u8ffd\u52a0\u3057\u307e\u3057\u305f
+standardContext.requestListenerConfig.error=\u30ea\u30af\u30a8\u30b9\u30c8\u30ea\u30b9\u30ca\u30d0\u30eb\u30d6\u8ffd\u52a0\u4e2d\u306e\u4f8b\u5916\u3067\u3059: {0}
+standardContext.isUnavailable=\u3053\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306f\u73fe\u5728\u5229\u7528\u3067\u304d\u307e\u305b\u3093
+standardContext.listenerStart=\u30af\u30e9\u30b9 {0} \u306e\u30ea\u30b9\u30ca\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u306b\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u521d\u671f\u5316\u30a4\u30d9\u30f3\u30c8\u3092\u9001\u4fe1\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+standardContext.listenerStartFailed=\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30ea\u30b9\u30ca\u306e\u8d77\u52d5\u306b\u5931\u6557\u3057\u307e\u3057\u305f
+standardContext.listenerStop=\u30af\u30e9\u30b9 {0} \u306e\u30ea\u30b9\u30ca\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u306b\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u7834\u68c4\u30a4\u30d9\u30f3\u30c8\u3092\u9001\u4fe1\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+standardContext.loginConfig.errorPage=\u30d5\u30a9\u30fc\u30e0\u306e\u30a8\u30e9\u30fc\u30da\u30fc\u30b8 {0} \u306f''/''\u3067\u59cb\u307e\u3089\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+standardContext.loginConfig.errorWarning=\u8b66\u544a: Servlet 2.4\u3067\u306f\u30d5\u30a9\u30fc\u30e0\u306e\u30a8\u30e9\u30fc\u30da\u30fc\u30b8 {0} \u306f''/''\u3067\u59cb\u307e\u3089\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+standardContext.loginConfig.loginPage=\u30d5\u30a9\u30fc\u30e0\u306e\u30ed\u30b0\u30a4\u30f3\u30da\u30fc\u30b8 {0} \u306f''/''\u3067\u59cb\u307e\u3089\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+standardContext.loginConfig.loginWarning=\u8b66\u544a: Servlet 2.4\u3067\u306f\u30d5\u30a9\u30fc\u30e0\u306e\u30ed\u30b0\u30a4\u30f3\u30da\u30fc\u30b8 {0} \u306f''/''\u3067\u59cb\u307e\u3089\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+standardContext.loginConfig.required=LoginConfig\u306fnull\u3067\u306f\u3044\u3051\u307e\u305b\u3093
+standardContext.mappingError=\u76f8\u5bfeURI {0} \u306e\u30de\u30c3\u30d4\u30f3\u30b0\u8a2d\u5b9a\u30a8\u30e9\u30fc\u3067\u3059
+standardContext.notFound=\u30ea\u30af\u30a8\u30b9\u30c8\u3055\u308c\u305f\u30ea\u30bd\u30fc\u30b9 ({0}) \u306f\u5229\u7528\u3067\u304d\u307e\u305b\u3093\u3002
+standardContext.notReloadable=\u3053\u306e\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u3067\u306f\u518d\u30ed\u30fc\u30c9\u306f\u7121\u52b9\u3067\u3059
+standardContext.notStarted=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306f\u307e\u3060\u8d77\u52d5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+standardContext.notWrapper=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306e\u5b50\u4f9b\u306f\u30e9\u30c3\u30d1\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+standardContext.parameter.duplicate=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u521d\u671f\u5316\u30d1\u30e9\u30e1\u30bf {0} \u304c\u91cd\u8907\u3057\u3066\u3044\u307e\u3059
+standardContext.parameter.required=\u30d1\u30e9\u30e1\u30bf\u540d\u3068\u30d1\u30e9\u30e1\u30bf\u5024\u306e\u4e21\u65b9\u304c\u5fc5\u8981\u3067\u3059
+standardContext.reloadingCompleted=\u3053\u306e\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306e\u518d\u30ed\u30fc\u30c9\u304c\u5b8c\u4e86\u3057\u307e\u3057\u305f
+standardContext.reloadingFailed=\u4ee5\u524d\u306e\u30a8\u30e9\u30fc\u306e\u305f\u3081\u306b\u3053\u306e\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306e\u518d\u30ed\u30fc\u30c9\u304c\u5931\u6557\u3057\u307e\u3057\u305f
+standardContext.reloadingStarted=\u3053\u306e\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306e\u518d\u30ed\u30fc\u30c9\u3092\u958b\u59cb\u3057\u307e\u3057\u305f
+standardContext.requestListener.requestInit=\u30af\u30e9\u30b9 {0} \u306e\u30ea\u30b9\u30ca\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u306b\u521d\u671f\u5316\u3059\u308b\u30e9\u30a4\u30d5\u30b5\u30a4\u30af\u30eb\u30a4\u30d9\u30f3\u30c8\u306e\u30ea\u30af\u30a8\u30b9\u30c8\u3092\u9001\u4fe1\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+standardContext.requestListener.requestDestroy=\u30af\u30e9\u30b9 {0} \u306e\u30ea\u30b9\u30ca\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u306b\u5ec3\u68c4\u3059\u308b\u30e9\u30a4\u30d5\u30b5\u30a4\u30af\u30eb\u30a4\u30d9\u30f3\u30c8\u306e\u30ea\u30af\u30a8\u30b9\u30c8\u3092\u9001\u4fe1\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+standardContext.resourcesStart=\u9759\u7684\u30ea\u30bd\u30fc\u30b9\u306e\u8d77\u52d5\u4e2d\u306e\u30a8\u30e9\u30fc\u3067\u3059
+standardContext.securityConstraint.pattern=\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u306e\u5236\u7d04\u306e\u4e2d\u306b\u7121\u52b9\u306a <url-pattern> {0} \u304c\u3042\u308a\u307e\u3059
+standardContext.servletMap.name=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u30de\u30c3\u30d4\u30f3\u30b0\u306f\u672a\u77e5\u306e\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u540d {0} \u3092\u6307\u5b9a\u3057\u3066\u3044\u307e\u3059
+standardContext.servletMap.pattern=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u30de\u30c3\u30d4\u30f3\u30b0\u4e2d\u306b\u7121\u52b9\u306a <url-pattern> {0} \u304c\u3042\u308a\u307e\u3059
+standardContext.startCleanup=\u8d77\u52d5\u304c\u5931\u6557\u3057\u305f\u5f8c\u306e\u30af\u30ea\u30fc\u30f3\u30ca\u30c3\u30d7\u4e2d\u306b\u4f8b\u5916\u304c\u767a\u751f\u3057\u307e\u3057\u305f
+standardContext.startFailed=\u4ee5\u524d\u306e\u30a8\u30e9\u30fc\u306e\u305f\u3081\u306b\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306e\u8d77\u52d5\u304c\u5931\u6557\u3057\u307e\u3057\u305f [{0}]
+standardContext.startingLoader=\u30ed\u30fc\u30c0\u3092\u8d77\u52d5\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+standardContext.startingManager=\u30de\u30cd\u30fc\u30b8\u30e3\u3092\u8d77\u52d5\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+standardContext.startingWrapper=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8 {0} \u306e\u30e9\u30c3\u30d1\u3092\u8d77\u52d5\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+standardContext.stoppingContext=\u30ed\u30fc\u30c0\u3092\u505c\u6b62\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+standardContext.stoppingLoader=\u30ed\u30fc\u30c0\u3092\u505c\u6b62\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+standardContext.stoppingManager=\u30de\u30cd\u30fc\u30b8\u30e3\u3092\u505c\u6b62\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+standardContext.stoppingWrapper=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8 {0} \u306e\u30e9\u30c3\u30d1\u3092\u505c\u6b62\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+standardContext.urlDecode=\u30ea\u30af\u30a8\u30b9\u30c8\u30d1\u30b9 {0} \u306eURL\u30c7\u30b3\u30fc\u30c9\u304c\u3067\u304d\u307e\u305b\u3093
+standardContext.urlPattern.patternWarning=\u8b66\u544a: Servlet 2.4\u3067\u306fURL\u30d1\u30bf\u30fc\u30f3 {0} \u306f''/''\u3067\u59cb\u307e\u3089\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+standardContext.urlValidate=URL\u30c7\u30b3\u30fc\u30c9\u3055\u308c\u305f\u30ea\u30af\u30a8\u30b9\u30c8\u30d1\u30b9 {0} \u3092\u691c\u8a3c\u3067\u304d\u307e\u305b\u3093
+standardEngine.alreadyStarted=\u30a8\u30f3\u30b8\u30f3\u306f\u65e2\u306b\u8d77\u52d5\u3055\u308c\u3066\u3044\u307e\u3059
+standardEngine.mappingError=\u30b5\u30fc\u30d0\u540d {0} \u306e\u30de\u30c3\u30d4\u30f3\u30b0\u8a2d\u5b9a\u30a8\u30e9\u30fc\u3067\u3059
+standardEngine.noHost=\u30b5\u30fc\u30d0\u540d {0} \u306b\u4e00\u81f4\u3059\u308b\u30db\u30b9\u30c8\u304c\u5b58\u5728\u3057\u307e\u305b\u3093
+standardEngine.noHostHeader=Host:\u30d8\u30c3\u30c0\u3092\u6301\u305f\u306a\u3044 HTTP/1.1 \u30ea\u30af\u30a8\u30b9\u30c8\u3067\u3059
+standardEngine.notHost=\u30a8\u30f3\u30b8\u30f3\u306e\u5b50\u4f9b\u306f\u30db\u30b9\u30c8\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+standardEngine.notParent=\u30a8\u30f3\u30b8\u30f3\u306f\u89aa\u306e\u30b3\u30f3\u30c6\u30ca\u3092\u6301\u3064\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093
+standardEngine.notStarted=\u30a8\u30f3\u30b8\u30f3\u306f\u307e\u3060\u8d77\u52d5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+standardEngine.unfoundHost=\u30d0\u30fc\u30c1\u30e3\u30eb\u30db\u30b9\u30c8 {0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+standardEngine.unknownHost=\u3053\u306e\u30ea\u30af\u30a8\u30b9\u30c8\u4e2d\u306b\u30b5\u30fc\u30d0\u30db\u30b9\u30c8\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+standardHost.accessBase=\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u30d9\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea {0} \u306b\u30a2\u30af\u30bb\u30b9\u3067\u304d\u307e\u305b\u3093
+standardHost.alreadyStarted=\u30db\u30b9\u30c8\u306f\u65e2\u306b\u8d77\u52d5\u3055\u308c\u3066\u3044\u307e\u3059
+standardHost.appBase=\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30d9\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea {0} \u304c\u5b58\u5728\u3057\u307e\u305b\u3093
+standardHost.clientAbort=\u30ea\u30e2\u30fc\u30c8\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u304c\u30ea\u30af\u30a8\u30b9\u30c8\u3092\u4e2d\u6b62\u3057\u307e\u3057\u305f, IOException: {0}
+standardHost.configRequired=\u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u3078\u306eURL\u304c\u5fc5\u8981\u3067\u3059
+standardHost.configNotAllowed=\u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u304c\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093
+standardHost.installBase=\u30db\u30b9\u30c8Web\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u4e2d\u306eWeb\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3060\u3051\u304c\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3067\u304d\u307e\u3059
+standardHost.installing=URL {1} \u304b\u3089\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306bWeb\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3057\u307e\u3059
+standardHost.installingWAR=URL {0} \u304b\u3089Web\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3057\u307e\u3059
+standardHost.installingXML=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u306eURL {0} \u3092\u51e6\u7406\u3057\u3066\u3044\u307e\u3059
+standardHost.installError=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306b\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u914d\u5099\u4e2d\u306e\u30a8\u30e9\u30fc\u3067\u3059
+standardHost.invalidErrorReportValveClass=\u6307\u5b9a\u3055\u308c\u305f\u30a8\u30e9\u30fc\u30ea\u30dd\u30fc\u30c8\u30d0\u30eb\u30d6\u30af\u30e9\u30b9\u3092\u30ed\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093: {0}
+standardHost.docBase=\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u30d9\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea {0} \u304c\u65e2\u306b\u5b58\u5728\u3057\u307e\u3059
+standardHost.mappingError=\u30ea\u30af\u30a8\u30b9\u30c8URI {0} \u306e\u30de\u30c3\u30d4\u30f3\u30b0\u8a2d\u5b9a\u30a8\u30e9\u30fc\u3067\u3059
+standardHost.noContext=\u3053\u306e\u30ea\u30af\u30a8\u30b9\u30c8\u3092\u51e6\u7406\u3059\u308b\u305f\u3081\u306b\u8a2d\u5b9a\u3055\u308c\u305f\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u304c\u3042\u308a\u307e\u305b\u3093
+standardHost.noHost=\u3053\u306e\u30ea\u30af\u30a8\u30b9\u30c8\u3092\u51e6\u7406\u3059\u308b\u305f\u3081\u306b\u8a2d\u5b9a\u3055\u308c\u305f\u30db\u30b9\u30c8\u304c\u3042\u308a\u307e\u305b\u3093
+standardHost.notContext=\u30db\u30b9\u30c8\u306e\u5b50\u4f9b\u306f\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+standardHost.notStarted=\u30db\u30b9\u30c8\u304c\u307e\u3060\u8d77\u52d5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+standardHost.nullName=\u30db\u30b9\u30c8\u540d\u304c\u5fc5\u8981\u3067\u3059
+standardHost.pathFormat=\u7121\u52b9\u306a\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9\u3067\u3059: {0}
+standardHost.pathMatch=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306f\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u53c8\u306fWAR\u30d5\u30a1\u30a4\u30eb\u540d\u306b\u4e00\u81f4\u3057\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093: {1}
+standardHost.pathMissing=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306f\u73fe\u5728\u4f7f\u7528\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+standardHost.pathRequired=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9\u304c\u5fc5\u8981\u3067\u3059
+standardHost.pathUsed=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306f\u65e2\u306b\u4f7f\u7528\u3055\u308c\u3066\u3044\u307e\u3059
+standardHost.removing=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306eWeb\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u524a\u9664\u3057\u307e\u3059
+standardHost.removeError=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u524a\u9664\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
+standardHost.start=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306eWeb\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u8d77\u52d5\u3057\u307e\u3059
+standardHost.stop=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306eWeb\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u505c\u6b62\u3057\u307e\u3059
+standardHost.unfoundContext=\u30ea\u30af\u30a8\u30b9\u30c8URI {0} \u306e\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u3092\u898b\u3064\u3051\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093
+standardHost.warRequired=Web\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30a2\u30fc\u30ab\u30a4\u30d6\u306eURL\u304c\u5fc5\u8981\u3067\u3059
+standardHost.warURL=Web\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30a2\u30fc\u30ab\u30a4\u30d6\u306b\u5bfe\u3059\u308b\u7121\u52b9\u306aURL\u3067\u3059: {0}
+standardService.start.name=\u30b5\u30fc\u30d3\u30b9 {0} \u3092\u8d77\u52d5\u3057\u307e\u3059
+standardService.stop.name=\u30b5\u30fc\u30d3\u30b9 {0} \u3092\u505c\u6b62\u3057\u307e\u3059
+standardWrapper.allocate=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u3092\u5272\u308a\u5f53\u3066\u4e2d\u306e\u30a8\u30e9\u30fc\u3067\u3059
+standardWrapper.allocateException=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8 {0} \u306b\u4f8b\u5916\u3092\u5272\u308a\u5f53\u3066\u307e\u3059
+standardWrapper.containerServlet=\u30b3\u30f3\u30c6\u30ca\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8 {0} \u3092\u30ed\u30fc\u30c9\u3057\u307e\u3059
+standardWrapper.createFilters=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8 {0}\u306b\u5bfe\u3059\u308b\u30d5\u30a3\u30eb\u30bf\u4f8b\u5916\u3092\u4f5c\u6210\u3057\u307e\u3059
+standardWrapper.deallocateException=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8 {0} \u306b\u5bfe\u3059\u308b\u4f8b\u5916\u306e\u5272\u308a\u5f53\u3066\u3092\u89e3\u9664\u3057\u307e\u3059
+standardWrapper.destroyException=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8 {0} \u306eServlet.destroy()\u304c\u4f8b\u5916\u3092\u6295\u3052\u307e\u3057\u305f
+standardWrapper.exception0=Tomcat\u306e\u4f8b\u5916\u306e\u5831\u544a
+standardWrapper.exception1=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u4f8b\u5916\u304c\u767a\u751f\u3057\u307e\u3057\u305f
+standardWrapper.exception2=\u4f8b\u5916\u306e\u5831\u544a:
+standardWrapper.exception3=\u6839\u672c\u306e\u539f\u56e0:
+standardWrapper.initException=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8 {0} \u306eServlet.init()\u304c\u4f8b\u5916\u3092\u6295\u3052\u307e\u3057\u305f
+standardWrapper.instantiate=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u30af\u30e9\u30b9 {0} \u3092\u521d\u671f\u5316\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
+standardWrapper.isUnavailable=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8 {0} \u306f\u73fe\u5728\u5229\u7528\u3067\u304d\u307e\u305b\u3093
+standardWrapper.jasperLoader=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8 {0} \u306bJasper\u30af\u30e9\u30b9\u30ed\u30fc\u30c0\u3092\u4f7f\u7528\u3057\u307e\u3059
+standardWrapper.jspFile.format=JSP\u30d5\u30a1\u30a4\u30eb {0} \u304c''/''\u6587\u5b57\u3067\u59cb\u307e\u3063\u3066\u3044\u307e\u305b\u3093
+standardWrapper.loadException=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8 {0} \u304cload()\u4f8b\u5916\u3092\u6295\u3052\u307e\u3057\u305f
+standardWrapper.missingClass=\u30e9\u30c3\u30d1\u304c\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u30af\u30e9\u30b9 {0} \u53c8\u306f\u305d\u308c\u304c\u4f9d\u5b58\u3059\u308b\u30af\u30e9\u30b9\u3092\u898b\u3064\u3051\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093
+standardWrapper.missingLoader=\u30e9\u30c3\u30d1\u304c\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8 {0} \u306e\u30ed\u30fc\u30c0\u3092\u898b\u3064\u3051\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093
+standardWrapper.notChild=\u30e9\u30c3\u30d1\u30b3\u30f3\u30c6\u30ca\u306f\u5b50\u4f9b\u306e\u30b3\u30f3\u30c6\u30ca\u3092\u6301\u3064\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093
+standardWrapper.notClass=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8 {0} \u306b\u6307\u5b9a\u3055\u308c\u305f\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u30af\u30e9\u30b9\u304c\u3042\u308a\u307e\u305b\u3093
+standardWrapper.notContext=\u30e9\u30c3\u30d1\u306e\u89aa\u306e\u30b3\u30f3\u30c6\u30ca\u306f\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+standardWrapper.notFound=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8 {0} \u304c\u5229\u7528\u3067\u304d\u307e\u305b\u3093
+standardWrapper.notServlet=\u30af\u30e9\u30b9 {0} \u306f\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u3067\u306f\u3042\u308a\u307e\u305b\u3093
+standardWrapper.releaseFilters=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8 {0} \u306e\u30d5\u30a3\u30eb\u30bf\u4f8b\u5916\u3092\u89e3\u9664\u3057\u307e\u3059
+standardWrapper.serviceException=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8 {0} \u306eServlet.service()\u304c\u4f8b\u5916\u3092\u6295\u3052\u307e\u3057\u305f
+standardWrapper.statusHeader=HTTP\u30b9\u30c6\u30fc\u30bf\u30b9 {0} - {1}
+standardWrapper.statusTitle=Tomcat\u306e\u30a8\u30e9\u30fc\u306e\u5831\u544a
+standardWrapper.unavailable=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8 {0} \u3092\u5229\u7528\u4e0d\u53ef\u80fd\u306b\u30de\u30fc\u30af\u3057\u307e\u3059
+standardWrapper.unloadException=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8 {0} \u304cunload()\u4f8b\u5916\u3092\u6295\u3052\u307e\u3057\u305f
+standardWrapper.unloading=\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8 {0} \u304c\u30ed\u30fc\u30c9\u3055\u308c\u3066\u3044\u306a\u3044\u306e\u3067\u3001\u5272\u308a\u5f53\u3066\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093
+standardWrapper.waiting={0} \u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u304c\u5272\u308a\u5f53\u3066\u89e3\u9664\u3055\u308c\u308b\u306e\u3092\u5f85\u3063\u3066\u3044\u307e\u3059
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/NamingContextListener.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/NamingContextListener.java
new file mode 100644
index 0000000..0f408ef
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/NamingContextListener.java
@@ -0,0 +1,1217 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.StringTokenizer;
+
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import javax.naming.NameAlreadyBoundException;
+import javax.naming.NamingException;
+import javax.naming.Reference;
+import javax.naming.StringRefAddr;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.ContainerEvent;
+import org.apache.catalina.ContainerListener;
+import org.apache.catalina.Context;
+import org.apache.catalina.Host;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.Server;
+import org.apache.catalina.deploy.ContextEjb;
+import org.apache.catalina.deploy.ContextEnvironment;
+import org.apache.catalina.deploy.ContextHandler;
+import org.apache.catalina.deploy.ContextLocalEjb;
+import org.apache.catalina.deploy.ContextResource;
+import org.apache.catalina.deploy.ContextResourceEnvRef;
+import org.apache.catalina.deploy.ContextResourceLink;
+import org.apache.catalina.deploy.ContextService;
+import org.apache.catalina.deploy.ContextTransaction;
+import org.apache.catalina.deploy.NamingResources;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.naming.ContextAccessController;
+import org.apache.naming.ContextBindings;
+import org.apache.naming.EjbRef;
+import org.apache.naming.HandlerRef;
+import org.apache.naming.NamingContext;
+import org.apache.naming.ResourceEnvRef;
+import org.apache.naming.ResourceLinkRef;
+import org.apache.naming.ResourceRef;
+import org.apache.naming.ServiceRef;
+import org.apache.naming.TransactionRef;
+import org.apache.tomcat.util.modeler.Registry;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Helper class used to initialize and populate the JNDI context associated
+ * with each context and server.
+ *
+ * @author Remy Maucherat
+ * @version $Id: NamingContextListener.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+public class NamingContextListener
+    implements LifecycleListener, ContainerListener, PropertyChangeListener {
+
+    private static final Log log = LogFactory.getLog(NamingContextListener.class);
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    protected Log logger = log;
+    
+    
+    /**
+     * Name of the associated naming context.
+     */
+    protected String name = "/";
+
+
+    /**
+     * Associated container.
+     */
+    protected Object container = null;
+
+
+    /**
+     * Initialized flag.
+     */
+    protected boolean initialized = false;
+
+
+    /**
+     * Associated naming resources.
+     */
+    protected NamingResources namingResources = null;
+
+
+    /**
+     * Associated JNDI context.
+     */
+    protected NamingContext namingContext = null;
+
+
+    /**
+     * Comp context.
+     */
+    protected javax.naming.Context compCtx = null;
+
+
+    /**
+     * Env context.
+     */
+    protected javax.naming.Context envCtx = null;
+
+    
+    /**
+     * Objectnames hashtable.
+     */
+    protected HashMap<String, ObjectName> objectNames =
+        new HashMap<String, ObjectName>();
+    
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the "name" property.
+     */
+    public String getName() {
+        return (this.name);
+    }
+
+
+    /**
+     * Set the "name" property.
+     *
+     * @param name The new name
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    
+    /**
+     * Return the comp context.
+     */
+    public javax.naming.Context getCompContext() {
+        return this.compCtx;
+    }
+    
+
+    /**
+     * Return the env context.
+     */
+    public javax.naming.Context getEnvContext() {
+        return this.envCtx;
+    }
+    
+
+    /**
+     * Return the associated naming context.
+     */
+    public NamingContext getNamingContext() {
+        return (this.namingContext);
+    }
+
+
+    // ---------------------------------------------- LifecycleListener Methods
+
+
+    /**
+     * Acknowledge the occurrence of the specified event.
+     *
+     * @param event LifecycleEvent that has occurred
+     */
+    @Override
+    public void lifecycleEvent(LifecycleEvent event) {
+
+        container = event.getLifecycle();
+
+        if (container instanceof Context) {
+            namingResources = ((Context) container).getNamingResources();
+            logger = log;
+        } else if (container instanceof Server) {
+            namingResources = ((Server) container).getGlobalNamingResources();
+        } else {
+            return;
+        }
+
+        if (Lifecycle.CONFIGURE_START_EVENT.equals(event.getType())) {
+
+            if (initialized)
+                return;
+
+            Hashtable<String, Object> contextEnv = new Hashtable<String, Object>();
+            try {
+                namingContext = new NamingContext(contextEnv, getName());
+            } catch (NamingException e) {
+                // Never happens
+            }
+            ContextAccessController.setSecurityToken(getName(), container);
+            ContextBindings.bindContext(container, namingContext, container);
+            if( log.isDebugEnabled() ) {
+                log.debug("Bound " + container );
+            }
+
+            // Setting the context in read/write mode
+            ContextAccessController.setWritable(getName(), container);
+
+            try {
+                createNamingContext();
+            } catch (NamingException e) {
+                logger.error
+                    (sm.getString("naming.namingContextCreationFailed", e));
+            }
+
+            namingResources.addPropertyChangeListener(this);
+
+            // Binding the naming context to the class loader
+            if (container instanceof Context) {
+                // Setting the context in read only mode
+                ContextAccessController.setReadOnly(getName());
+                try {
+                    ContextBindings.bindClassLoader
+                        (container, container, 
+                         ((Container) container).getLoader().getClassLoader());
+                } catch (NamingException e) {
+                    logger.error(sm.getString("naming.bindFailed", e));
+                }
+            }
+
+            if (container instanceof Server) {
+                org.apache.naming.factory.ResourceLinkFactory.setGlobalContext
+                    (namingContext);
+                try {
+                    ContextBindings.bindClassLoader
+                        (container, container, 
+                         this.getClass().getClassLoader());
+                } catch (NamingException e) {
+                    logger.error(sm.getString("naming.bindFailed", e));
+                }
+                if (container instanceof StandardServer) {
+                    ((StandardServer) container).setGlobalNamingContext
+                        (namingContext);
+                }
+            }
+
+            initialized = true;
+
+        } else if (Lifecycle.CONFIGURE_STOP_EVENT.equals(event.getType())) {
+
+            if (!initialized)
+                return;
+
+            // Setting the context in read/write mode
+            ContextAccessController.setWritable(getName(), container);
+            ContextBindings.unbindContext(container, container);
+
+            if (container instanceof Context) {
+                ContextBindings.unbindClassLoader
+                    (container, container, 
+                     ((Container) container).getLoader().getClassLoader());
+            }
+
+            if (container instanceof Server) {
+                namingResources.removePropertyChangeListener(this);
+                ContextBindings.unbindClassLoader
+                    (container, container, 
+                     this.getClass().getClassLoader());
+            }
+
+            ContextAccessController.unsetSecurityToken(getName(), container);
+
+            namingContext = null;
+            envCtx = null;
+            compCtx = null;
+            initialized = false;
+
+        }
+
+    }
+
+
+    // ---------------------------------------------- ContainerListener Methods
+
+
+    /**
+     * Acknowledge the occurrence of the specified event.
+     * Note: Will never be called when the listener is associated to a Server,
+     * since it is not a Container.
+     *
+     * @param event ContainerEvent that has occurred
+     */
+    @Override
+    public void containerEvent(ContainerEvent event) {
+
+        if (!initialized)
+            return;
+
+        // Setting the context in read/write mode
+        ContextAccessController.setWritable(getName(), container);
+
+        String type = event.getType();
+
+        if (type.equals("addEjb")) {
+
+            String ejbName = (String) event.getData();
+            if (ejbName != null) {
+                ContextEjb ejb = namingResources.findEjb(ejbName);
+                addEjb(ejb);
+            }
+
+        } else if (type.equals("addEnvironment")) {
+
+            String environmentName = (String) event.getData();
+            if (environmentName != null) {
+                ContextEnvironment env = 
+                    namingResources.findEnvironment(environmentName);
+                addEnvironment(env);
+            }
+
+        } else if (type.equals("addLocalEjb")) {
+
+            String localEjbName = (String) event.getData();
+            if (localEjbName != null) {
+                ContextLocalEjb localEjb = 
+                    namingResources.findLocalEjb(localEjbName);
+                addLocalEjb(localEjb);
+            }
+
+        } else if (type.equals("addResource")) {
+
+            String resourceName = (String) event.getData();
+            if (resourceName != null) {
+                ContextResource resource = 
+                    namingResources.findResource(resourceName);
+                addResource(resource);
+            }
+
+        } else if (type.equals("addResourceLink")) {
+
+            String resourceLinkName = (String) event.getData();
+            if (resourceLinkName != null) {
+                ContextResourceLink resourceLink = 
+                    namingResources.findResourceLink(resourceLinkName);
+                addResourceLink(resourceLink);
+            }
+
+        } else if (type.equals("addResourceEnvRef")) {
+
+            String resourceEnvRefName = (String) event.getData();
+            if (resourceEnvRefName != null) {
+                ContextResourceEnvRef resourceEnvRef = 
+                    namingResources.findResourceEnvRef(resourceEnvRefName);
+                addResourceEnvRef(resourceEnvRef);
+            }
+
+        } else if (type.equals("addService")) {
+
+            String serviceName = (String) event.getData();
+            if (serviceName != null) {
+                ContextService service = 
+                    namingResources.findService(serviceName);
+                addService(service);
+            }
+
+        } else if (type.equals("removeEjb")) {
+
+            String ejbName = (String) event.getData();
+            if (ejbName != null) {
+                removeEjb(ejbName);
+            }
+
+        } else if (type.equals("removeEnvironment")) {
+
+            String environmentName = (String) event.getData();
+            if (environmentName != null) {
+                removeEnvironment(environmentName);
+            }
+
+        } else if (type.equals("removeLocalEjb")) {
+
+            String localEjbName = (String) event.getData();
+            if (localEjbName != null) {
+                removeLocalEjb(localEjbName);
+            }
+
+        } else if (type.equals("removeResource")) {
+
+            String resourceName = (String) event.getData();
+            if (resourceName != null) {
+                removeResource(resourceName);
+            }
+
+        } else if (type.equals("removeResourceLink")) {
+
+            String resourceLinkName = (String) event.getData();
+            if (resourceLinkName != null) {
+                removeResourceLink(resourceLinkName);
+            }
+
+        } else if (type.equals("removeResourceEnvRef")) {
+
+            String resourceEnvRefName = (String) event.getData();
+            if (resourceEnvRefName != null) {
+                removeResourceEnvRef(resourceEnvRefName);
+            }
+
+        } else if (type.equals("removeService")) {
+
+            String serviceName = (String) event.getData();
+            if (serviceName != null) {
+                removeService(serviceName);
+            }
+
+        }
+
+        // Setting the context in read only mode
+        ContextAccessController.setReadOnly(getName());
+
+    }
+
+
+    // ----------------------------------------- PropertyChangeListener Methods
+
+
+    /**
+     * Process property change events.
+     *
+     * @param event The property change event that has occurred
+     */
+    @Override
+    public void propertyChange(PropertyChangeEvent event) {
+
+        if (!initialized)
+            return;
+
+        Object source = event.getSource();
+        if (source == namingResources) {
+
+            // Setting the context in read/write mode
+            ContextAccessController.setWritable(getName(), container);
+
+            processGlobalResourcesChange(event.getPropertyName(),
+                                         event.getOldValue(),
+                                         event.getNewValue());
+
+            // Setting the context in read only mode
+            ContextAccessController.setReadOnly(getName());
+
+        }
+
+    }
+
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Process a property change on the naming resources, by making the
+     * corresponding addition or removal to the associated JNDI context.
+     *
+     * @param name Property name of the change to be processed
+     * @param oldValue The old value (or <code>null</code> if adding)
+     * @param newValue The new value (or <code>null</code> if removing)
+     */
+    private void processGlobalResourcesChange(String name,
+                                              Object oldValue,
+                                              Object newValue) {
+
+        if (name.equals("ejb")) {
+            if (oldValue != null) {
+                ContextEjb ejb = (ContextEjb) oldValue;
+                if (ejb.getName() != null) {
+                    removeEjb(ejb.getName());
+                }
+            }
+            if (newValue != null) {
+                ContextEjb ejb = (ContextEjb) newValue;
+                if (ejb.getName() != null) {
+                    addEjb(ejb);
+                }
+            }
+        } else if (name.equals("environment")) {
+            if (oldValue != null) {
+                ContextEnvironment env = (ContextEnvironment) oldValue;
+                if (env.getName() != null) {
+                    removeEnvironment(env.getName());
+                }
+            }
+            if (newValue != null) {
+                ContextEnvironment env = (ContextEnvironment) newValue;
+                if (env.getName() != null) {
+                    addEnvironment(env);
+                }
+            }
+        } else if (name.equals("localEjb")) {
+            if (oldValue != null) {
+                ContextLocalEjb ejb = (ContextLocalEjb) oldValue;
+                if (ejb.getName() != null) {
+                    removeLocalEjb(ejb.getName());
+                }
+            }
+            if (newValue != null) {
+                ContextLocalEjb ejb = (ContextLocalEjb) newValue;
+                if (ejb.getName() != null) {
+                    addLocalEjb(ejb);
+                }
+            }
+        } else if (name.equals("resource")) {
+            if (oldValue != null) {
+                ContextResource resource = (ContextResource) oldValue;
+                if (resource.getName() != null) {
+                    removeResource(resource.getName());
+                }
+            }
+            if (newValue != null) {
+                ContextResource resource = (ContextResource) newValue;
+                if (resource.getName() != null) {
+                    addResource(resource);
+                }
+            }
+        } else if (name.equals("resourceEnvRef")) {
+            if (oldValue != null) {
+                ContextResourceEnvRef resourceEnvRef = 
+                    (ContextResourceEnvRef) oldValue;
+                if (resourceEnvRef.getName() != null) {
+                    removeResourceEnvRef(resourceEnvRef.getName());
+                }
+            }
+            if (newValue != null) {
+                ContextResourceEnvRef resourceEnvRef = 
+                    (ContextResourceEnvRef) newValue;
+                if (resourceEnvRef.getName() != null) {
+                    addResourceEnvRef(resourceEnvRef);
+                }
+            }
+        } else if (name.equals("resourceLink")) {
+            if (oldValue != null) {
+                ContextResourceLink rl = (ContextResourceLink) oldValue;
+                if (rl.getName() != null) {
+                    removeResourceLink(rl.getName());
+                }
+            }
+            if (newValue != null) {
+                ContextResourceLink rl = (ContextResourceLink) newValue;
+                if (rl.getName() != null) {
+                    addResourceLink(rl);
+                }
+            }
+        } else if (name.equals("service")) {
+            if (oldValue != null) {
+                ContextService service = (ContextService) oldValue;
+                if (service.getName() != null) {
+                    removeService(service.getName());
+                }
+            }
+            if (newValue != null) {
+                ContextService service = (ContextService) newValue;
+                if (service.getName() != null) {
+                    addService(service);
+                }
+            }
+        }
+
+
+    }
+
+
+    /**
+     * Create and initialize the JNDI naming context.
+     */
+    private void createNamingContext()
+        throws NamingException {
+
+        // Creating the comp subcontext
+        if (container instanceof Server) {
+            compCtx = namingContext;
+            envCtx = namingContext;
+        } else {
+            compCtx = namingContext.createSubcontext("comp");
+            envCtx = compCtx.createSubcontext("env");
+        }
+
+        int i;
+
+        if (log.isDebugEnabled())
+            log.debug("Creating JNDI naming context");
+
+        if (namingResources == null) {
+            namingResources = new NamingResources();
+            namingResources.setContainer(container);
+        }
+
+        // Resource links
+        ContextResourceLink[] resourceLinks = 
+            namingResources.findResourceLinks();
+        for (i = 0; i < resourceLinks.length; i++) {
+            addResourceLink(resourceLinks[i]);
+        }
+
+        // Resources
+        ContextResource[] resources = namingResources.findResources();
+        for (i = 0; i < resources.length; i++) {
+            addResource(resources[i]);
+        }
+
+        // Resources Env
+        ContextResourceEnvRef[] resourceEnvRefs = namingResources.findResourceEnvRefs();
+        for (i = 0; i < resourceEnvRefs.length; i++) {
+            addResourceEnvRef(resourceEnvRefs[i]);
+        }
+
+        // Environment entries
+        ContextEnvironment[] contextEnvironments = 
+            namingResources.findEnvironments();
+        for (i = 0; i < contextEnvironments.length; i++) {
+            addEnvironment(contextEnvironments[i]);
+        }
+
+        // EJB references
+        ContextEjb[] ejbs = namingResources.findEjbs();
+        for (i = 0; i < ejbs.length; i++) {
+            addEjb(ejbs[i]);
+        }
+
+        // WebServices references
+        ContextService[] services = namingResources.findServices();
+        for (i = 0; i < services.length; i++) {
+            addService(services[i]);
+        }
+
+        // Binding a User Transaction reference
+        if (container instanceof Context) {
+            try {
+                Reference ref = new TransactionRef();
+                compCtx.bind("UserTransaction", ref);
+                ContextTransaction transaction = namingResources.getTransaction();
+                if (transaction != null) {
+                    Iterator<String> params = transaction.listProperties();
+                    while (params.hasNext()) {
+                        String paramName = params.next();
+                        String paramValue = (String) transaction.getProperty(paramName);
+                        StringRefAddr refAddr = new StringRefAddr(paramName, paramValue);
+                        ref.add(refAddr);
+                    }
+                }
+            } catch (NameAlreadyBoundException e) {
+                // Ignore because UserTransaction was obviously 
+                // added via ResourceLink
+            } catch (NamingException e) {
+                logger.error(sm.getString("naming.bindFailed", e));
+            }
+        }
+
+        // Binding the resources directory context
+        if (container instanceof Context) {
+            try {
+                compCtx.bind("Resources", 
+                             ((Container) container).getResources());
+            } catch (NamingException e) {
+                logger.error(sm.getString("naming.bindFailed", e));
+            }
+        }
+
+    }
+
+
+    /**
+     * Create an <code>ObjectName</code> for this
+     * <code>ContextResource</code> object.
+     *
+     * @param resource The resource
+     * @return ObjectName The object name
+     * @exception MalformedObjectNameException if a name cannot be created
+     */
+    protected ObjectName createObjectName(ContextResource resource)
+        throws MalformedObjectNameException {
+
+        String domain = null;
+        if (container instanceof StandardServer) {
+            domain = ((StandardServer) container).getDomain();
+        } else if (container instanceof ContainerBase) {
+            domain = ((ContainerBase) container).getDomain();
+        }
+        if (domain == null) {
+            domain = "Catalina";
+        }
+        
+        ObjectName name = null;
+        String quotedResourceName = ObjectName.quote(resource.getName());
+        if (container instanceof Server) {        
+            name = new ObjectName(domain + ":type=DataSource" +
+                        ",class=" + resource.getType() + 
+                        ",name=" + quotedResourceName);
+        } else if (container instanceof Context) {                    
+            String contextName = ((Context)container).getName();
+            if (!contextName.startsWith("/"))
+                contextName = "/" + contextName;
+            Host host = (Host) ((Context)container).getParent();
+            name = new ObjectName(domain + ":type=DataSource" +
+                        ",context=" + contextName + 
+                        ",host=" + host.getName() +
+                        ",class=" + resource.getType() +
+                        ",name=" + quotedResourceName);
+        }
+        
+        return (name);
+
+    }
+
+    
+    /**
+     * Set the specified EJBs in the naming context.
+     */
+    public void addEjb(ContextEjb ejb) {
+
+        // Create a reference to the EJB.
+        Reference ref = new EjbRef
+            (ejb.getType(), ejb.getHome(), ejb.getRemote(), ejb.getLink());
+        // Adding the additional parameters, if any
+        Iterator<String> params = ejb.listProperties();
+        while (params.hasNext()) {
+            String paramName = params.next();
+            String paramValue = (String) ejb.getProperty(paramName);
+            StringRefAddr refAddr = new StringRefAddr(paramName, paramValue);
+            ref.add(refAddr);
+        }
+        try {
+            createSubcontexts(envCtx, ejb.getName());
+            envCtx.bind(ejb.getName(), ref);
+        } catch (NamingException e) {
+            logger.error(sm.getString("naming.bindFailed", e));
+        }
+
+    }
+
+
+    /**
+     * Set the specified environment entries in the naming context.
+     */
+    public void addEnvironment(ContextEnvironment env) {
+
+        Object value = null;
+        // Instantiating a new instance of the correct object type, and
+        // initializing it.
+        String type = env.getType();
+        try {
+            if (type.equals("java.lang.String")) {
+                value = env.getValue();
+            } else if (type.equals("java.lang.Byte")) {
+                if (env.getValue() == null) {
+                    value = Byte.valueOf((byte) 0);
+                } else {
+                    value = Byte.decode(env.getValue());
+                }
+            } else if (type.equals("java.lang.Short")) {
+                if (env.getValue() == null) {
+                    value = Short.valueOf((short) 0);
+                } else {
+                    value = Short.decode(env.getValue());
+                }
+            } else if (type.equals("java.lang.Integer")) {
+                if (env.getValue() == null) {
+                    value = Integer.valueOf(0);
+                } else {
+                    value = Integer.decode(env.getValue());
+                }
+            } else if (type.equals("java.lang.Long")) {
+                if (env.getValue() == null) {
+                    value = Long.valueOf(0);
+                } else {
+                    value = Long.decode(env.getValue());
+                }
+            } else if (type.equals("java.lang.Boolean")) {
+                value = Boolean.valueOf(env.getValue());
+            } else if (type.equals("java.lang.Double")) {
+                if (env.getValue() == null) {
+                    value = Double.valueOf(0);
+                } else {
+                    value = Double.valueOf(env.getValue());
+                }
+            } else if (type.equals("java.lang.Float")) {
+                if (env.getValue() == null) {
+                    value = Float.valueOf(0);
+                } else {
+                    value = Float.valueOf(env.getValue());
+                }
+            } else if (type.equals("java.lang.Character")) {
+                if (env.getValue() == null) {
+                    value = Character.valueOf((char) 0);
+                } else {
+                    if (env.getValue().length() == 1) {
+                        value = Character.valueOf(env.getValue().charAt(0));
+                    } else {
+                        throw new IllegalArgumentException();
+                    }
+                }
+            } else {
+                logger.error(sm.getString("naming.invalidEnvEntryType", env.getName()));
+            }
+        } catch (NumberFormatException e) {
+            logger.error(sm.getString("naming.invalidEnvEntryValue", env.getName()));
+        } catch (IllegalArgumentException e) {
+            logger.error(sm.getString("naming.invalidEnvEntryValue", env.getName()));
+        }
+
+        // Binding the object to the appropriate name
+        if (value != null) {
+            try {
+                if (logger.isDebugEnabled())
+                    logger.debug("  Adding environment entry " + env.getName());
+                createSubcontexts(envCtx, env.getName());
+                envCtx.bind(env.getName(), value);
+            } catch (NamingException e) {
+                logger.error(sm.getString("naming.invalidEnvEntryValue", e));
+            }
+        }
+
+    }
+
+
+    /**
+     * Set the specified local EJBs in the naming context.
+     */
+    public void addLocalEjb(
+            @SuppressWarnings("unused") ContextLocalEjb localEjb) {
+        // NO-OP
+    }
+
+
+    /**
+     * Set the specified web service in the naming context.
+     */
+    public void addService(ContextService service) {
+
+        if (service.getWsdlfile() != null) {
+            URL wsdlURL = null;
+
+            try {
+                wsdlURL = new URL(service.getWsdlfile());
+            } catch (MalformedURLException e) {
+                // Ignore and carry on
+            }
+            if (wsdlURL == null) {
+                try {
+                    wsdlURL = ((Context) container).
+                                                    getServletContext().
+                                                    getResource(service.getWsdlfile());
+                } catch (MalformedURLException e) {
+                    // Ignore and carry on
+                }
+            }
+            if (wsdlURL == null) {
+                try {
+                    wsdlURL = ((Context) container).
+                                                    getServletContext().
+                                                    getResource("/" + service.getWsdlfile());
+                    logger.debug("  Changing service ref wsdl file for /" 
+                                + service.getWsdlfile());
+                } catch (MalformedURLException e) {
+                    logger.error(sm.getString("naming.wsdlFailed", e));
+                }
+            }
+            if (wsdlURL == null)
+                service.setWsdlfile(null);
+            else
+                service.setWsdlfile(wsdlURL.toString());
+        }
+
+        if (service.getJaxrpcmappingfile() != null) {
+            URL jaxrpcURL = null;
+
+            try {
+                jaxrpcURL = new URL(service.getJaxrpcmappingfile());
+            } catch (MalformedURLException e) {
+                // Ignore and carry on
+            }
+            if (jaxrpcURL == null) {
+                try {
+                    jaxrpcURL = ((Context) container).
+                                                    getServletContext().
+                                                    getResource(service.getJaxrpcmappingfile());
+                } catch (MalformedURLException e) {
+                    // Ignore and carry on
+                }
+            }
+            if (jaxrpcURL == null) {
+                try {
+                    jaxrpcURL = ((Context) container).
+                                                    getServletContext().
+                                                    getResource("/" + service.getJaxrpcmappingfile());
+                    logger.debug("  Changing service ref jaxrpc file for /" 
+                                + service.getJaxrpcmappingfile());
+                } catch (MalformedURLException e) {
+                    logger.error(sm.getString("naming.wsdlFailed", e));
+                }
+            }
+            if (jaxrpcURL == null)
+                service.setJaxrpcmappingfile(null);
+            else
+                service.setJaxrpcmappingfile(jaxrpcURL.toString());
+        }
+
+        // Create a reference to the resource.
+        Reference ref = new ServiceRef
+            (service.getName(), service.getType(), service.getServiceqname(),
+             service.getWsdlfile(), service.getJaxrpcmappingfile());
+        // Adding the additional port-component-ref, if any
+        Iterator<String> portcomponent = service.getServiceendpoints();
+        while (portcomponent.hasNext()) {
+            String serviceendpoint = portcomponent.next();
+            StringRefAddr refAddr = new StringRefAddr(ServiceRef.SERVICEENDPOINTINTERFACE, serviceendpoint);
+            ref.add(refAddr);
+            String portlink = service.getPortlink(serviceendpoint);
+            refAddr = new StringRefAddr(ServiceRef.PORTCOMPONENTLINK, portlink);
+            ref.add(refAddr);
+        }
+        // Adding the additional parameters, if any
+        Iterator<String> handlers = service.getHandlers();
+        while (handlers.hasNext()) {
+            String handlername = handlers.next();
+            ContextHandler handler = service.getHandler(handlername);
+            HandlerRef handlerRef = new HandlerRef(handlername, handler.getHandlerclass());
+            Iterator<String> localParts = handler.getLocalparts();
+            while (localParts.hasNext()) {
+                String localPart = localParts.next();
+                String namespaceURI = handler.getNamespaceuri(localPart);
+                handlerRef.add(new StringRefAddr(HandlerRef.HANDLER_LOCALPART, localPart));
+                handlerRef.add(new StringRefAddr(HandlerRef.HANDLER_NAMESPACE, namespaceURI));
+            }
+            Iterator<String> params = handler.listProperties();
+            while (params.hasNext()) {
+                String paramName = params.next();
+                String paramValue = (String) handler.getProperty(paramName);
+                handlerRef.add(new StringRefAddr(HandlerRef.HANDLER_PARAMNAME, paramName));
+                handlerRef.add(new StringRefAddr(HandlerRef.HANDLER_PARAMVALUE, paramValue));
+            }
+            for (int i = 0; i < handler.getSoapRolesSize(); i++) {
+                handlerRef.add(new StringRefAddr(HandlerRef.HANDLER_SOAPROLE, handler.getSoapRole(i)));
+            }
+            for (int i = 0; i < handler.getPortNamesSize(); i++) {
+                handlerRef.add(new StringRefAddr(HandlerRef.HANDLER_PORTNAME, handler.getPortName(i)));
+            }
+            ((ServiceRef) ref).addHandler(handlerRef);
+        }
+
+        try {
+            if (logger.isDebugEnabled()) {
+                logger.debug("  Adding service ref " 
+                             + service.getName() + "  " + ref);
+            }
+            createSubcontexts(envCtx, service.getName());
+            envCtx.bind(service.getName(), ref);
+        } catch (NamingException e) {
+            logger.error(sm.getString("naming.bindFailed", e));
+        }
+
+    }
+
+
+    /**
+     * Set the specified resources in the naming context.
+     */
+    public void addResource(ContextResource resource) {
+
+        // Create a reference to the resource.
+        Reference ref = new ResourceRef
+            (resource.getType(), resource.getDescription(),
+             resource.getScope(), resource.getAuth(),
+             resource.getSingleton());
+        // Adding the additional parameters, if any
+        Iterator<String> params = resource.listProperties();
+        while (params.hasNext()) {
+            String paramName = params.next();
+            String paramValue = (String) resource.getProperty(paramName);
+            StringRefAddr refAddr = new StringRefAddr(paramName, paramValue);
+            ref.add(refAddr);
+        }
+        try {
+            if (logger.isDebugEnabled()) {
+                logger.debug("  Adding resource ref " 
+                             + resource.getName() + "  " + ref);
+            }
+            createSubcontexts(envCtx, resource.getName());
+            envCtx.bind(resource.getName(), ref);
+        } catch (NamingException e) {
+            logger.error(sm.getString("naming.bindFailed", e));
+        }
+
+        if ("javax.sql.DataSource".equals(ref.getClassName()) &&
+                resource.getSingleton()) {
+            try {
+                ObjectName on = createObjectName(resource);
+                Object actualResource = envCtx.lookup(resource.getName());
+                Registry.getRegistry(null, null).registerComponent(actualResource, on, null);
+                objectNames.put(resource.getName(), on);
+            } catch (Exception e) {
+                logger.warn(sm.getString("naming.jmxRegistrationFailed", e));
+            }
+        }
+        
+    }
+
+
+    /**
+     * Set the specified resources in the naming context.
+     */
+    public void addResourceEnvRef(ContextResourceEnvRef resourceEnvRef) {
+
+        // Create a reference to the resource env.
+        Reference ref = new ResourceEnvRef(resourceEnvRef.getType());
+        // Adding the additional parameters, if any
+        Iterator<String> params = resourceEnvRef.listProperties();
+        while (params.hasNext()) {
+            String paramName = params.next();
+            String paramValue = (String) resourceEnvRef.getProperty(paramName);
+            StringRefAddr refAddr = new StringRefAddr(paramName, paramValue);
+            ref.add(refAddr);
+        }
+        try {
+            if (logger.isDebugEnabled())
+                log.debug("  Adding resource env ref " + resourceEnvRef.getName());
+            createSubcontexts(envCtx, resourceEnvRef.getName());
+            envCtx.bind(resourceEnvRef.getName(), ref);
+        } catch (NamingException e) {
+            logger.error(sm.getString("naming.bindFailed", e));
+        }
+
+    }
+
+
+    /**
+     * Set the specified resource link in the naming context.
+     */
+    public void addResourceLink(ContextResourceLink resourceLink) {
+
+        // Create a reference to the resource.
+        Reference ref = new ResourceLinkRef
+            (resourceLink.getType(), resourceLink.getGlobal(), resourceLink.getFactory(), null);
+        Iterator<String> i = resourceLink.listProperties();
+        while (i.hasNext()) {
+            String key = i.next().toString();
+            Object val = resourceLink.getProperty(key);
+            if (val!=null) {
+                StringRefAddr refAddr = new StringRefAddr(key, val.toString());
+                ref.add(refAddr);
+            }
+        }
+        javax.naming.Context ctx = 
+            "UserTransaction".equals(resourceLink.getName()) 
+            ? compCtx : envCtx;
+        try {
+            if (logger.isDebugEnabled())
+                log.debug("  Adding resource link " + resourceLink.getName());
+            createSubcontexts(envCtx, resourceLink.getName());
+            ctx.bind(resourceLink.getName(), ref);
+        } catch (NamingException e) {
+            logger.error(sm.getString("naming.bindFailed", e));
+        }
+
+    }
+
+
+    /**
+     * Set the specified EJBs in the naming context.
+     */
+    public void removeEjb(String name) {
+
+        try {
+            envCtx.unbind(name);
+        } catch (NamingException e) {
+            logger.error(sm.getString("naming.unbindFailed", e));
+        }
+
+    }
+
+
+    /**
+     * Set the specified environment entries in the naming context.
+     */
+    public void removeEnvironment(String name) {
+
+        try {
+            envCtx.unbind(name);
+        } catch (NamingException e) {
+            logger.error(sm.getString("naming.unbindFailed", e));
+        }
+
+    }
+
+
+    /**
+     * Set the specified local EJBs in the naming context.
+     */
+    public void removeLocalEjb(String name) {
+
+        try {
+            envCtx.unbind(name);
+        } catch (NamingException e) {
+            logger.error(sm.getString("naming.unbindFailed", e));
+        }
+
+    }
+
+
+    /**
+     * Set the specified web services in the naming context.
+     */
+    public void removeService(String name) {
+
+        try {
+            envCtx.unbind(name);
+        } catch (NamingException e) {
+            logger.error(sm.getString("naming.unbindFailed", e));
+        }
+
+    }
+
+
+    /**
+     * Set the specified resources in the naming context.
+     */
+    public void removeResource(String name) {
+
+        try {
+            envCtx.unbind(name);
+        } catch (NamingException e) {
+            logger.error(sm.getString("naming.unbindFailed", e));
+        }
+
+        ObjectName on = objectNames.get(name);
+        if (on != null) {
+            Registry.getRegistry(null, null).unregisterComponent(on);
+        }
+
+    }
+
+
+    /**
+     * Set the specified resources in the naming context.
+     */
+    public void removeResourceEnvRef(String name) {
+
+        try {
+            envCtx.unbind(name);
+        } catch (NamingException e) {
+            logger.error(sm.getString("naming.unbindFailed", e));
+        }
+
+    }
+
+
+    /**
+     * Set the specified resources in the naming context.
+     */
+    public void removeResourceLink(String name) {
+
+        try {
+            envCtx.unbind(name);
+        } catch (NamingException e) {
+            logger.error(sm.getString("naming.unbindFailed", e));
+        }
+
+    }
+
+
+    /**
+     * Create all intermediate subcontexts.
+     */
+    private void createSubcontexts(javax.naming.Context ctx, String name)
+        throws NamingException {
+        javax.naming.Context currentContext = ctx;
+        StringTokenizer tokenizer = new StringTokenizer(name, "/");
+        while (tokenizer.hasMoreTokens()) {
+            String token = tokenizer.nextToken();
+            if ((!token.equals("")) && (tokenizer.hasMoreTokens())) {
+                try {
+                    currentContext = currentContext.createSubcontext(token);
+                } catch (NamingException e) {
+                    // Silent catch. Probably an object is already bound in
+                    // the context.
+                    currentContext =
+                        (javax.naming.Context) currentContext.lookup(token);
+                }
+            }
+        }
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/RestrictedFilters.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/core/RestrictedFilters.properties
new file mode 100644
index 0000000..c916920
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/RestrictedFilters.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.
+
+org.apache.catalina.ssi.SSIFilter=restricted
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/RestrictedListeners.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/core/RestrictedListeners.properties
new file mode 100644
index 0000000..ae1e83e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/RestrictedListeners.properties
@@ -0,0 +1,14 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/RestrictedServlets.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/core/RestrictedServlets.properties
new file mode 100644
index 0000000..d336968
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/RestrictedServlets.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+org.apache.catalina.ssi.SSIServlet=restricted
+org.apache.catalina.servlets.CGIServlet=restricted
+org.apache.catalina.manager.JMXProxyServlet=restricted
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardContext.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardContext.java
new file mode 100644
index 0000000..4113326
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardContext.java
@@ -0,0 +1,6527 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+import java.util.TreeMap;
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanNotificationInfo;
+import javax.management.Notification;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationEmitter;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+import javax.servlet.FilterConfig;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextAttributeListener;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestAttributeListener;
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.descriptor.JspConfigDescriptor;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionListener;
+
+import org.apache.catalina.Authenticator;
+import org.apache.catalina.Container;
+import org.apache.catalina.ContainerListener;
+import org.apache.catalina.Context;
+import org.apache.catalina.Globals;
+import org.apache.catalina.Host;
+import org.apache.catalina.InstanceListener;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.Loader;
+import org.apache.catalina.Manager;
+import org.apache.catalina.Pipeline;
+import org.apache.catalina.Valve;
+import org.apache.catalina.Wrapper;
+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.Injectable;
+import org.apache.catalina.deploy.InjectionTarget;
+import org.apache.catalina.deploy.LoginConfig;
+import org.apache.catalina.deploy.MessageDestination;
+import org.apache.catalina.deploy.MessageDestinationRef;
+import org.apache.catalina.deploy.NamingResources;
+import org.apache.catalina.deploy.SecurityCollection;
+import org.apache.catalina.deploy.SecurityConstraint;
+import org.apache.catalina.loader.WebappLoader;
+import org.apache.catalina.session.StandardManager;
+import org.apache.catalina.startup.TldConfig;
+import org.apache.catalina.util.CharsetMapper;
+import org.apache.catalina.util.ContextName;
+import org.apache.catalina.util.ExtensionValidator;
+import org.apache.catalina.util.RequestUtil;
+import org.apache.catalina.util.URLEncoder;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.naming.ContextBindings;
+import org.apache.naming.resources.BaseDirContext;
+import org.apache.naming.resources.DirContextURLStreamHandler;
+import org.apache.naming.resources.FileDirContext;
+import org.apache.naming.resources.ProxyDirContext;
+import org.apache.naming.resources.WARDirContext;
+import org.apache.tomcat.InstanceManager;
+import org.apache.tomcat.JarScanner;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.modeler.Registry;
+import org.apache.tomcat.util.scan.StandardJarScanner;
+import org.apache.tomcat.util.threads.DedicatedThreadExecutor;
+
+/**
+ * Standard implementation of the <b>Context</b> interface.  Each
+ * child container must be a Wrapper implementation to process the
+ * requests directed to a particular servlet.
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @version $Id: StandardContext.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+public class StandardContext extends ContainerBase
+        implements Context, NotificationEmitter {
+
+    private static final Log log = LogFactory.getLog(StandardContext.class);
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Create a new StandardContext component with the default basic Valve.
+     */
+    public StandardContext() {
+
+        super();
+        pipeline.setBasic(new StandardContextValve());
+        broadcaster = new NotificationBroadcasterSupport();
+        // Set defaults
+        if (!Globals.STRICT_SERVLET_COMPLIANCE) {
+            // Strict servlet compliance requires all extension mapped servlets
+            // to be checked against welcome files
+            resourceOnlyServlets.add("jsp");
+        }
+    }
+
+
+    // ----------------------------------------------------- Class Variables
+
+
+    /**
+     * The descriptive information string for this implementation.
+     */
+    private static final String info =
+        "org.apache.catalina.core.StandardContext/1.0";
+
+
+    /**
+     * Array containing the safe characters set.
+     */
+    protected static URLEncoder urlEncoder;
+
+
+    /**
+     * GMT timezone - all HTTP dates are on GMT
+     */
+    static {
+        urlEncoder = new URLEncoder();
+        urlEncoder.addSafeCharacter('~');
+        urlEncoder.addSafeCharacter('-');
+        urlEncoder.addSafeCharacter('_');
+        urlEncoder.addSafeCharacter('.');
+        urlEncoder.addSafeCharacter('*');
+        urlEncoder.addSafeCharacter('/');
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Allow multipart/form-data requests to be parsed even when the
+     * target servlet doesn't specify @MultipartConfig or have a
+     * &lt;multipart-config&gt; element.
+     */
+    protected boolean allowCasualMultipartParsing = false;
+     
+    /**
+     * Control whether remaining request data will be read
+     * (swallowed) even if the request violates a data size constraint.
+     */
+    private boolean swallowAbortedUploads = true;
+
+    /**
+     * The alternate deployment descriptor name.
+     */
+    private String altDDName = null;
+
+
+    /**
+     * Lifecycle provider.
+     */
+    private InstanceManager instanceManager = null;
+
+
+   /**
+     * Associated host name.
+     */
+    private String hostName;
+
+
+    /**
+     * The antiJARLocking flag for this Context.
+     */
+    private boolean antiJARLocking = false;
+
+    
+    /**
+     * The antiResourceLocking flag for this Context.
+     */
+    private boolean antiResourceLocking = false;
+
+    
+    /**
+     * The set of application listener class names configured for this
+     * application, in the order they were encountered in the web.xml file.
+     */
+    private String applicationListeners[] = new String[0];
+    
+    private final Object applicationListenersLock = new Object();
+
+
+    /**
+     * The set of instantiated application event listener objects</code>.
+     */
+    private Object applicationEventListenersObjects[] = 
+        new Object[0];
+
+
+    /**
+     * The set of instantiated application lifecycle listener objects</code>.
+     */
+    private Object applicationLifecycleListenersObjects[] = 
+        new Object[0];
+
+
+    /**
+     * The ordered set of ServletContainerInitializers for this web application.
+     */
+    private Map<ServletContainerInitializer,Set<Class<?>>> initializers =
+        new LinkedHashMap<ServletContainerInitializer,Set<Class<?>>>();
+    
+    
+    /**
+     * The set of application parameters defined for this application.
+     */
+    private ApplicationParameter applicationParameters[] =
+        new ApplicationParameter[0];
+
+    private final Object applicationParametersLock = new Object();
+    
+
+    /**
+     * The broadcaster that sends j2ee notifications. 
+     */
+    private NotificationBroadcasterSupport broadcaster = null;
+    
+    /**
+     * The Locale to character set mapper for this application.
+     */
+    private CharsetMapper charsetMapper = null;
+
+
+    /**
+     * The Java class name of the CharsetMapper class to be created.
+     */
+    private String charsetMapperClass =
+      "org.apache.catalina.util.CharsetMapper";
+
+
+    /**
+     * The URL of the XML descriptor for this context.
+     */
+    private URL configFile = null;
+
+
+    /**
+     * The "correctly configured" flag for this Context.
+     */
+    private boolean configured = false;
+
+
+    /**
+     * The security constraints for this web application.
+     */
+    private volatile SecurityConstraint constraints[] =
+            new SecurityConstraint[0];
+    
+    private final Object constraintsLock = new Object();
+
+
+    /**
+     * The ServletContext implementation associated with this Context.
+     */
+    protected ApplicationContext context = null;
+
+
+    /**
+     * Compiler classpath to use.
+     */
+    private String compilerClasspath = null;
+
+
+    /**
+     * Should we attempt to use cookies for session id communication?
+     */
+    private boolean cookies = true;
+
+
+    /**
+     * Should we allow the <code>ServletContext.getContext()</code> method
+     * to access the context of other web applications in this server?
+     */
+    private boolean crossContext = false;
+
+    
+    /**
+     * Encoded path.
+     */
+    private String encodedPath = null;
+    
+
+    /**
+     * Unencoded path for this web application.
+     */
+    private String path = null;
+
+
+    /**
+     * The "follow standard delegation model" flag that will be used to
+     * configure our ClassLoader.
+     */
+    private boolean delegate = false;
+
+
+    /**
+     * The display name of this web application.
+     */
+    private String displayName = null;
+
+
+    /** 
+     * Override the default context xml location.
+     */
+    private String defaultContextXml;
+
+
+    /** 
+     * Override the default web xml location.
+     */
+    private String defaultWebXml;
+
+
+    /**
+     * The distributable flag for this web application.
+     */
+    private boolean distributable = false;
+
+
+    /**
+     * The document root for this web application.
+     */
+    private String docBase = null;
+
+
+    /**
+     * The exception pages for this web application, keyed by fully qualified
+     * class name of the Java exception.
+     */
+    private HashMap<String, ErrorPage> exceptionPages =
+        new HashMap<String, ErrorPage>();
+
+
+    /**
+     * The set of filter configurations (and associated filter instances) we
+     * have initialized, keyed by filter name.
+     */
+    private HashMap<String, ApplicationFilterConfig> filterConfigs =
+        new HashMap<String, ApplicationFilterConfig>();
+
+
+    /**
+     * The set of filter definitions for this application, keyed by
+     * filter name.
+     */
+    private HashMap<String, FilterDef> filterDefs =
+        new HashMap<String, FilterDef>();
+
+
+    /**
+     * The set of filter mappings for this application, in the order
+     * they were defined in the deployment descriptor with additional mappings
+     * added via the {@link ServletContext} possibly both before and after those
+     * defined in the deployment descriptor.
+     */
+    private final ContextFilterMaps filterMaps = new ContextFilterMaps();
+
+    /**
+     * Ignore annotations.
+     */
+    private boolean ignoreAnnotations = false;
+
+
+    /**
+     * The set of classnames of InstanceListeners that will be added
+     * to each newly created Wrapper by <code>createWrapper()</code>.
+     */
+    private String instanceListeners[] = new String[0];
+
+    private final Object instanceListenersLock = new Object();
+
+
+    /**
+     * The login configuration descriptor for this web application.
+     */
+    private LoginConfig loginConfig = null;
+
+
+    /**
+     * The mapper associated with this context.
+     */
+    private org.apache.tomcat.util.http.mapper.Mapper mapper = 
+        new org.apache.tomcat.util.http.mapper.Mapper();
+
+
+    /**
+     * The naming context listener for this web application.
+     */
+    private NamingContextListener namingContextListener = null;
+
+
+    /**
+     * The naming resources for this web application.
+     */
+    private NamingResources namingResources = null;
+
+    /**
+     * The message destinations for this web application.
+     */
+    private HashMap<String, MessageDestination> messageDestinations =
+        new HashMap<String, MessageDestination>();
+
+
+    /**
+     * The MIME mappings for this web application, keyed by extension.
+     */
+    private HashMap<String, String> mimeMappings =
+        new HashMap<String, String>();
+
+
+     /**
+      * Special case: error page for status 200.
+      */
+     private ErrorPage okErrorPage = null;
+
+
+    /**
+     * The context initialization parameters for this web application,
+     * keyed by name.
+     */
+    private HashMap<String, String> parameters = new HashMap<String, String>();
+
+
+    /**
+     * The request processing pause flag (while reloading occurs)
+     */
+    private boolean paused = false;
+
+
+    /**
+     * The public identifier of the DTD for the web application deployment
+     * descriptor version we are currently parsing.  This is used to support
+     * relaxed validation rules when processing version 2.2 web.xml files.
+     */
+    private String publicId = null;
+
+
+    /**
+     * The reloadable flag for this web application.
+     */
+    private boolean reloadable = false;
+
+
+    /**
+     * Unpack WAR property.
+     */
+    private boolean unpackWAR = true;
+
+
+    /**
+     * The default context override flag for this web application.
+     */
+    private boolean override = false;
+
+
+    /**
+     * The original document root for this web application.
+     */
+    private String originalDocBase = null;
+    
+    
+    /**
+     * The privileged flag for this web application.
+     */
+    private boolean privileged = false;
+
+
+    /**
+     * Should the next call to <code>addWelcomeFile()</code> cause replacement
+     * of any existing welcome files?  This will be set before processing the
+     * web application's deployment descriptor, so that application specified
+     * choices <strong>replace</strong>, rather than append to, those defined
+     * in the global descriptor.
+     */
+    private boolean replaceWelcomeFiles = false;
+
+
+    /**
+     * The security role mappings for this application, keyed by role
+     * name (as used within the application).
+     */
+    private HashMap<String, String> roleMappings =
+        new HashMap<String, String>();
+
+
+    /**
+     * The security roles for this application, keyed by role name.
+     */
+    private String securityRoles[] = new String[0];
+
+    private final Object securityRolesLock = new Object();
+
+
+    /**
+     * The servlet mappings for this web application, keyed by
+     * matching pattern.
+     */
+    private HashMap<String, String> servletMappings =
+        new HashMap<String, String>();
+    
+    private final Object servletMappingsLock = new Object();
+
+
+    /**
+     * The session timeout (in minutes) for this web application.
+     */
+    private int sessionTimeout = 30;
+
+    /**
+     * The notification sequence number.
+     */
+    private AtomicLong sequenceNumber = new AtomicLong(0);
+    
+    /**
+     * The status code error pages for this web application, keyed by
+     * HTTP status code (as an Integer).
+     */
+    private HashMap<Integer, ErrorPage> statusPages =
+        new HashMap<Integer, ErrorPage>();
+
+
+    /**
+     * Set flag to true to cause the system.out and system.err to be redirected
+     * to the logger when executing a servlet.
+     */
+    private boolean swallowOutput = false;
+
+
+    /**
+     * Amount of ms that the container will wait for servlets to unload.
+     */
+    private long unloadDelay = 2000;
+
+
+    /**
+     * The watched resources for this application.
+     */
+    private String watchedResources[] = new String[0];
+
+    private final Object watchedResourcesLock = new Object();
+
+
+    /**
+     * The welcome files for this application.
+     */
+    private String welcomeFiles[] = new String[0];
+
+    private final Object welcomeFilesLock = new Object();
+
+
+    /**
+     * The set of classnames of LifecycleListeners that will be added
+     * to each newly created Wrapper by <code>createWrapper()</code>.
+     */
+    private String wrapperLifecycles[] = new String[0];
+
+    private final Object wrapperLifecyclesLock = new Object();
+
+    /**
+     * The set of classnames of ContainerListeners that will be added
+     * to each newly created Wrapper by <code>createWrapper()</code>.
+     */
+    private String wrapperListeners[] = new String[0];
+
+    private final Object wrapperListenersLock = new Object();
+
+    /**
+     * The pathname to the work directory for this context (relative to
+     * the server's home if not absolute).
+     */
+    private String workDir = null;
+
+
+    /**
+     * Java class name of the Wrapper class implementation we use.
+     */
+    private String wrapperClassName = StandardWrapper.class.getName();
+    private Class<?> wrapperClass = null;
+
+
+    /**
+     * JNDI use flag.
+     */
+    private boolean useNaming = true;
+
+
+    /**
+     * Filesystem based flag.
+     */
+    private boolean filesystemBased = false;
+
+
+    /**
+     * Name of the associated naming context.
+     */
+    private String namingContextName = null;
+
+
+    /**
+     * Caching allowed flag.
+     */
+    private boolean cachingAllowed = true;
+
+
+    /**
+     * Allow linking.
+     */
+    protected boolean allowLinking = false;
+
+
+    /**
+     * Cache max size in KB.
+     */
+    protected int cacheMaxSize = 10240; // 10 MB
+
+
+    /**
+     * Cache object max size in KB.
+     */
+    protected int cacheObjectMaxSize = 512; // 512K
+
+
+    /**
+     * Cache TTL in ms.
+     */
+    protected int cacheTTL = 5000;
+
+
+    /**
+     * List of resource aliases.
+     */
+    private String aliases = null;
+
+
+    /**
+     * Non proxied resources.
+     */
+    private DirContext webappResources = null;
+
+    private long startupTime;
+    private long startTime;
+    private long tldScanTime;
+
+    /** 
+     * Name of the engine. If null, the domain is used.
+     */ 
+    private String j2EEApplication="none";
+    private String j2EEServer="none";
+
+
+    /**
+     * Attribute value used to turn on/off XML validation
+     */
+    private boolean webXmlValidation = Globals.STRICT_SERVLET_COMPLIANCE;
+
+
+    /**
+     * Attribute value used to turn on/off XML namespace validation
+     */
+    private boolean webXmlNamespaceAware = Globals.STRICT_SERVLET_COMPLIANCE;
+
+    /**
+     * Attribute value used to turn on/off TLD processing
+     */
+    private boolean processTlds = true;
+
+    /**
+     * Attribute value used to turn on/off XML validation
+     */
+    private boolean tldValidation = Globals.STRICT_SERVLET_COMPLIANCE;
+
+
+    /**
+     * Attribute value used to turn on/off TLD XML namespace validation
+     */
+    private boolean tldNamespaceAware = Globals.STRICT_SERVLET_COMPLIANCE;
+
+
+    /**
+     * Should we save the configuration.
+     */
+    private boolean saveConfig = true;
+
+    
+    /**
+     * The name to use for session cookies. <code>null</code> indicates that
+     * the name is controlled by the application.
+     */
+    private String sessionCookieName;
+    
+    
+    /**
+     * The flag that indicates that session cookies should use HttpOnly
+     */
+    private boolean useHttpOnly = true;
+
+    
+    /**
+     * The domain to use for session cookies. <code>null</code> indicates that
+     * the domain is controlled by the application.
+     */
+    private String sessionCookieDomain;
+    
+    
+    /**
+     * The path to use for session cookies. <code>null</code> indicates that
+     * the path is controlled by the application.
+     */
+    private String sessionCookiePath;
+    
+    
+    /**
+     * The Jar scanner to use to search for Jars that might contain
+     * configuration information such as TLDs or web-fragment.xml files. 
+     */
+    private JarScanner jarScanner = null;
+
+    /**
+     * 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;
+
+    /**
+     * 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;
+
+    /**
+     * Should Tomcat renew the threads of the thread pool when the application
+     * is stopped to avoid memory leaks because of uncleaned ThreadLocal
+     * variables. This also requires that the threadRenewalDelay property of the
+     * StandardThreadExecutor of ThreadPoolExecutor be set to a positive value.
+     */
+    private boolean renewThreadsWhenStoppingContext = true;
+    
+    /**
+     * Should the effective web.xml be logged when the context starts?
+     */
+    private boolean logEffectiveWebXml = false;
+
+    private int effectiveMajorVersion = 3;
+    
+    private int effectiveMinorVersion = 0;
+
+    private JspConfigDescriptor jspConfigDescriptor =
+        new ApplicationJspConfigDescriptor();
+
+    private Set<String> resourceOnlyServlets = new HashSet<String>();
+
+    private String webappVersion = "";
+
+    private boolean addWebinfClassesResources = false;
+    
+    private boolean fireRequestListenersOnForwards = false;
+
+    /**
+     * Servlets created via {@link ApplicationContext#createServlet(Class)} for
+     * tracking purposes.
+     */
+    private Set<Servlet> createdServlets = new HashSet<Servlet>();
+
+    private boolean preemptiveAuthentication = false;
+
+    // ----------------------------------------------------- Context Properties
+
+
+    @Override
+    public boolean getPreemptiveAuthentication() {
+        return preemptiveAuthentication;
+    }
+
+
+    @Override
+    public void setPreemptiveAuthentication(boolean preemptiveAuthentication) {
+        this.preemptiveAuthentication = preemptiveAuthentication;
+    }
+
+
+    @Override
+    public void setFireRequestListenersOnForwards(boolean enable) {
+        fireRequestListenersOnForwards = enable;
+    }
+
+
+    @Override
+    public boolean getFireRequestListenersOnForwards() {
+        return fireRequestListenersOnForwards;
+    }
+
+
+    public void setAddWebinfClassesResources(
+            boolean addWebinfClassesResources) {
+        this.addWebinfClassesResources = addWebinfClassesResources;
+    }
+
+
+    public boolean getAddWebinfClassesResources() {
+        return addWebinfClassesResources;
+    }
+
+
+    @Override
+    public void setWebappVersion(String webappVersion) {
+        if (null == webappVersion) {
+            this.webappVersion = "";
+        } else {
+            this.webappVersion = webappVersion;
+        }
+    }
+
+
+    @Override
+    public String getWebappVersion() {
+        return webappVersion;
+    }
+
+
+    @Override
+    public String getBaseName() {
+        return new ContextName(path, webappVersion).getBaseName();
+    }
+
+
+    @Override
+    public String getResourceOnlyServlets() {
+        StringBuilder result = new StringBuilder();
+        boolean first = true;
+        for (String servletName : resourceOnlyServlets) {
+            if (!first) {
+                result.append(',');
+            }
+            result.append(servletName);
+        }
+        return result.toString();
+    }
+
+
+    @Override
+    public void setResourceOnlyServlets(String resourceOnlyServlets) {
+        this.resourceOnlyServlets.clear();
+        if (resourceOnlyServlets == null) {
+            return;
+        }
+        for (String servletName : resourceOnlyServlets.split(",")) {
+            servletName = servletName.trim();
+            if (servletName.length()>0) {
+                this.resourceOnlyServlets.add(servletName);
+            }
+        }
+    }
+
+
+    @Override
+    public boolean isResourceOnlyServlet(String servletName) {
+        return resourceOnlyServlets.contains(servletName);
+    }
+
+
+    @Override
+    public int getEffectiveMajorVersion() {
+        return effectiveMajorVersion;
+    }
+
+    @Override
+    public void setEffectiveMajorVersion(int effectiveMajorVersion) {
+        this.effectiveMajorVersion = effectiveMajorVersion;
+    }
+
+    @Override
+    public int getEffectiveMinorVersion() {
+        return effectiveMinorVersion;
+    }
+
+    @Override
+    public void setEffectiveMinorVersion(int effectiveMinorVersion) {
+        this.effectiveMinorVersion = effectiveMinorVersion;
+    }
+    
+    @Override
+    public void setLogEffectiveWebXml(boolean logEffectiveWebXml) {
+        this.logEffectiveWebXml = logEffectiveWebXml;
+    }
+    
+    @Override
+    public boolean getLogEffectiveWebXml() {
+        return logEffectiveWebXml;
+    }
+
+    @Override
+    public Authenticator getAuthenticator() {
+        if (this instanceof Authenticator)
+            return (Authenticator) this;
+        
+        Pipeline pipeline = getPipeline();
+        if (pipeline != null) {
+            Valve basic = pipeline.getBasic();
+            if ((basic != null) && (basic instanceof Authenticator))
+                return (Authenticator) basic;
+            Valve valves[] = pipeline.getValves();
+            for (int i = 0; i < valves.length; i++) {
+                if (valves[i] instanceof Authenticator)
+                    return (Authenticator) valves[i];
+            }
+        }
+        return null;
+    }
+    
+    @Override
+    public JarScanner getJarScanner() {
+        if (jarScanner == null) {
+            jarScanner = new StandardJarScanner();
+        }
+        return jarScanner;
+    }
+
+
+    @Override
+    public void setJarScanner(JarScanner jarScanner) {
+        this.jarScanner = jarScanner;
+    }
+
+     
+    public InstanceManager getInstanceManager() {
+       return instanceManager;
+    }
+
+
+    public void setInstanceManager(InstanceManager instanceManager) {
+       this.instanceManager = instanceManager;
+    }
+
+    
+    @Override
+    public String getEncodedPath() {
+        return encodedPath;
+    }
+
+
+    /**
+     * Is caching allowed ?
+     */
+    public boolean isCachingAllowed() {
+        return cachingAllowed;
+    }
+
+
+    /**
+     * Set caching allowed flag.
+     */
+    public void setCachingAllowed(boolean cachingAllowed) {
+        this.cachingAllowed = cachingAllowed;
+    }
+
+
+    /**
+     * Set allow linking.
+     */
+    public void setAllowLinking(boolean allowLinking) {
+        this.allowLinking = allowLinking;
+    }
+
+
+    /**
+     * Is linking allowed.
+     */
+    public boolean isAllowLinking() {
+        return allowLinking;
+    }
+
+    /**
+     * Set to <code>true</code> to allow requests mapped to servlets that
+     * do not explicitly declare @MultipartConfig or have
+     * &lt;multipart-config&gt; specified in web.xml to parse
+     * multipart/form-data requests.
+     *
+     * @param allowCasualMultipartParsing <code>true</code> to allow such
+     *        casual parsing, <code>false</code> otherwise.
+     */
+    @Override
+    public void setAllowCasualMultipartParsing(
+            boolean allowCasualMultipartParsing) {
+        this.allowCasualMultipartParsing = allowCasualMultipartParsing;
+    }
+
+    /**
+     * Returns <code>true</code> if requests mapped to servlets without
+     * "multipart config" to parse multipart/form-data requests anyway.
+     *
+     * @return <code>true</code> if requests mapped to servlets without
+     *    "multipart config" to parse multipart/form-data requests,
+     *    <code>false</code> otherwise.
+     */
+    @Override
+    public boolean getAllowCasualMultipartParsing() {
+        return this.allowCasualMultipartParsing;
+    }
+
+    /**
+     * Set to <code>false</code> to disable request data swallowing
+     * after an upload was aborted due to size constraints.
+     *
+     * @param swallowAbortedUploads <code>false</code> to disable
+     *        swallowing, <code>true</code> otherwise (default).
+     */
+    @Override
+    public void setSwallowAbortedUploads(boolean swallowAbortedUploads) {
+        this.swallowAbortedUploads = swallowAbortedUploads;
+    }
+
+    /**
+     * Returns <code>true</code> if remaining request data will be read
+     * (swallowed) even the request violates a data size constraint.
+     *
+     * @return <code>true</code> if data will be swallowed (default),
+     *    <code>false</code> otherwise.
+     */
+    @Override
+    public boolean getSwallowAbortedUploads() {
+        return this.swallowAbortedUploads;
+    }
+
+    /**
+     * 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;
+    }
+
+
+    /**
+     * Return the list of resource aliases. 
+     */
+    public String getAliases() {
+        return this.aliases;
+    }
+
+
+    /**
+     * Add a URL for a JAR that contains static resources in a
+     * META-INF/resources directory that should be included in the static
+     * resources for this context.
+     */
+    @Override
+    public void addResourceJarUrl(URL url) {
+        if (webappResources instanceof BaseDirContext) {
+            ((BaseDirContext) webappResources).addResourcesJar(url);
+        } else {
+            log.error(sm.getString("standardContext.noResourceJar", url,
+                    getName()));
+        }
+    }
+    
+    
+    /**
+     * Set the current alias configuration. The list of aliases 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.
+     */
+    public void setAliases(String aliases) {
+        this.aliases = aliases;
+    }
+    
+    
+    /**
+     * Add a ServletContainerInitializer instance to this web application.
+     * 
+     * @param sci       The instance to add
+     * @param classes   The classes in which the initializer expressed an
+     *                  interest
+     */
+    @Override
+    public void addServletContainerInitializer(
+            ServletContainerInitializer sci, Set<Class<?>> classes) {
+        initializers.put(sci, classes);
+    }
+
+    
+    /**
+     * Return the "follow standard delegation model" flag used to configure
+     * our ClassLoader.
+     */
+    public boolean getDelegate() {
+
+        return (this.delegate);
+
+    }
+
+
+    /**
+     * Set the "follow standard delegation model" flag used to configure
+     * our ClassLoader.
+     *
+     * @param delegate The new flag
+     */
+    public void setDelegate(boolean delegate) {
+
+        boolean oldDelegate = this.delegate;
+        this.delegate = delegate;
+        support.firePropertyChange("delegate", oldDelegate,
+                                   this.delegate);
+
+    }
+
+
+    /**
+     * Returns true if the internal naming support is used.
+     */
+    public boolean isUseNaming() {
+
+        return (useNaming);
+
+    }
+
+
+    /**
+     * Enables or disables naming.
+     */
+    public void setUseNaming(boolean useNaming) {
+        this.useNaming = useNaming;
+    }
+
+
+    /**
+     * Returns true if the resources associated with this context are
+     * filesystem based.
+     */
+    public boolean isFilesystemBased() {
+
+        return (filesystemBased);
+
+    }
+
+
+    /**
+     * Return the set of initialized application event listener objects,
+     * in the order they were specified in the web application deployment
+     * descriptor, for this application.
+     *
+     * @exception IllegalStateException if this method is called before
+     *  this application has started, or after it has been stopped
+     */
+    @Override
+    public Object[] getApplicationEventListeners() {
+        return (applicationEventListenersObjects);
+    }
+
+
+    /**
+     * Store the set of initialized application event listener objects,
+     * in the order they were specified in the web application deployment
+     * descriptor, for this application.
+     *
+     * @param listeners The set of instantiated listener objects.
+     */
+    @Override
+    public void setApplicationEventListeners(Object listeners[]) {
+        applicationEventListenersObjects = listeners;
+    }
+
+
+    /**
+     * Add a listener to the end of the list of initialized application event
+     * listeners.
+     */
+    public void addApplicationEventListener(Object listener) {
+        int len = applicationEventListenersObjects.length;
+        Object[] newListeners = Arrays.copyOf(applicationEventListenersObjects,
+                len + 1);
+        newListeners[len] = listener;
+        applicationEventListenersObjects = newListeners;
+    }
+    
+    
+    /**
+     * Return the set of initialized application lifecycle listener objects,
+     * in the order they were specified in the web application deployment
+     * descriptor, for this application.
+     *
+     * @exception IllegalStateException if this method is called before
+     *  this application has started, or after it has been stopped
+     */
+    @Override
+    public Object[] getApplicationLifecycleListeners() {
+        return (applicationLifecycleListenersObjects);
+    }
+
+
+    /**
+     * Store the set of initialized application lifecycle listener objects,
+     * in the order they were specified in the web application deployment
+     * descriptor, for this application.
+     *
+     * @param listeners The set of instantiated listener objects.
+     */
+    @Override
+    public void setApplicationLifecycleListeners(Object listeners[]) {
+        applicationLifecycleListenersObjects = listeners;
+    }
+
+
+    /**
+     * Add a listener to the end of the list of initialized application
+     * lifecycle listeners.
+     */
+    public void addApplicationLifecycleListener(Object listener) {
+        int len = applicationLifecycleListenersObjects.length;
+        Object[] newListeners = Arrays.copyOf(
+                applicationLifecycleListenersObjects, len + 1);
+        newListeners[len] = listener;
+        applicationLifecycleListenersObjects = newListeners;
+    }
+
+    
+    /**
+     * Return the antiJARLocking flag for this Context.
+     */
+    public boolean getAntiJARLocking() {
+
+        return (this.antiJARLocking);
+
+    }
+
+
+    /**
+     * Return the antiResourceLocking flag for this Context.
+     */
+    public boolean getAntiResourceLocking() {
+
+        return (this.antiResourceLocking);
+
+    }
+
+
+    /**
+     * Set the antiJARLocking feature for this Context.
+     *
+     * @param antiJARLocking The new flag value
+     */
+    public void setAntiJARLocking(boolean antiJARLocking) {
+
+        boolean oldAntiJARLocking = this.antiJARLocking;
+        this.antiJARLocking = antiJARLocking;
+        support.firePropertyChange("antiJARLocking",
+                                   oldAntiJARLocking,
+                                   this.antiJARLocking);
+
+    }
+
+
+    /**
+     * Set the antiResourceLocking feature for this Context.
+     *
+     * @param antiResourceLocking The new flag value
+     */
+    public void setAntiResourceLocking(boolean antiResourceLocking) {
+
+        boolean oldAntiResourceLocking = this.antiResourceLocking;
+        this.antiResourceLocking = antiResourceLocking;
+        support.firePropertyChange("antiResourceLocking",
+                                   oldAntiResourceLocking,
+                                   this.antiResourceLocking);
+
+    }
+
+
+    /**
+     * Return the application available flag for this Context.
+     */
+    @Override
+    public boolean getAvailable() {
+
+        // TODO Remove this method entirely
+        return getState().isAvailable();
+
+    }
+
+
+    /**
+     * Return the Locale to character set mapper for this Context.
+     */
+    @Override
+    public CharsetMapper getCharsetMapper() {
+
+        // Create a mapper the first time it is requested
+        if (this.charsetMapper == null) {
+            try {
+                Class<?> clazz = Class.forName(charsetMapperClass);
+                this.charsetMapper = (CharsetMapper) clazz.newInstance();
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                this.charsetMapper = new CharsetMapper();
+            }
+        }
+
+        return (this.charsetMapper);
+
+    }
+
+
+    /**
+     * Set the Locale to character set mapper for this Context.
+     *
+     * @param mapper The new mapper
+     */
+    @Override
+    public void setCharsetMapper(CharsetMapper mapper) {
+
+        CharsetMapper oldCharsetMapper = this.charsetMapper;
+        this.charsetMapper = mapper;
+        if( mapper != null )
+            this.charsetMapperClass= mapper.getClass().getName();
+        support.firePropertyChange("charsetMapper", oldCharsetMapper,
+                                   this.charsetMapper);
+
+    }
+
+    /**
+     * Return the URL of the XML descriptor for this context.
+     */
+    @Override
+    public URL getConfigFile() {
+
+        return (this.configFile);
+
+    }
+
+
+    /**
+     * Set the URL of the XML descriptor for this context.
+     *
+     * @param configFile The URL of the XML descriptor for this context.
+     */
+    @Override
+    public void setConfigFile(URL configFile) {
+
+        this.configFile = configFile;
+    }
+
+
+    /**
+     * Return the "correctly configured" flag for this Context.
+     */
+    @Override
+    public boolean getConfigured() {
+
+        return (this.configured);
+
+    }
+
+
+    /**
+     * Set the "correctly configured" flag for this Context.  This can be
+     * set to false by startup listeners that detect a fatal configuration
+     * error to avoid the application from being made available.
+     *
+     * @param configured The new correctly configured flag
+     */
+    @Override
+    public void setConfigured(boolean configured) {
+
+        boolean oldConfigured = this.configured;
+        this.configured = configured;
+        support.firePropertyChange("configured",
+                                   oldConfigured,
+                                   this.configured);
+
+    }
+
+
+    /**
+     * Return the "use cookies for session ids" flag.
+     */
+    @Override
+    public boolean getCookies() {
+
+        return (this.cookies);
+
+    }
+
+
+    /**
+     * Set the "use cookies for session ids" flag.
+     *
+     * @param cookies The new flag
+     */
+    @Override
+    public void setCookies(boolean cookies) {
+
+        boolean oldCookies = this.cookies;
+        this.cookies = cookies;
+        support.firePropertyChange("cookies",
+                                   oldCookies,
+                                   this.cookies);
+
+    }
+    
+    
+    /**
+     * Gets the name to use for session cookies. Overrides any setting that
+     * may be specified by the application.
+     * 
+     * @return  The value of the default session cookie name or null if not
+     *          specified
+     */
+    @Override
+    public String getSessionCookieName() {
+        return sessionCookieName;
+    }
+    
+    
+    /**
+     * Sets the name to use for session cookies. Overrides any setting that
+     * may be specified by the application.
+     * 
+     * @param sessionCookieName   The name to use
+     */
+    @Override
+    public void setSessionCookieName(String sessionCookieName) {
+        String oldSessionCookieName = this.sessionCookieName;
+        this.sessionCookieName = sessionCookieName;
+        support.firePropertyChange("sessionCookieName",
+                oldSessionCookieName, sessionCookieName);
+    }
+
+    
+    /**
+     * Gets the value of the use HttpOnly cookies for session cookies flag.
+     * 
+     * @return <code>true</code> if the HttpOnly flag should be set on session
+     *         cookies
+     */
+    @Override
+    public boolean getUseHttpOnly() {
+        return useHttpOnly;
+    }
+
+
+    /**
+     * Sets the use HttpOnly cookies for session cookies flag.
+     * 
+     * @param useHttpOnly   Set to <code>true</code> to use HttpOnly cookies
+     *                          for session cookies
+     */
+    @Override
+    public void setUseHttpOnly(boolean useHttpOnly) {
+        boolean oldUseHttpOnly = this.useHttpOnly;
+        this.useHttpOnly = useHttpOnly;
+        support.firePropertyChange("useHttpOnly",
+                oldUseHttpOnly,
+                this.useHttpOnly);
+    }
+    
+    
+    /**
+     * Gets the domain to use for session cookies. Overrides any setting that
+     * may be specified by the application.
+     * 
+     * @return  The value of the default session cookie domain or null if not
+     *          specified
+     */
+    @Override
+    public String getSessionCookieDomain() {
+        return sessionCookieDomain;
+    }
+    
+    
+    /**
+     * Sets the domain to use for session cookies. Overrides any setting that
+     * may be specified by the application.
+     * 
+     * @param sessionCookieDomain   The domain to use
+     */
+    @Override
+    public void setSessionCookieDomain(String sessionCookieDomain) {
+        String oldSessionCookieDomain = this.sessionCookieDomain;
+        this.sessionCookieDomain = sessionCookieDomain;
+        support.firePropertyChange("sessionCookieDomain",
+                oldSessionCookieDomain, sessionCookieDomain);
+    }
+    
+
+    /**
+     * Gets the path to use for session cookies. Overrides any setting that
+     * may be specified by the application.
+     * 
+     * @return  The value of the default session cookie path or null if not
+     *          specified
+     */
+    @Override
+    public String getSessionCookiePath() {
+        return sessionCookiePath;
+    }
+    
+    
+    /**
+     * Sets the path to use for session cookies. Overrides any setting that
+     * may be specified by the application.
+     * 
+     * @param sessionCookiePath   The path to use
+     */
+    @Override
+    public void setSessionCookiePath(String sessionCookiePath) {
+        String oldSessionCookiePath = this.sessionCookiePath;
+        this.sessionCookiePath = sessionCookiePath;
+        support.firePropertyChange("sessionCookiePath",
+                oldSessionCookiePath, sessionCookiePath);
+    }
+    
+
+    /**
+     * Return the "allow crossing servlet contexts" flag.
+     */
+    @Override
+    public boolean getCrossContext() {
+
+        return (this.crossContext);
+
+    }
+
+
+    /**
+     * Set the "allow crossing servlet contexts" flag.
+     *
+     * @param crossContext The new cross contexts flag
+     */
+    @Override
+    public void setCrossContext(boolean crossContext) {
+
+        boolean oldCrossContext = this.crossContext;
+        this.crossContext = crossContext;
+        support.firePropertyChange("crossContext",
+                                   oldCrossContext,
+                                   this.crossContext);
+
+    }
+
+    public String getDefaultContextXml() {
+        return defaultContextXml;
+    }
+
+    /** 
+     * Set the location of the default context xml that will be used.
+     * If not absolute, it'll be made relative to the engine's base dir
+     * ( which defaults to catalina.base system property ).
+     *
+     * @param defaultContextXml The default web xml 
+     */
+    public void setDefaultContextXml(String defaultContextXml) {
+        this.defaultContextXml = defaultContextXml;
+    }
+
+    public String getDefaultWebXml() {
+        return defaultWebXml;
+    }
+
+    /** 
+     * Set the location of the default web xml that will be used.
+     * If not absolute, it'll be made relative to the engine's base dir
+     * ( which defaults to catalina.base system property ).
+     *
+     * @param defaultWebXml The default web xml 
+     */
+    public void setDefaultWebXml(String defaultWebXml) {
+        this.defaultWebXml = defaultWebXml;
+    }
+
+    /**
+     * Gets the time (in milliseconds) it took to start this context.
+     *
+     * @return Time (in milliseconds) it took to start this context.
+     */
+    public long getStartupTime() {
+        return startupTime;
+    }
+
+    public void setStartupTime(long startupTime) {
+        this.startupTime = startupTime;
+    }
+
+    public long getTldScanTime() {
+        return tldScanTime;
+    }
+
+    public void setTldScanTime(long tldScanTime) {
+        this.tldScanTime = tldScanTime;
+    }
+
+    /**
+     * Return the display name of this web application.
+     */
+    @Override
+    public String getDisplayName() {
+
+        return (this.displayName);
+
+    }
+
+
+    /**
+     * Return the alternate Deployment Descriptor name.
+     */
+    @Override
+    public String getAltDDName(){
+        return altDDName;
+    }
+
+
+    /**
+     * Set an alternate Deployment Descriptor name.
+     */
+    @Override
+    public void setAltDDName(String altDDName) {
+        this.altDDName = altDDName;
+        if (context != null) {
+            context.setAttribute(Globals.ALT_DD_ATTR,altDDName);
+        }
+    }
+
+
+    /**
+     * Return the compiler classpath.
+     */
+    public String getCompilerClasspath(){
+        return compilerClasspath;
+    }
+
+
+    /**
+     * Set the compiler classpath.
+     */
+    public void setCompilerClasspath(String compilerClasspath) {
+        this.compilerClasspath = compilerClasspath;
+    }
+
+
+    /**
+     * Set the display name of this web application.
+     *
+     * @param displayName The new display name
+     */
+    @Override
+    public void setDisplayName(String displayName) {
+
+        String oldDisplayName = this.displayName;
+        this.displayName = displayName;
+        support.firePropertyChange("displayName", oldDisplayName,
+                                   this.displayName);
+    }
+
+
+    /**
+     * Return the distributable flag for this web application.
+     */
+    @Override
+    public boolean getDistributable() {
+
+        return (this.distributable);
+
+    }
+
+    /**
+     * Set the distributable flag for this web application.
+     *
+     * @param distributable The new distributable flag
+     */
+    @Override
+    public void setDistributable(boolean distributable) {
+        boolean oldDistributable = this.distributable;
+        this.distributable = distributable;
+        support.firePropertyChange("distributable",
+                                   oldDistributable,
+                                   this.distributable);
+
+        // Bugzilla 32866
+        if(getManager() != null) {
+            if(log.isDebugEnabled()) {
+                log.debug("Propagating distributable=" + distributable
+                          + " to manager");
+            }
+            getManager().setDistributable(distributable);
+        }
+    }
+
+
+    /**
+     * Return the document root for this Context.  This can be an absolute
+     * pathname, a relative pathname, or a URL.
+     */
+    @Override
+    public String getDocBase() {
+
+        return (this.docBase);
+
+    }
+
+
+    /**
+     * Set the document root for this Context.  This can be an absolute
+     * pathname, a relative pathname, or a URL.
+     *
+     * @param docBase The new document root
+     */
+    @Override
+    public void setDocBase(String docBase) {
+
+        this.docBase = docBase;
+
+    }
+
+    /**
+     * Return descriptive information about this Container implementation and
+     * the corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+    public String getJ2EEApplication() {
+        return j2EEApplication;
+    }
+
+    public void setJ2EEApplication(String j2EEApplication) {
+        this.j2EEApplication = j2EEApplication;
+    }
+
+    public String getJ2EEServer() {
+        return j2EEServer;
+    }
+
+    public void setJ2EEServer(String j2EEServer) {
+        this.j2EEServer = j2EEServer;
+    }
+
+
+    /**
+     * Set the Loader with which this Context is associated.
+     *
+     * @param loader The newly associated loader
+     */
+    @Override
+    public synchronized void setLoader(Loader loader) {
+
+        super.setLoader(loader);
+
+    }
+
+
+    /**
+     * Return the boolean on the annotations parsing.
+     */
+    @Override
+    public boolean getIgnoreAnnotations() {
+        return this.ignoreAnnotations;
+    }
+    
+    
+    /**
+     * Set the boolean on the annotations parsing for this web 
+     * application.
+     * 
+     * @param ignoreAnnotations The boolean on the annotations parsing
+     */
+    @Override
+    public void setIgnoreAnnotations(boolean ignoreAnnotations) {
+        boolean oldIgnoreAnnotations = this.ignoreAnnotations;
+        this.ignoreAnnotations = ignoreAnnotations;
+        support.firePropertyChange("ignoreAnnotations", oldIgnoreAnnotations,
+                this.ignoreAnnotations);
+    }
+    
+    
+    /**
+     * Return the login configuration descriptor for this web application.
+     */
+    @Override
+    public LoginConfig getLoginConfig() {
+
+        return (this.loginConfig);
+
+    }
+
+
+    /**
+     * Set the login configuration descriptor for this web application.
+     *
+     * @param config The new login configuration
+     */
+    @Override
+    public void setLoginConfig(LoginConfig config) {
+
+        // Validate the incoming property value
+        if (config == null)
+            throw new IllegalArgumentException
+                (sm.getString("standardContext.loginConfig.required"));
+        String loginPage = config.getLoginPage();
+        if ((loginPage != null) && !loginPage.startsWith("/")) {
+            if (isServlet22()) {
+                if(log.isDebugEnabled())
+                    log.debug(sm.getString("standardContext.loginConfig.loginWarning",
+                                 loginPage));
+                config.setLoginPage("/" + loginPage);
+            } else {
+                throw new IllegalArgumentException
+                    (sm.getString("standardContext.loginConfig.loginPage",
+                                  loginPage));
+            }
+        }
+        String errorPage = config.getErrorPage();
+        if ((errorPage != null) && !errorPage.startsWith("/")) {
+            if (isServlet22()) {
+                if(log.isDebugEnabled())
+                    log.debug(sm.getString("standardContext.loginConfig.errorWarning",
+                                 errorPage));
+                config.setErrorPage("/" + errorPage);
+            } else {
+                throw new IllegalArgumentException
+                    (sm.getString("standardContext.loginConfig.errorPage",
+                                  errorPage));
+            }
+        }
+
+        // Process the property setting change
+        LoginConfig oldLoginConfig = this.loginConfig;
+        this.loginConfig = config;
+        support.firePropertyChange("loginConfig",
+                                   oldLoginConfig, this.loginConfig);
+
+    }
+
+
+    /**
+     * Get the mapper associated with the context.
+     */
+    @Override
+    public org.apache.tomcat.util.http.mapper.Mapper getMapper() {
+        return (mapper);
+    }
+
+
+    /**
+     * Return the naming resources associated with this web application.
+     */
+    @Override
+    public NamingResources getNamingResources() {
+
+        if (namingResources == null) {
+            setNamingResources(new NamingResources());
+        }
+        return (namingResources);
+
+    }
+
+
+    /**
+     * Set the naming resources for this web application.
+     *
+     * @param namingResources The new naming resources
+     */
+    @Override
+    public void setNamingResources(NamingResources namingResources) {
+
+        // Process the property setting change
+        NamingResources oldNamingResources = this.namingResources;
+        this.namingResources = namingResources;
+        if (namingResources != null) {
+            namingResources.setContainer(this);
+        }
+        support.firePropertyChange("namingResources",
+                                   oldNamingResources, this.namingResources);
+        
+        if (getState() == LifecycleState.NEW ||
+                getState() == LifecycleState.INITIALIZING ||
+                getState() == LifecycleState.INITIALIZED) {
+            // NEW will occur if Context is defined in server.xml
+            // At this point getObjectKeyPropertiesNameOnly() will trigger an
+            // NPE.
+            // INITIALIZED will occur if the Context is defined in a context.xml
+            // file
+            // If started now, a second start will be attempted when the context
+            // starts
+            
+            // In both cases, return and let context init the namingResources
+            // when it starts
+            return;
+        }
+        
+        if (oldNamingResources != null) {
+            try {
+                oldNamingResources.stop();
+                oldNamingResources.destroy();
+            } catch (LifecycleException e) {
+                log.warn("standardContext.namingResource.destroy.fail", e);
+            }
+        }
+        if (namingResources != null) {
+            try {
+                namingResources.init();
+                namingResources.start();
+            } catch (LifecycleException e) {
+                log.warn("standardContext.namingResource.init.fail", e);
+            }
+        }
+    }
+
+
+    /**
+     * Return the context path for this Context.
+     */
+    @Override
+    public String getPath() {
+        return (path);
+    }
+
+
+    /**
+     * Set the context path for this Context.
+     * 
+     * @param path The new context path
+     */
+    @Override
+    public void setPath(String path) {
+        if (path == null || (!path.equals("") && !path.startsWith("/"))) {
+            this.path = "/" + path;
+            log.warn(sm.getString(
+                    "standardContext.pathInvalid", path, this.path));
+        } else {
+            this.path = path;
+        }
+        encodedPath = urlEncoder.encode(this.path);
+        if (getName() == null) {
+            setName(this.path);
+        }
+    }
+
+
+    /**
+     * Return the public identifier of the deployment descriptor DTD that is
+     * currently being parsed.
+     */
+    @Override
+    public String getPublicId() {
+
+        return (this.publicId);
+
+    }
+
+
+    /**
+     * Set the public identifier of the deployment descriptor DTD that is
+     * currently being parsed.
+     *
+     * @param publicId The public identifier
+     */
+    @Override
+    public void setPublicId(String publicId) {
+
+        if (log.isDebugEnabled())
+            log.debug("Setting deployment descriptor public ID to '" +
+                publicId + "'");
+
+        String oldPublicId = this.publicId;
+        this.publicId = publicId;
+        support.firePropertyChange("publicId", oldPublicId, publicId);
+
+    }
+
+
+    /**
+     * Return the reloadable flag for this web application.
+     */
+    @Override
+    public boolean getReloadable() {
+
+        return (this.reloadable);
+
+    }
+
+
+    /**
+     * Return the default context override flag for this web application.
+     */
+    @Override
+    public boolean getOverride() {
+
+        return (this.override);
+
+    }
+
+
+    /**
+     * Return the original document root for this Context.  This can be an absolute
+     * pathname, a relative pathname, or a URL.
+     * Is only set as deployment has change docRoot!
+     */
+    public String getOriginalDocBase() {
+
+        return (this.originalDocBase);
+
+    }
+
+    /**
+     * Set the original document root for this Context.  This can be an absolute
+     * pathname, a relative pathname, or a URL.
+     *
+     * @param docBase The original document root
+     */
+    public void setOriginalDocBase(String docBase) {
+
+        this.originalDocBase = docBase;
+    }
+    
+
+    /**
+     * Return the parent class loader (if any) for this web application.
+     * This call is meaningful only <strong>after</strong> a Loader has
+     * been configured.
+     */
+    @Override
+    public ClassLoader getParentClassLoader() {
+        if (parentClassLoader != null)
+            return (parentClassLoader);
+        if (getPrivileged()) {
+            return this.getClass().getClassLoader();
+        } else if (parent != null) {
+            return (parent.getParentClassLoader());
+        }
+        return (ClassLoader.getSystemClassLoader());
+    }
+
+    
+    /**
+     * Return the privileged flag for this web application.
+     */
+    @Override
+    public boolean getPrivileged() {
+
+        return (this.privileged);
+
+    }
+
+
+    /**
+     * Set the privileged flag for this web application.
+     *
+     * @param privileged The new privileged flag
+     */
+    @Override
+    public void setPrivileged(boolean privileged) {
+
+        boolean oldPrivileged = this.privileged;
+        this.privileged = privileged;
+        support.firePropertyChange("privileged",
+                                   oldPrivileged,
+                                   this.privileged);
+
+    }
+
+
+    /**
+     * Set the reloadable flag for this web application.
+     *
+     * @param reloadable The new reloadable flag
+     */
+    @Override
+    public void setReloadable(boolean reloadable) {
+
+        boolean oldReloadable = this.reloadable;
+        this.reloadable = reloadable;
+        support.firePropertyChange("reloadable",
+                                   oldReloadable,
+                                   this.reloadable);
+
+    }
+
+
+    /**
+     * Set the default context override flag for this web application.
+     *
+     * @param override The new override flag
+     */
+    @Override
+    public void setOverride(boolean override) {
+
+        boolean oldOverride = this.override;
+        this.override = override;
+        support.firePropertyChange("override",
+                                   oldOverride,
+                                   this.override);
+
+    }
+
+
+    /**
+     * Return the "replace welcome files" property.
+     */
+    public boolean isReplaceWelcomeFiles() {
+
+        return (this.replaceWelcomeFiles);
+
+    }
+
+
+    /**
+     * Set the "replace welcome files" property.
+     *
+     * @param replaceWelcomeFiles The new property value
+     */
+    public void setReplaceWelcomeFiles(boolean replaceWelcomeFiles) {
+
+        boolean oldReplaceWelcomeFiles = this.replaceWelcomeFiles;
+        this.replaceWelcomeFiles = replaceWelcomeFiles;
+        support.firePropertyChange("replaceWelcomeFiles",
+                                   oldReplaceWelcomeFiles,
+                                   this.replaceWelcomeFiles);
+
+    }
+
+
+    /**
+     * Return the servlet context for which this Context is a facade.
+     */
+    @Override
+    public ServletContext getServletContext() {
+
+        if (context == null) {
+            context = new ApplicationContext(this);
+            if (altDDName != null)
+                context.setAttribute(Globals.ALT_DD_ATTR,altDDName);
+        }
+        return (context.getFacade());
+
+    }
+
+
+    /**
+     * Return the default session timeout (in minutes) for this
+     * web application.
+     */
+    @Override
+    public int getSessionTimeout() {
+
+        return (this.sessionTimeout);
+
+    }
+
+
+    /**
+     * Set the default session timeout (in minutes) for this
+     * web application.
+     *
+     * @param timeout The new default session timeout
+     */
+    @Override
+    public void setSessionTimeout(int timeout) {
+
+        int oldSessionTimeout = this.sessionTimeout;
+        /*
+         * SRV.13.4 ("Deployment Descriptor"):
+         * If the timeout is 0 or less, the container ensures the default
+         * behaviour of sessions is never to time out.
+         */
+        this.sessionTimeout = (timeout == 0) ? -1 : timeout;
+        support.firePropertyChange("sessionTimeout",
+                                   oldSessionTimeout,
+                                   this.sessionTimeout);
+
+    }
+
+
+    /**
+     * Return the value of the swallowOutput flag.
+     */
+    @Override
+    public boolean getSwallowOutput() {
+
+        return (this.swallowOutput);
+
+    }
+
+
+    /**
+     * Set the value of the swallowOutput flag. If set to true, the system.out
+     * and system.err will be redirected to the logger during a servlet
+     * execution.
+     *
+     * @param swallowOutput The new value
+     */
+    @Override
+    public void setSwallowOutput(boolean swallowOutput) {
+
+        boolean oldSwallowOutput = this.swallowOutput;
+        this.swallowOutput = swallowOutput;
+        support.firePropertyChange("swallowOutput",
+                                   oldSwallowOutput,
+                                   this.swallowOutput);
+
+    }
+
+
+    /**
+     * Return the value of the unloadDelay flag.
+     */
+    public long getUnloadDelay() {
+
+        return (this.unloadDelay);
+
+    }
+
+
+    /**
+     * Set the value of the unloadDelay flag, which represents the amount
+     * of ms that the container will wait when unloading servlets.
+     * Setting this to a small value may cause more requests to fail 
+     * to complete when stopping a web application.
+     *
+     * @param unloadDelay The new value
+     */
+    public void setUnloadDelay(long unloadDelay) {
+
+        long oldUnloadDelay = this.unloadDelay;
+        this.unloadDelay = unloadDelay;
+        support.firePropertyChange("unloadDelay",
+                                   Long.valueOf(oldUnloadDelay),
+                                   Long.valueOf(this.unloadDelay));
+
+    }
+
+
+    /**
+     * Unpack WAR flag accessor.
+     */
+    public boolean getUnpackWAR() {
+
+        return (unpackWAR);
+
+    }
+
+
+    /**
+     * Unpack WAR flag mutator.
+     */
+    public void setUnpackWAR(boolean unpackWAR) {
+
+        this.unpackWAR = unpackWAR;
+
+    }
+
+    /**
+     * Return the Java class name of the Wrapper implementation used
+     * for servlets registered in this Context.
+     */
+    @Override
+    public String getWrapperClass() {
+
+        return (this.wrapperClassName);
+
+    }
+
+
+    /**
+     * Set the Java class name of the Wrapper implementation used
+     * for servlets registered in this Context.
+     *
+     * @param wrapperClassName The new wrapper class name
+     *
+     * @throws IllegalArgumentException if the specified wrapper class
+     * cannot be found or is not a subclass of StandardWrapper
+     */
+    @Override
+    public void setWrapperClass(String wrapperClassName) {
+
+        this.wrapperClassName = wrapperClassName;
+
+        try {
+            wrapperClass = Class.forName(wrapperClassName);         
+            if (!StandardWrapper.class.isAssignableFrom(wrapperClass)) {
+                throw new IllegalArgumentException(
+                    sm.getString("standardContext.invalidWrapperClass",
+                                 wrapperClassName));
+            }
+        } catch (ClassNotFoundException cnfe) {
+            throw new IllegalArgumentException(cnfe.getMessage());
+        }
+    }
+
+
+    /**
+     * Set the resources DirContext object with which this Container is
+     * associated.
+     *
+     * @param resources The newly associated DirContext
+     */
+    @Override
+    public synchronized void setResources(DirContext resources) {
+
+        if (getState().isAvailable()) {
+            throw new IllegalStateException
+                (sm.getString("standardContext.resources.started"));
+        }
+
+        DirContext oldResources = this.webappResources;
+        if (oldResources == resources)
+            return;
+
+        if (resources instanceof BaseDirContext) {
+            // Caching
+            ((BaseDirContext) resources).setCached(isCachingAllowed());
+            ((BaseDirContext) resources).setCacheTTL(getCacheTTL());
+            ((BaseDirContext) resources).setCacheMaxSize(getCacheMaxSize());
+            ((BaseDirContext) resources).setCacheObjectMaxSize(
+                    getCacheObjectMaxSize());
+            // Alias support
+            ((BaseDirContext) resources).setAliases(getAliases());
+        }
+        if (resources instanceof FileDirContext) {
+            filesystemBased = true;
+            ((FileDirContext) resources).setAllowLinking(isAllowLinking());
+        }
+        this.webappResources = resources;
+
+        // The proxied resources will be refreshed on start
+        this.resources = null;
+
+        support.firePropertyChange("resources", oldResources,
+                                   this.webappResources);
+
+    }
+
+    
+    @Override
+    public JspConfigDescriptor getJspConfigDescriptor() {
+        return jspConfigDescriptor;
+    }
+
+
+    // ------------------------------------------------------ Public Properties
+
+
+    /**
+     * Return the Locale to character set mapper class for this Context.
+     */
+    public String getCharsetMapperClass() {
+
+        return (this.charsetMapperClass);
+
+    }
+
+
+    /**
+     * Set the Locale to character set mapper class for this Context.
+     *
+     * @param mapper The new mapper class
+     */
+    public void setCharsetMapperClass(String mapper) {
+
+        String oldCharsetMapperClass = this.charsetMapperClass;
+        this.charsetMapperClass = mapper;
+        support.firePropertyChange("charsetMapperClass",
+                                   oldCharsetMapperClass,
+                                   this.charsetMapperClass);
+
+    }
+
+
+    /** Get the absolute path to the work dir.
+     *  To avoid duplication.
+     * 
+     * @return The work path
+     */ 
+    public String getWorkPath() {
+        if (getWorkDir() == null) {
+            return null;
+        }
+        File workDir = new File(getWorkDir());
+        if (!workDir.isAbsolute()) {
+            File catalinaHome = engineBase();
+            String catalinaHomePath = null;
+            try {
+                catalinaHomePath = catalinaHome.getCanonicalPath();
+                workDir = new File(catalinaHomePath,
+                        getWorkDir());
+            } catch (IOException e) {
+                log.warn(sm.getString("standardContext.workPath", getName()),
+                        e);
+            }
+        }
+        return workDir.getAbsolutePath();
+    }
+    
+    /**
+     * Return the work directory for this Context.
+     */
+    public String getWorkDir() {
+
+        return (this.workDir);
+
+    }
+
+
+    /**
+     * Set the work directory for this Context.
+     *
+     * @param workDir The new work directory
+     */
+    public void setWorkDir(String workDir) {
+
+        this.workDir = workDir;
+
+        if (getState().isAvailable()) {
+            postWorkDirectory();
+        }
+    }
+
+
+    /**
+     * Save config ?
+     */
+    public boolean isSaveConfig() {
+        return saveConfig;
+    }
+
+
+    /**
+     * Set save config flag.
+     */
+    public void setSaveConfig(boolean saveConfig) {
+        this.saveConfig = saveConfig;
+    }
+
+
+    /**
+     * 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) {
+
+        boolean oldClearReferencesStatic = this.clearReferencesStatic;
+        this.clearReferencesStatic = clearReferencesStatic;
+        support.firePropertyChange("clearReferencesStatic",
+                                   oldClearReferencesStatic,
+                                   this.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) {
+
+        boolean oldClearReferencesStopThreads = this.clearReferencesStopThreads;
+        this.clearReferencesStopThreads = clearReferencesStopThreads;
+        support.firePropertyChange("clearReferencesStopThreads",
+                                   oldClearReferencesStopThreads,
+                                   this.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) {
+
+        boolean oldClearReferencesStopTimerThreads =
+            this.clearReferencesStopTimerThreads;
+        this.clearReferencesStopTimerThreads = clearReferencesStopTimerThreads;
+        support.firePropertyChange("clearReferencesStopTimerThreads",
+                                   oldClearReferencesStopTimerThreads,
+                                   this.clearReferencesStopTimerThreads);
+    }
+
+
+    /**
+     * 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;
+    }
+
+
+    public boolean getRenewThreadsWhenStoppingContext() {
+        return this.renewThreadsWhenStoppingContext;
+    }
+
+    public void setRenewThreadsWhenStoppingContext(
+            boolean renewThreadsWhenStoppingContext) {
+        boolean oldRenewThreadsWhenStoppingContext =
+                this.renewThreadsWhenStoppingContext;
+        this.renewThreadsWhenStoppingContext = renewThreadsWhenStoppingContext;
+        support.firePropertyChange("renewThreadsWhenStoppingContext",
+                oldRenewThreadsWhenStoppingContext,
+                this.renewThreadsWhenStoppingContext);
+    }
+
+    // -------------------------------------------------------- Context Methods
+
+
+    /**
+     * Add a new Listener class name to the set of Listeners
+     * configured for this application.
+     *
+     * @param listener Java class name of a listener class
+     */
+    @Override
+    public void addApplicationListener(String listener) {
+
+        synchronized (applicationListenersLock) {
+            String results[] =new String[applicationListeners.length + 1];
+            for (int i = 0; i < applicationListeners.length; i++) {
+                if (listener.equals(applicationListeners[i])) {
+                    log.info(sm.getString(
+                            "standardContext.duplicateListener",listener));
+                    return;
+                }
+                results[i] = applicationListeners[i];
+            }
+            results[applicationListeners.length] = listener;
+            applicationListeners = results;
+        }
+        fireContainerEvent("addApplicationListener", listener);
+
+        // FIXME - add instance if already started?
+
+    }
+
+
+    /**
+     * Add a new application parameter for this application.
+     *
+     * @param parameter The new application parameter
+     */
+    @Override
+    public void addApplicationParameter(ApplicationParameter parameter) {
+
+        synchronized (applicationParametersLock) {
+            String newName = parameter.getName();
+            for (ApplicationParameter p : applicationParameters) {
+                if (newName.equals(p.getName()) && !p.getOverride())
+                    return;
+            }
+            ApplicationParameter results[] = Arrays.copyOf(
+                    applicationParameters, applicationParameters.length + 1);
+            results[applicationParameters.length] = parameter;
+            applicationParameters = results;
+        }
+        fireContainerEvent("addApplicationParameter", parameter);
+
+    }
+
+
+    /**
+     * Add a child Container, only if the proposed child is an implementation
+     * of Wrapper.
+     *
+     * @param child Child container to be added
+     *
+     * @exception IllegalArgumentException if the proposed container is
+     *  not an implementation of Wrapper
+     */
+    @Override
+    public void addChild(Container child) {
+
+        // Global JspServlet
+        Wrapper oldJspServlet = null;
+
+        if (!(child instanceof Wrapper)) {
+            throw new IllegalArgumentException
+                (sm.getString("standardContext.notWrapper"));
+        }
+
+        boolean isJspServlet = "jsp".equals(child.getName());
+
+        // Allow webapp to override JspServlet inherited from global web.xml.
+        if (isJspServlet) {
+            oldJspServlet = (Wrapper) findChild("jsp");
+            if (oldJspServlet != null) {
+                removeChild(oldJspServlet);
+            }
+        }
+
+        super.addChild(child);
+
+        if (isJspServlet && oldJspServlet != null) {
+            /*
+             * The webapp-specific JspServlet inherits all the mappings
+             * specified in the global web.xml, and may add additional ones.
+             */
+            String[] jspMappings = oldJspServlet.findMappings();
+            for (int i=0; jspMappings!=null && i<jspMappings.length; i++) {
+                addServletMapping(jspMappings[i], child.getName());
+            }
+        }
+    }
+
+
+    /**
+     * Add a security constraint to the set for this web application.
+     */
+    @Override
+    public void addConstraint(SecurityConstraint constraint) {
+
+        // Validate the proposed constraint
+        SecurityCollection collections[] = constraint.findCollections();
+        for (int i = 0; i < collections.length; i++) {
+            String patterns[] = collections[i].findPatterns();
+            for (int j = 0; j < patterns.length; j++) {
+                patterns[j] = adjustURLPattern(patterns[j]);
+                if (!validateURLPattern(patterns[j]))
+                    throw new IllegalArgumentException
+                        (sm.getString
+                         ("standardContext.securityConstraint.pattern",
+                          patterns[j]));
+            }
+            if (collections[i].findMethods().length > 0 &&
+                    collections[i].findOmittedMethods().length > 0) {
+                throw new IllegalArgumentException(sm.getString(
+                        "standardContext.securityConstraint.mixHttpMethod"));
+            }
+        }
+
+        // Add this constraint to the set for our web application
+        synchronized (constraintsLock) {
+            SecurityConstraint results[] =
+                new SecurityConstraint[constraints.length + 1];
+            for (int i = 0; i < constraints.length; i++)
+                results[i] = constraints[i];
+            results[constraints.length] = constraint;
+            constraints = results;
+        }
+
+    }
+
+
+
+    /**
+     * Add an error page for the specified error or Java exception.
+     *
+     * @param errorPage The error page definition to be added
+     */
+    @Override
+    public void addErrorPage(ErrorPage errorPage) {
+        // Validate the input parameters
+        if (errorPage == null)
+            throw new IllegalArgumentException
+                (sm.getString("standardContext.errorPage.required"));
+        String location = errorPage.getLocation();
+        if ((location != null) && !location.startsWith("/")) {
+            if (isServlet22()) {
+                if(log.isDebugEnabled())
+                    log.debug(sm.getString("standardContext.errorPage.warning",
+                                 location));
+                errorPage.setLocation("/" + location);
+            } else {
+                throw new IllegalArgumentException
+                    (sm.getString("standardContext.errorPage.error",
+                                  location));
+            }
+        }
+
+        // Add the specified error page to our internal collections
+        String exceptionType = errorPage.getExceptionType();
+        if (exceptionType != null) {
+            synchronized (exceptionPages) {
+                exceptionPages.put(exceptionType, errorPage);
+            }
+        } else {
+            synchronized (statusPages) {
+                if (errorPage.getErrorCode() == 200) {
+                    this.okErrorPage = errorPage;
+                }
+                statusPages.put(Integer.valueOf(errorPage.getErrorCode()),
+                                errorPage);
+            }
+        }
+        fireContainerEvent("addErrorPage", errorPage);
+
+    }
+
+
+    /**
+     * Add a filter definition to this Context.
+     *
+     * @param filterDef The filter definition to be added
+     */
+    @Override
+    public void addFilterDef(FilterDef filterDef) {
+
+        synchronized (filterDefs) {
+            filterDefs.put(filterDef.getFilterName(), filterDef);
+        }
+        fireContainerEvent("addFilterDef", filterDef);
+
+    }
+
+
+    /**
+     * Add a filter mapping to this Context at the end of the current set
+     * of filter mappings.
+     *
+     * @param filterMap The filter mapping to be added
+     *
+     * @exception IllegalArgumentException if the specified filter name
+     *  does not match an existing filter definition, or the filter mapping
+     *  is malformed
+     */
+    @Override
+    public void addFilterMap(FilterMap filterMap) {
+        validateFilterMap(filterMap);
+        // Add this filter mapping to our registered set
+        filterMaps.add(filterMap);
+        fireContainerEvent("addFilterMap", filterMap);
+    }
+
+    
+    /**
+     * Add a filter mapping to this Context before the mappings defined in the
+     * deployment descriptor but after any other mappings added via this method.
+     *
+     * @param filterMap The filter mapping to be added
+     *
+     * @exception IllegalArgumentException if the specified filter name
+     *  does not match an existing filter definition, or the filter mapping
+     *  is malformed
+     */
+    @Override
+    public void addFilterMapBefore(FilterMap filterMap) {
+        validateFilterMap(filterMap);
+        // Add this filter mapping to our registered set
+        filterMaps.addBefore(filterMap);
+        fireContainerEvent("addFilterMap", filterMap);
+    }
+
+
+    /**
+     * Validate the supplied FilterMap.
+     */
+    private void validateFilterMap(FilterMap filterMap) {
+        // Validate the proposed filter mapping
+        String filterName = filterMap.getFilterName();
+        String[] servletNames = filterMap.getServletNames();
+        String[] urlPatterns = filterMap.getURLPatterns();
+        if (findFilterDef(filterName) == null)
+            throw new IllegalArgumentException
+                (sm.getString("standardContext.filterMap.name", filterName));
+
+        if (!filterMap.getMatchAllServletNames() && 
+            !filterMap.getMatchAllUrlPatterns() && 
+            (servletNames.length == 0) && (urlPatterns.length == 0))
+            throw new IllegalArgumentException
+                (sm.getString("standardContext.filterMap.either"));
+        // FIXME: Older spec revisions may still check this
+        /*
+        if ((servletNames.length != 0) && (urlPatterns.length != 0))
+            throw new IllegalArgumentException
+                (sm.getString("standardContext.filterMap.either"));
+        */
+        for (int i = 0; i < urlPatterns.length; i++) {
+            if (!validateURLPattern(urlPatterns[i])) {
+                throw new IllegalArgumentException
+                    (sm.getString("standardContext.filterMap.pattern",
+                            urlPatterns[i]));
+            }
+        }
+    }
+
+    /**
+     * Add the classname of an InstanceListener to be added to each
+     * Wrapper appended to this Context.
+     *
+     * @param listener Java class name of an InstanceListener class
+     */
+    @Override
+    public void addInstanceListener(String listener) {
+
+        synchronized (instanceListenersLock) {
+            String results[] =new String[instanceListeners.length + 1];
+            for (int i = 0; i < instanceListeners.length; i++)
+                results[i] = instanceListeners[i];
+            results[instanceListeners.length] = listener;
+            instanceListeners = results;
+        }
+        fireContainerEvent("addInstanceListener", listener);
+
+    }
+
+    /**
+     * Add a Locale Encoding Mapping (see Sec 5.4 of Servlet spec 2.4)
+     *
+     * @param locale locale to map an encoding for
+     * @param encoding encoding to be used for a give locale
+     */
+    @Override
+    public void addLocaleEncodingMappingParameter(String locale, String encoding){
+        getCharsetMapper().addCharsetMappingFromDeploymentDescriptor(locale, encoding);
+    }
+
+
+    /**
+     * Add a message destination for this web application.
+     *
+     * @param md New message destination
+     */
+    public void addMessageDestination(MessageDestination md) {
+
+        synchronized (messageDestinations) {
+            messageDestinations.put(md.getName(), md);
+        }
+        fireContainerEvent("addMessageDestination", md.getName());
+
+    }
+
+
+    /**
+     * Add a message destination reference for this web application.
+     *
+     * @param mdr New message destination reference
+     */
+    public void addMessageDestinationRef
+        (MessageDestinationRef mdr) {
+
+        namingResources.addMessageDestinationRef(mdr);
+        fireContainerEvent("addMessageDestinationRef", mdr.getName());
+
+    }
+
+
+    /**
+     * Add a new MIME mapping, replacing any existing mapping for
+     * the specified extension.
+     *
+     * @param extension Filename extension being mapped
+     * @param mimeType Corresponding MIME type
+     */
+    @Override
+    public void addMimeMapping(String extension, String mimeType) {
+
+        synchronized (mimeMappings) {
+            mimeMappings.put(extension, mimeType);
+        }
+        fireContainerEvent("addMimeMapping", extension);
+
+    }
+
+
+    /**
+     * Add a new context initialization parameter.
+     *
+     * @param name Name of the new parameter
+     * @param value Value of the new  parameter
+     *
+     * @exception IllegalArgumentException if the name or value is missing,
+     *  or if this context initialization parameter has already been
+     *  registered
+     */
+    @Override
+    public void addParameter(String name, String value) {
+        // Validate the proposed context initialization parameter
+        if ((name == null) || (value == null))
+            throw new IllegalArgumentException
+                (sm.getString("standardContext.parameter.required"));
+        if (parameters.get(name) != null)
+            throw new IllegalArgumentException
+                (sm.getString("standardContext.parameter.duplicate", name));
+
+        // Add this parameter to our defined set
+        synchronized (parameters) {
+            parameters.put(name, value);
+        }
+        fireContainerEvent("addParameter", name);
+
+    }
+
+
+    /**
+     * Add a security role reference for this web application.
+     *
+     * @param role Security role used in the application
+     * @param link Actual security role to check for
+     */
+    @Override
+    public void addRoleMapping(String role, String link) {
+
+        synchronized (roleMappings) {
+            roleMappings.put(role, link);
+        }
+        fireContainerEvent("addRoleMapping", role);
+
+    }
+
+
+    /**
+     * Add a new security role for this web application.
+     *
+     * @param role New security role
+     */
+    @Override
+    public void addSecurityRole(String role) {
+
+        synchronized (securityRolesLock) {
+            String results[] =new String[securityRoles.length + 1];
+            for (int i = 0; i < securityRoles.length; i++)
+                results[i] = securityRoles[i];
+            results[securityRoles.length] = role;
+            securityRoles = results;
+        }
+        fireContainerEvent("addSecurityRole", role);
+
+    }
+
+
+    /**
+     * Add a new servlet mapping, replacing any existing mapping for
+     * the specified pattern.
+     *
+     * @param pattern URL pattern to be mapped
+     * @param name Name of the corresponding servlet to execute
+     *
+     * @exception IllegalArgumentException if the specified servlet name
+     *  is not known to this Context
+     */
+    @Override
+    public void addServletMapping(String pattern, String name) {
+        addServletMapping(pattern, name, false);
+    }
+
+
+    /**
+     * Add a new servlet mapping, replacing any existing mapping for
+     * the specified pattern.
+     *
+     * @param pattern URL pattern to be mapped
+     * @param name Name of the corresponding servlet to execute
+     * @param jspWildCard true if name identifies the JspServlet
+     * and pattern contains a wildcard; false otherwise
+     *
+     * @exception IllegalArgumentException if the specified servlet name
+     *  is not known to this Context
+     */
+    @Override
+    public void addServletMapping(String pattern, String name,
+                                  boolean jspWildCard) {
+        // Validate the proposed mapping
+        if (findChild(name) == null)
+            throw new IllegalArgumentException
+                (sm.getString("standardContext.servletMap.name", name));
+        String decodedPattern = adjustURLPattern(RequestUtil.URLDecode(pattern));
+        if (!validateURLPattern(decodedPattern))
+            throw new IllegalArgumentException
+                (sm.getString("standardContext.servletMap.pattern", decodedPattern));
+
+        // Add this mapping to our registered set
+        synchronized (servletMappingsLock) {
+            String name2 = servletMappings.get(decodedPattern);
+            if (name2 != null) {
+                // Don't allow more than one servlet on the same pattern
+                Wrapper wrapper = (Wrapper) findChild(name2);
+                wrapper.removeMapping(decodedPattern);
+                mapper.removeWrapper(decodedPattern);
+            }
+            servletMappings.put(decodedPattern, name);
+        }
+        Wrapper wrapper = (Wrapper) findChild(name);
+        wrapper.addMapping(decodedPattern);
+
+        // Update context mapper
+        mapper.addWrapper(decodedPattern, wrapper, jspWildCard,
+                resourceOnlyServlets.contains(name));
+
+        fireContainerEvent("addServletMapping", decodedPattern);
+
+    }
+
+
+    /**
+     * Add a new watched resource to the set recognized by this Context.
+     *
+     * @param name New watched resource file name
+     */
+    @Override
+    public void addWatchedResource(String name) {
+
+        synchronized (watchedResourcesLock) {
+            String results[] = new String[watchedResources.length + 1];
+            for (int i = 0; i < watchedResources.length; i++)
+                results[i] = watchedResources[i];
+            results[watchedResources.length] = name;
+            watchedResources = results;
+        }
+        fireContainerEvent("addWatchedResource", name);
+
+    }
+
+
+    /**
+     * Add a new welcome file to the set recognized by this Context.
+     *
+     * @param name New welcome file name
+     */
+    @Override
+    public void addWelcomeFile(String name) {
+
+        synchronized (welcomeFilesLock) {
+            // Welcome files from the application deployment descriptor
+            // completely replace those from the default conf/web.xml file
+            if (replaceWelcomeFiles) {
+                fireContainerEvent(CLEAR_WELCOME_FILES_EVENT, null);
+                welcomeFiles = new String[0];
+                setReplaceWelcomeFiles(false);
+            }
+            String results[] =new String[welcomeFiles.length + 1];
+            for (int i = 0; i < welcomeFiles.length; i++)
+                results[i] = welcomeFiles[i];
+            results[welcomeFiles.length] = name;
+            welcomeFiles = results;
+        }
+        if(this.getState().equals(LifecycleState.STARTED))
+            fireContainerEvent(ADD_WELCOME_FILE_EVENT, name);
+    }
+
+
+    /**
+     * Add the classname of a LifecycleListener to be added to each
+     * Wrapper appended to this Context.
+     *
+     * @param listener Java class name of a LifecycleListener class
+     */
+    @Override
+    public void addWrapperLifecycle(String listener) {
+
+        synchronized (wrapperLifecyclesLock) {
+            String results[] =new String[wrapperLifecycles.length + 1];
+            for (int i = 0; i < wrapperLifecycles.length; i++)
+                results[i] = wrapperLifecycles[i];
+            results[wrapperLifecycles.length] = listener;
+            wrapperLifecycles = results;
+        }
+        fireContainerEvent("addWrapperLifecycle", listener);
+
+    }
+
+
+    /**
+     * Add the classname of a ContainerListener to be added to each
+     * Wrapper appended to this Context.
+     *
+     * @param listener Java class name of a ContainerListener class
+     */
+    @Override
+    public void addWrapperListener(String listener) {
+
+        synchronized (wrapperListenersLock) {
+            String results[] =new String[wrapperListeners.length + 1];
+            for (int i = 0; i < wrapperListeners.length; i++)
+                results[i] = wrapperListeners[i];
+            results[wrapperListeners.length] = listener;
+            wrapperListeners = results;
+        }
+        fireContainerEvent("addWrapperListener", listener);
+
+    }
+
+
+    /**
+     * Factory method to create and return a new Wrapper instance, of
+     * the Java implementation class appropriate for this Context
+     * implementation.  The constructor of the instantiated Wrapper
+     * will have been called, but no properties will have been set.
+     */
+    @Override
+    public Wrapper createWrapper() {
+
+        Wrapper wrapper = null;
+        if (wrapperClass != null) {
+            try {
+                wrapper = (Wrapper) wrapperClass.newInstance();
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                log.error("createWrapper", t);
+                return (null);
+            }
+        } else {
+            wrapper = new StandardWrapper();
+        }
+
+        synchronized (instanceListenersLock) {
+            for (int i = 0; i < instanceListeners.length; i++) {
+                try {
+                    Class<?> clazz = Class.forName(instanceListeners[i]);
+                    InstanceListener listener =
+                      (InstanceListener) clazz.newInstance();
+                    wrapper.addInstanceListener(listener);
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    log.error("createWrapper", t);
+                    return (null);
+                }
+            }
+        }
+
+        synchronized (wrapperLifecyclesLock) {
+            for (int i = 0; i < wrapperLifecycles.length; i++) {
+                try {
+                    Class<?> clazz = Class.forName(wrapperLifecycles[i]);
+                    LifecycleListener listener =
+                        (LifecycleListener) clazz.newInstance();
+                    wrapper.addLifecycleListener(listener);
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    log.error("createWrapper", t);
+                    return (null);
+                }
+            }
+        }
+
+        synchronized (wrapperListenersLock) {
+            for (int i = 0; i < wrapperListeners.length; i++) {
+                try {
+                    Class<?> clazz = Class.forName(wrapperListeners[i]);
+                    ContainerListener listener =
+                      (ContainerListener) clazz.newInstance();
+                    wrapper.addContainerListener(listener);
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    log.error("createWrapper", t);
+                    return (null);
+                }
+            }
+        }
+
+        return (wrapper);
+
+    }
+
+
+    /**
+     * Return the set of application listener class names configured
+     * for this application.
+     */
+    @Override
+    public String[] findApplicationListeners() {
+
+        return (applicationListeners);
+
+    }
+
+
+    /**
+     * Return the set of application parameters for this application.
+     */
+    @Override
+    public ApplicationParameter[] findApplicationParameters() {
+
+        synchronized (applicationParametersLock) {
+            return (applicationParameters);
+        }
+
+    }
+
+
+    /**
+     * Return the security constraints for this web application.
+     * If there are none, a zero-length array is returned.
+     */
+    @Override
+    public SecurityConstraint[] findConstraints() {
+
+        return (constraints);
+
+    }
+
+
+    /**
+     * 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
+     */
+    @Override
+    public ErrorPage findErrorPage(int errorCode) {
+        if (errorCode == 200) {
+            return (okErrorPage);
+        } else {
+            return (statusPages.get(Integer.valueOf(errorCode)));
+        }
+
+    }
+
+
+    /**
+     * 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
+     */
+    @Override
+    public ErrorPage findErrorPage(String exceptionType) {
+
+        synchronized (exceptionPages) {
+            return (exceptionPages.get(exceptionType));
+        }
+
+    }
+
+
+    /**
+     * Return the set of defined error pages for all specified error codes
+     * and exception types.
+     */
+    @Override
+    public ErrorPage[] findErrorPages() {
+
+        synchronized(exceptionPages) {
+            synchronized(statusPages) {
+                ErrorPage results1[] = new ErrorPage[exceptionPages.size()];
+                results1 = exceptionPages.values().toArray(results1);
+                ErrorPage results2[] = new ErrorPage[statusPages.size()];
+                results2 = statusPages.values().toArray(results2);
+                ErrorPage results[] =
+                    new ErrorPage[results1.length + results2.length];
+                for (int i = 0; i < results1.length; i++)
+                    results[i] = results1[i];
+                for (int i = results1.length; i < results.length; i++)
+                    results[i] = results2[i - results1.length];
+                return (results);
+            }
+        }
+
+    }
+
+
+    /**
+     * Return the filter definition for the specified filter name, if any;
+     * otherwise return <code>null</code>.
+     *
+     * @param filterName Filter name to look up
+     */
+    @Override
+    public FilterDef findFilterDef(String filterName) {
+
+        synchronized (filterDefs) {
+            return (filterDefs.get(filterName));
+        }
+
+    }
+
+
+    /**
+     * Return the set of defined filters for this Context.
+     */
+    @Override
+    public FilterDef[] findFilterDefs() {
+
+        synchronized (filterDefs) {
+            FilterDef results[] = new FilterDef[filterDefs.size()];
+            return (filterDefs.values().toArray(results));
+        }
+
+    }
+
+
+    /**
+     * Return the set of filter mappings for this Context.
+     */
+    @Override
+    public FilterMap[] findFilterMaps() {
+        return filterMaps.asArray();
+    }
+
+
+    /**
+     * Return the set of InstanceListener classes that will be added to
+     * newly created Wrappers automatically.
+     */
+    @Override
+    public String[] findInstanceListeners() {
+
+        synchronized (instanceListenersLock) {
+            return (instanceListeners);
+        }
+
+    }
+
+
+    /**
+     * FIXME: Fooling introspection ...
+     */
+    public Context findMappingObject() {
+        return (Context) getMappingObject();
+    }
+    
+    
+    /**
+     * Return the message destination with the specified name, if any;
+     * otherwise, return <code>null</code>.
+     *
+     * @param name Name of the desired message destination
+     */
+    public MessageDestination findMessageDestination(String name) {
+
+        synchronized (messageDestinations) {
+            return (messageDestinations.get(name));
+        }
+
+    }
+
+
+    /**
+     * Return the set of defined message destinations for this web
+     * application.  If none have been defined, a zero-length array
+     * is returned.
+     */
+    public MessageDestination[] findMessageDestinations() {
+
+        synchronized (messageDestinations) {
+            MessageDestination results[] =
+                new MessageDestination[messageDestinations.size()];
+            return (messageDestinations.values().toArray(results));
+        }
+
+    }
+
+
+    /**
+     * Return the message destination ref with the specified name, if any;
+     * otherwise, return <code>null</code>.
+     *
+     * @param name Name of the desired message destination ref
+     */
+    public MessageDestinationRef
+        findMessageDestinationRef(String name) {
+
+        return namingResources.findMessageDestinationRef(name);
+
+    }
+
+
+    /**
+     * Return the set of defined message destination refs for this web
+     * application.  If none have been defined, a zero-length array
+     * is returned.
+     */
+    public MessageDestinationRef[]
+        findMessageDestinationRefs() {
+
+        return namingResources.findMessageDestinationRefs();
+
+    }
+
+
+    /**
+     * Return the MIME type to which the specified extension is mapped,
+     * if any; otherwise return <code>null</code>.
+     *
+     * @param extension Extension to map to a MIME type
+     */
+    @Override
+    public String findMimeMapping(String extension) {
+
+        return (mimeMappings.get(extension));
+
+    }
+
+
+    /**
+     * Return the extensions for which MIME mappings are defined.  If there
+     * are none, a zero-length array is returned.
+     */
+    @Override
+    public String[] findMimeMappings() {
+
+        synchronized (mimeMappings) {
+            String results[] = new String[mimeMappings.size()];
+            return
+                (mimeMappings.keySet().toArray(results));
+        }
+
+    }
+
+
+    /**
+     * Return the value for the specified context initialization
+     * parameter name, if any; otherwise return <code>null</code>.
+     *
+     * @param name Name of the parameter to return
+     */
+    @Override
+    public String findParameter(String name) {
+
+        synchronized (parameters) {
+            return (parameters.get(name));
+        }
+
+    }
+
+
+    /**
+     * Return the names of all defined context initialization parameters
+     * for this Context.  If no parameters are defined, a zero-length
+     * array is returned.
+     */
+    @Override
+    public String[] findParameters() {
+
+        synchronized (parameters) {
+            String results[] = new String[parameters.size()];
+            return (parameters.keySet().toArray(results));
+        }
+
+    }
+
+
+    /**
+     * For the given security role (as used by an application), return the
+     * corresponding role name (as defined by the underlying Realm) if there
+     * is one.  Otherwise, return the specified role unchanged.
+     *
+     * @param role Security role to map
+     */
+    @Override
+    public String findRoleMapping(String role) {
+
+        String realRole = null;
+        synchronized (roleMappings) {
+            realRole = roleMappings.get(role);
+        }
+        if (realRole != null)
+            return (realRole);
+        else
+            return (role);
+
+    }
+
+
+    /**
+     * Return <code>true</code> if the specified security role is defined
+     * for this application; otherwise return <code>false</code>.
+     *
+     * @param role Security role to verify
+     */
+    @Override
+    public boolean findSecurityRole(String role) {
+
+        synchronized (securityRolesLock) {
+            for (int i = 0; i < securityRoles.length; i++) {
+                if (role.equals(securityRoles[i]))
+                    return (true);
+            }
+        }
+        return (false);
+
+    }
+
+
+    /**
+     * Return the security roles defined for this application.  If none
+     * have been defined, a zero-length array is returned.
+     */
+    @Override
+    public String[] findSecurityRoles() {
+
+        synchronized (securityRolesLock) {
+            return (securityRoles);
+        }
+
+    }
+
+
+    /**
+     * Return the servlet name mapped by the specified pattern (if any);
+     * otherwise return <code>null</code>.
+     *
+     * @param pattern Pattern for which a mapping is requested
+     */
+    @Override
+    public String findServletMapping(String pattern) {
+
+        synchronized (servletMappingsLock) {
+            return (servletMappings.get(pattern));
+        }
+
+    }
+
+
+    /**
+     * Return the patterns of all defined servlet mappings for this
+     * Context.  If no mappings are defined, a zero-length array is returned.
+     */
+    @Override
+    public String[] findServletMappings() {
+
+        synchronized (servletMappingsLock) {
+            String results[] = new String[servletMappings.size()];
+            return
+               (servletMappings.keySet().toArray(results));
+        }
+
+    }
+
+
+    /**
+     * Return the context-relative URI of the error page for the specified
+     * HTTP status code, if any; otherwise return <code>null</code>.
+     *
+     * @param status HTTP status code to look up
+     */
+    @Override
+    public String findStatusPage(int status) {
+
+        ErrorPage errorPage = statusPages.get(Integer.valueOf(status));
+        if (errorPage!=null) {
+            return errorPage.getLocation();
+        }
+        return null;
+
+    }
+
+
+    /**
+     * Return the set of HTTP status codes for which error pages have
+     * been specified.  If none are specified, a zero-length array
+     * is returned.
+     */
+    @Override
+    public int[] findStatusPages() {
+
+        synchronized (statusPages) {
+            int results[] = new int[statusPages.size()];
+            Iterator<Integer> elements = statusPages.keySet().iterator();
+            int i = 0;
+            while (elements.hasNext())
+                results[i++] = elements.next().intValue();
+            return (results);
+        }
+
+    }
+
+
+    /**
+     * Return <code>true</code> if the specified welcome file is defined
+     * for this Context; otherwise return <code>false</code>.
+     *
+     * @param name Welcome file to verify
+     */
+    @Override
+    public boolean findWelcomeFile(String name) {
+
+        synchronized (welcomeFilesLock) {
+            for (int i = 0; i < welcomeFiles.length; i++) {
+                if (name.equals(welcomeFiles[i]))
+                    return (true);
+            }
+        }
+        return (false);
+
+    }
+
+
+    /**
+     * Return the set of watched resources for this Context. If none are 
+     * defined, a zero length array will be returned.
+     */
+    @Override
+    public String[] findWatchedResources() {
+        synchronized (watchedResourcesLock) {
+            return watchedResources;
+        }
+    }
+    
+    
+    /**
+     * Return the set of welcome files defined for this Context.  If none are
+     * defined, a zero-length array is returned.
+     */
+    @Override
+    public String[] findWelcomeFiles() {
+
+        synchronized (welcomeFilesLock) {
+            return (welcomeFiles);
+        }
+
+    }
+
+
+    /**
+     * Return the set of LifecycleListener classes that will be added to
+     * newly created Wrappers automatically.
+     */
+    @Override
+    public String[] findWrapperLifecycles() {
+
+        synchronized (wrapperLifecyclesLock) {
+            return (wrapperLifecycles);
+        }
+
+    }
+
+
+    /**
+     * Return the set of ContainerListener classes that will be added to
+     * newly created Wrappers automatically.
+     */
+    @Override
+    public String[] findWrapperListeners() {
+
+        synchronized (wrapperListenersLock) {
+            return (wrapperListeners);
+        }
+
+    }
+
+
+    /**
+     * Reload this web application, if reloading is supported.
+     * <p>
+     * <b>IMPLEMENTATION NOTE</b>:  This method is designed to deal with
+     * reloads required by changes to classes in the underlying repositories
+     * of our class loader.  It does not handle changes to the web application
+     * deployment descriptor.  If that has occurred, you should stop this
+     * Context and create (and start) a new Context instance instead.
+     *
+     * @exception IllegalStateException if the <code>reloadable</code>
+     *  property is set to <code>false</code>.
+     */
+    @Override
+    public synchronized void reload() {
+
+        // Validate our current component state
+        if (!getState().isAvailable())
+            throw new IllegalStateException
+                (sm.getString("standardContext.notStarted", getName()));
+
+        if(log.isInfoEnabled())
+            log.info(sm.getString("standardContext.reloadingStarted",
+                    getName()));
+
+        // Stop accepting requests temporarily
+        setPaused(true);
+
+        try {
+            stop();
+        } catch (LifecycleException e) {
+            log.error(
+                sm.getString("standardContext.stoppingContext", getName()), e);
+        }
+
+        try {
+            start();
+        } catch (LifecycleException e) {
+            log.error(
+                sm.getString("standardContext.startingContext", getName()), e);
+        }
+
+        setPaused(false);
+
+        if(log.isInfoEnabled())
+            log.info(sm.getString("standardContext.reloadingCompleted",
+                    getName()));
+
+    }
+
+
+    /**
+     * Remove the specified application listener class from the set of
+     * listeners for this application.
+     *
+     * @param listener Java class name of the listener to be removed
+     */
+    @Override
+    public void removeApplicationListener(String listener) {
+
+        synchronized (applicationListenersLock) {
+
+            // Make sure this welcome file is currently present
+            int n = -1;
+            for (int i = 0; i < applicationListeners.length; i++) {
+                if (applicationListeners[i].equals(listener)) {
+                    n = i;
+                    break;
+                }
+            }
+            if (n < 0)
+                return;
+
+            // Remove the specified constraint
+            int j = 0;
+            String results[] = new String[applicationListeners.length - 1];
+            for (int i = 0; i < applicationListeners.length; i++) {
+                if (i != n)
+                    results[j++] = applicationListeners[i];
+            }
+            applicationListeners = results;
+
+        }
+
+        // Inform interested listeners
+        fireContainerEvent("removeApplicationListener", listener);
+
+        // FIXME - behavior if already started?
+
+    }
+
+
+    /**
+     * Remove the application parameter with the specified name from
+     * the set for this application.
+     *
+     * @param name Name of the application parameter to remove
+     */
+    @Override
+    public void removeApplicationParameter(String name) {
+
+        synchronized (applicationParametersLock) {
+
+            // Make sure this parameter is currently present
+            int n = -1;
+            for (int i = 0; i < applicationParameters.length; i++) {
+                if (name.equals(applicationParameters[i].getName())) {
+                    n = i;
+                    break;
+                }
+            }
+            if (n < 0)
+                return;
+
+            // Remove the specified parameter
+            int j = 0;
+            ApplicationParameter results[] =
+                new ApplicationParameter[applicationParameters.length - 1];
+            for (int i = 0; i < applicationParameters.length; i++) {
+                if (i != n)
+                    results[j++] = applicationParameters[i];
+            }
+            applicationParameters = results;
+
+        }
+
+        // Inform interested listeners
+        fireContainerEvent("removeApplicationParameter", name);
+
+    }
+
+
+    /**
+     * Add a child Container, only if the proposed child is an implementation
+     * of Wrapper.
+     *
+     * @param child Child container to be added
+     *
+     * @exception IllegalArgumentException if the proposed container is
+     *  not an implementation of Wrapper
+     */
+    @Override
+    public void removeChild(Container child) {
+
+        if (!(child instanceof Wrapper)) {
+            throw new IllegalArgumentException
+                (sm.getString("standardContext.notWrapper"));
+        }
+
+        super.removeChild(child);
+
+    }
+
+
+    /**
+     * Remove the specified security constraint from this web application.
+     *
+     * @param constraint Constraint to be removed
+     */
+    @Override
+    public void removeConstraint(SecurityConstraint constraint) {
+
+        synchronized (constraintsLock) {
+
+            // Make sure this constraint is currently present
+            int n = -1;
+            for (int i = 0; i < constraints.length; i++) {
+                if (constraints[i].equals(constraint)) {
+                    n = i;
+                    break;
+                }
+            }
+            if (n < 0)
+                return;
+
+            // Remove the specified constraint
+            int j = 0;
+            SecurityConstraint results[] =
+                new SecurityConstraint[constraints.length - 1];
+            for (int i = 0; i < constraints.length; i++) {
+                if (i != n)
+                    results[j++] = constraints[i];
+            }
+            constraints = results;
+
+        }
+
+        // Inform interested listeners
+        fireContainerEvent("removeConstraint", constraint);
+
+    }
+
+
+    /**
+     * Remove the error page for the specified error code or
+     * Java language exception, if it exists; otherwise, no action is taken.
+     *
+     * @param errorPage The error page definition to be removed
+     */
+    @Override
+    public void removeErrorPage(ErrorPage errorPage) {
+
+        String exceptionType = errorPage.getExceptionType();
+        if (exceptionType != null) {
+            synchronized (exceptionPages) {
+                exceptionPages.remove(exceptionType);
+            }
+        } else {
+            synchronized (statusPages) {
+                if (errorPage.getErrorCode() == 200) {
+                    this.okErrorPage = null;
+                }
+                statusPages.remove(Integer.valueOf(errorPage.getErrorCode()));
+            }
+        }
+        fireContainerEvent("removeErrorPage", errorPage);
+
+    }
+
+
+    /**
+     * Remove the specified filter definition from this Context, if it exists;
+     * otherwise, no action is taken.
+     *
+     * @param filterDef Filter definition to be removed
+     */
+    @Override
+    public void removeFilterDef(FilterDef filterDef) {
+
+        synchronized (filterDefs) {
+            filterDefs.remove(filterDef.getFilterName());
+        }
+        fireContainerEvent("removeFilterDef", filterDef);
+
+    }
+
+
+    /**
+     * Remove a filter mapping from this Context.
+     *
+     * @param filterMap The filter mapping to be removed
+     */
+    @Override
+    public void removeFilterMap(FilterMap filterMap) {
+        filterMaps.remove(filterMap);
+        // Inform interested listeners
+        fireContainerEvent("removeFilterMap", filterMap);
+    }
+
+
+    /**
+     * Remove a class name from the set of InstanceListener classes that
+     * will be added to newly created Wrappers.
+     *
+     * @param listener Class name of an InstanceListener class to be removed
+     */
+    @Override
+    public void removeInstanceListener(String listener) {
+
+        synchronized (instanceListenersLock) {
+
+            // Make sure this welcome file is currently present
+            int n = -1;
+            for (int i = 0; i < instanceListeners.length; i++) {
+                if (instanceListeners[i].equals(listener)) {
+                    n = i;
+                    break;
+                }
+            }
+            if (n < 0)
+                return;
+
+            // Remove the specified constraint
+            int j = 0;
+            String results[] = new String[instanceListeners.length - 1];
+            for (int i = 0; i < instanceListeners.length; i++) {
+                if (i != n)
+                    results[j++] = instanceListeners[i];
+            }
+            instanceListeners = results;
+
+        }
+
+        // Inform interested listeners
+        fireContainerEvent("removeInstanceListener", listener);
+
+    }
+
+
+    /**
+     * Remove any message destination with the specified name.
+     *
+     * @param name Name of the message destination to remove
+     */
+    public void removeMessageDestination(String name) {
+
+        synchronized (messageDestinations) {
+            messageDestinations.remove(name);
+        }
+        fireContainerEvent("removeMessageDestination", name);
+
+    }
+
+
+    /**
+     * Remove any message destination ref with the specified name.
+     *
+     * @param name Name of the message destination ref to remove
+     */
+    public void removeMessageDestinationRef(String name) {
+
+        namingResources.removeMessageDestinationRef(name);
+        fireContainerEvent("removeMessageDestinationRef", name);
+
+    }
+
+
+    /**
+     * Remove the MIME mapping for the specified extension, if it exists;
+     * otherwise, no action is taken.
+     *
+     * @param extension Extension to remove the mapping for
+     */
+    @Override
+    public void removeMimeMapping(String extension) {
+
+        synchronized (mimeMappings) {
+            mimeMappings.remove(extension);
+        }
+        fireContainerEvent("removeMimeMapping", extension);
+
+    }
+
+
+    /**
+     * Remove the context initialization parameter with the specified
+     * name, if it exists; otherwise, no action is taken.
+     *
+     * @param name Name of the parameter to remove
+     */
+    @Override
+    public void removeParameter(String name) {
+
+        synchronized (parameters) {
+            parameters.remove(name);
+        }
+        fireContainerEvent("removeParameter", name);
+
+    }
+
+
+    /**
+     * Remove any security role reference for the specified name
+     *
+     * @param role Security role (as used in the application) to remove
+     */
+    @Override
+    public void removeRoleMapping(String role) {
+
+        synchronized (roleMappings) {
+            roleMappings.remove(role);
+        }
+        fireContainerEvent("removeRoleMapping", role);
+
+    }
+
+
+    /**
+     * Remove any security role with the specified name.
+     *
+     * @param role Security role to remove
+     */
+    @Override
+    public void removeSecurityRole(String role) {
+
+        synchronized (securityRolesLock) {
+
+            // Make sure this security role is currently present
+            int n = -1;
+            for (int i = 0; i < securityRoles.length; i++) {
+                if (role.equals(securityRoles[i])) {
+                    n = i;
+                    break;
+                }
+            }
+            if (n < 0)
+                return;
+
+            // Remove the specified security role
+            int j = 0;
+            String results[] = new String[securityRoles.length - 1];
+            for (int i = 0; i < securityRoles.length; i++) {
+                if (i != n)
+                    results[j++] = securityRoles[i];
+            }
+            securityRoles = results;
+
+        }
+
+        // Inform interested listeners
+        fireContainerEvent("removeSecurityRole", role);
+
+    }
+
+
+    /**
+     * Remove any servlet mapping for the specified pattern, if it exists;
+     * otherwise, no action is taken.
+     *
+     * @param pattern URL pattern of the mapping to remove
+     */
+    @Override
+    public void removeServletMapping(String pattern) {
+
+        String name = null;
+        synchronized (servletMappingsLock) {
+            name = servletMappings.remove(pattern);
+        }
+        Wrapper wrapper = (Wrapper) findChild(name);
+        if( wrapper != null ) {
+            wrapper.removeMapping(pattern);
+        }
+        mapper.removeWrapper(pattern);
+        fireContainerEvent("removeServletMapping", pattern);
+
+    }
+
+
+    /**
+     * Remove the specified watched resource name from the list associated
+     * with this Context.
+     * 
+     * @param name Name of the watched resource to be removed
+     */
+    @Override
+    public void removeWatchedResource(String name) {
+        
+        synchronized (watchedResourcesLock) {
+
+            // Make sure this watched resource is currently present
+            int n = -1;
+            for (int i = 0; i < watchedResources.length; i++) {
+                if (watchedResources[i].equals(name)) {
+                    n = i;
+                    break;
+                }
+            }
+            if (n < 0)
+                return;
+
+            // Remove the specified watched resource
+            int j = 0;
+            String results[] = new String[watchedResources.length - 1];
+            for (int i = 0; i < watchedResources.length; i++) {
+                if (i != n)
+                    results[j++] = watchedResources[i];
+            }
+            watchedResources = results;
+
+        }
+
+        fireContainerEvent("removeWatchedResource", name);
+
+    }
+    
+    
+    /**
+     * Remove the specified welcome file name from the list recognized
+     * by this Context.
+     *
+     * @param name Name of the welcome file to be removed
+     */
+    @Override
+    public void removeWelcomeFile(String name) {
+
+        synchronized (welcomeFilesLock) {
+
+            // Make sure this welcome file is currently present
+            int n = -1;
+            for (int i = 0; i < welcomeFiles.length; i++) {
+                if (welcomeFiles[i].equals(name)) {
+                    n = i;
+                    break;
+                }
+            }
+            if (n < 0)
+                return;
+
+            // Remove the specified constraint
+            int j = 0;
+            String results[] = new String[welcomeFiles.length - 1];
+            for (int i = 0; i < welcomeFiles.length; i++) {
+                if (i != n)
+                    results[j++] = welcomeFiles[i];
+            }
+            welcomeFiles = results;
+
+        }
+
+        // Inform interested listeners
+        if(this.getState().equals(LifecycleState.STARTED))
+            fireContainerEvent(REMOVE_WELCOME_FILE_EVENT, name);
+
+    }
+
+
+    /**
+     * Remove a class name from the set of LifecycleListener classes that
+     * will be added to newly created Wrappers.
+     *
+     * @param listener Class name of a LifecycleListener class to be removed
+     */
+    @Override
+    public void removeWrapperLifecycle(String listener) {
+
+
+        synchronized (wrapperLifecyclesLock) {
+
+            // Make sure this welcome file is currently present
+            int n = -1;
+            for (int i = 0; i < wrapperLifecycles.length; i++) {
+                if (wrapperLifecycles[i].equals(listener)) {
+                    n = i;
+                    break;
+                }
+            }
+            if (n < 0)
+                return;
+
+            // Remove the specified constraint
+            int j = 0;
+            String results[] = new String[wrapperLifecycles.length - 1];
+            for (int i = 0; i < wrapperLifecycles.length; i++) {
+                if (i != n)
+                    results[j++] = wrapperLifecycles[i];
+            }
+            wrapperLifecycles = results;
+
+        }
+
+        // Inform interested listeners
+        fireContainerEvent("removeWrapperLifecycle", listener);
+
+    }
+
+
+    /**
+     * Remove a class name from the set of ContainerListener classes that
+     * will be added to newly created Wrappers.
+     *
+     * @param listener Class name of a ContainerListener class to be removed
+     */
+    @Override
+    public void removeWrapperListener(String listener) {
+
+
+        synchronized (wrapperListenersLock) {
+
+            // Make sure this welcome file is currently present
+            int n = -1;
+            for (int i = 0; i < wrapperListeners.length; i++) {
+                if (wrapperListeners[i].equals(listener)) {
+                    n = i;
+                    break;
+                }
+            }
+            if (n < 0)
+                return;
+
+            // Remove the specified constraint
+            int j = 0;
+            String results[] = new String[wrapperListeners.length - 1];
+            for (int i = 0; i < wrapperListeners.length; i++) {
+                if (i != n)
+                    results[j++] = wrapperListeners[i];
+            }
+            wrapperListeners = results;
+
+        }
+
+        // Inform interested listeners
+        fireContainerEvent("removeWrapperListener", listener);
+
+    }
+
+
+    /**
+     * Gets the cumulative processing times of all servlets in this
+     * StandardContext.
+     *
+     * @return Cumulative processing times of all servlets in this
+     * StandardContext
+     */
+    public long getProcessingTime() {
+        
+        long result = 0;
+
+        Container[] children = findChildren();
+        if (children != null) {
+            for( int i=0; i< children.length; i++ ) {
+                result += ((StandardWrapper)children[i]).getProcessingTime();
+            }
+        }
+
+        return result;
+    }
+
+
+    /**
+     * 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
+    public String getRealPath(String path) {
+        if (webappResources instanceof BaseDirContext) {
+            return ((BaseDirContext) webappResources).getRealPath(path);
+        }
+        return null;
+    }
+
+    /**
+     * hook to register that we need to scan for security annotations.
+     * @param wrapper   The wrapper for the Servlet that was added
+     */
+    public ServletRegistration.Dynamic dynamicServletAdded(Wrapper wrapper) {
+        Servlet s = wrapper.getServlet();
+        if (s != null && createdServlets.contains(s)) {
+            // Mark the wrapper to indicate annotations need to be scanned
+            wrapper.setServletSecurityAnnotationScanRequired(true);
+        }
+        return new ApplicationServletRegistration(wrapper, this);
+    }
+
+    /**
+     * hook to track which registrations need annotation scanning
+     * @param servlet
+     */
+    public void dynamicServletCreated(Servlet servlet) {
+        createdServlets.add(servlet);
+    }
+
+
+    /**
+     * A helper class to manage the filter mappings in a Context.
+     */
+    private static final class ContextFilterMaps {
+        private final Object lock = new Object();
+
+        /**
+         * The set of filter mappings for this application, in the order they
+         * were defined in the deployment descriptor with additional mappings
+         * added via the {@link ServletContext} possibly both before and after
+         * those defined in the deployment descriptor.
+         */
+        private FilterMap[] array = new FilterMap[0];
+
+        /**
+         * Filter mappings added via {@link ServletContext} may have to be
+         * inserted before the mappings in the deployment descriptor but must be
+         * inserted in the order the {@link ServletContext} methods are called.
+         * This isn't an issue for the mappings added after the deployment
+         * descriptor - they are just added to the end - but correctly the
+         * adding mappings before the deployment descriptor mappings requires
+         * knowing where the last 'before' mapping was added.
+         */
+        private int insertPoint = 0;
+
+        /**
+         * Return the set of filter mappings.
+         */
+        public FilterMap[] asArray() {
+            synchronized (lock) {
+                return array;
+            }
+        }
+
+        /**
+         * Add a filter mapping at the end of the current set of filter
+         * mappings.
+         * 
+         * @param filterMap
+         *            The filter mapping to be added
+         */
+        public void add(FilterMap filterMap) {
+            synchronized (lock) {
+                FilterMap results[] = Arrays.copyOf(array, array.length + 1);
+                results[array.length] = filterMap;
+                array = results;
+            }
+        }
+
+        /**
+         * Add a filter mapping before the mappings defined in the deployment
+         * descriptor but after any other mappings added via this method.
+         * 
+         * @param filterMap
+         *            The filter mapping to be added
+         */
+        public void addBefore(FilterMap filterMap) {
+            synchronized (lock) {
+                FilterMap results[] = new FilterMap[array.length + 1];
+                System.arraycopy(array, 0, results, 0, insertPoint);
+                System.arraycopy(array, insertPoint, results, insertPoint + 1,
+                        array.length - insertPoint);
+                results[insertPoint] = filterMap;
+                array = results;
+                insertPoint++;
+            }
+        }
+
+        /**
+         * Remove a filter mapping.
+         *
+         * @param filterMap The filter mapping to be removed
+         */
+        public void remove(FilterMap filterMap) {
+            synchronized (lock) {
+                // Make sure this filter mapping is currently present
+                int n = -1;
+                for (int i = 0; i < array.length; i++) {
+                    if (array[i] == filterMap) {
+                        n = i;
+                        break;
+                    }
+                }
+                if (n < 0)
+                    return;
+
+                // Remove the specified filter mapping
+                FilterMap results[] = new FilterMap[array.length - 1];
+                System.arraycopy(array, 0, results, 0, n);
+                System.arraycopy(array, n + 1, results, n, (array.length - 1)
+                        - n);
+                array = results;
+                if (n < insertPoint) {
+                    insertPoint--;
+                }
+            }
+        }
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Configure and initialize the set of filters for this Context.
+     * Return <code>true</code> if all filter initialization completed
+     * successfully, or <code>false</code> otherwise.
+     */
+    public boolean filterStart() {
+
+        if (getLogger().isDebugEnabled())
+            getLogger().debug("Starting filters");
+        // Instantiate and record a FilterConfig for each defined filter
+        boolean ok = true;
+        synchronized (filterConfigs) {
+            filterConfigs.clear();
+            Iterator<String> names = filterDefs.keySet().iterator();
+            while (names.hasNext()) {
+                String name = names.next();
+                if (getLogger().isDebugEnabled())
+                    getLogger().debug(" Starting filter '" + name + "'");
+                ApplicationFilterConfig filterConfig = null;
+                try {
+                    filterConfig =
+                        new ApplicationFilterConfig(this, filterDefs.get(name));
+                    filterConfigs.put(name, filterConfig);
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    getLogger().error
+                        (sm.getString("standardContext.filterStart", name), t);
+                    ok = false;
+                }
+            }
+        }
+
+        return (ok);
+
+    }
+
+
+    /**
+     * Finalize and release the set of filters for this Context.
+     * Return <code>true</code> if all filter finalization completed
+     * successfully, or <code>false</code> otherwise.
+     */
+    public boolean filterStop() {
+
+        if (getLogger().isDebugEnabled())
+            getLogger().debug("Stopping filters");
+
+        // Release all Filter and FilterConfig instances
+        synchronized (filterConfigs) {
+            Iterator<String> names = filterConfigs.keySet().iterator();
+            while (names.hasNext()) {
+                String name = names.next();
+                if (getLogger().isDebugEnabled())
+                    getLogger().debug(" Stopping filter '" + name + "'");
+                ApplicationFilterConfig filterConfig = filterConfigs.get(name);
+                filterConfig.release();
+            }
+            filterConfigs.clear();
+        }
+        return (true);
+
+    }
+
+
+    /**
+     * Find and return the initialized <code>FilterConfig</code> for the
+     * specified filter name, if any; otherwise return <code>null</code>.
+     *
+     * @param name Name of the desired filter
+     */
+    public FilterConfig findFilterConfig(String name) {
+
+        return (filterConfigs.get(name));
+
+    }
+
+
+    /**
+     * Configure the set of instantiated application event listeners
+     * for this Context.  Return <code>true</code> if all listeners wre
+     * initialized successfully, or <code>false</code> otherwise.
+     */
+    public boolean listenerStart() {
+
+        if (log.isDebugEnabled())
+            log.debug("Configuring application event listeners");
+
+        // Instantiate the required listeners
+        String listeners[] = findApplicationListeners();
+        Object results[] = new Object[listeners.length];
+        boolean ok = true;
+        for (int i = 0; i < results.length; i++) {
+            if (getLogger().isDebugEnabled())
+                getLogger().debug(" Configuring event listener class '" +
+                    listeners[i] + "'");
+            try {
+                results[i] = instanceManager.newInstance(listeners[i]);
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                getLogger().error
+                    (sm.getString("standardContext.applicationListener",
+                                  listeners[i]), t);
+                ok = false;
+            }
+        }
+        if (!ok) {
+            getLogger().error(sm.getString("standardContext.applicationSkipped"));
+            return (false);
+        }
+
+        // Sort listeners in two arrays
+        ArrayList<Object> eventListeners = new ArrayList<Object>();
+        ArrayList<Object> lifecycleListeners = new ArrayList<Object>();
+        for (int i = 0; i < results.length; i++) {
+            if ((results[i] instanceof ServletContextAttributeListener)
+                || (results[i] instanceof ServletRequestAttributeListener)
+                || (results[i] instanceof ServletRequestListener)
+                || (results[i] instanceof HttpSessionAttributeListener)) {
+                eventListeners.add(results[i]);
+            }
+            if ((results[i] instanceof ServletContextListener)
+                || (results[i] instanceof HttpSessionListener)) {
+                lifecycleListeners.add(results[i]);
+            }
+        }
+
+        //Listeners may have been added by ServletContextInitializers.  Put them after the ones we know about.
+        for (Object eventListener: getApplicationEventListeners()) {
+            eventListeners.add(eventListener);
+        }
+        setApplicationEventListeners(eventListeners.toArray());
+        for (Object lifecycleListener: getApplicationLifecycleListeners()) {
+            lifecycleListeners.add(lifecycleListener);
+        }
+        setApplicationLifecycleListeners(lifecycleListeners.toArray());
+
+        // Send application start events
+
+        if (getLogger().isDebugEnabled())
+            getLogger().debug("Sending application start events");
+
+        // Ensure context is not null
+        getServletContext();
+        context.setNewServletContextListenerAllowed(false);
+        
+        Object instances[] = getApplicationLifecycleListeners();
+        if (instances == null)
+            return (ok);
+        ServletContextEvent event =
+          new ServletContextEvent(getServletContext());
+        for (int i = 0; i < instances.length; i++) {
+            if (instances[i] == null)
+                continue;
+            if (!(instances[i] instanceof ServletContextListener))
+                continue;
+            ServletContextListener listener =
+                (ServletContextListener) instances[i];
+            try {
+                fireContainerEvent("beforeContextInitialized", listener);
+                listener.contextInitialized(event);
+                fireContainerEvent("afterContextInitialized", listener);
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                fireContainerEvent("afterContextInitialized", listener);
+                getLogger().error
+                    (sm.getString("standardContext.listenerStart",
+                                  instances[i].getClass().getName()), t);
+                ok = false;
+            }
+        }
+        return (ok);
+
+    }
+
+
+    /**
+     * Send an application stop event to all interested listeners.
+     * Return <code>true</code> if all events were sent successfully,
+     * or <code>false</code> otherwise.
+     */
+    public boolean listenerStop() {
+
+        if (log.isDebugEnabled())
+            log.debug("Sending application stop events");
+
+        boolean ok = true;
+        Object listeners[] = getApplicationLifecycleListeners();
+        if (listeners != null) {
+            ServletContextEvent event =
+                new ServletContextEvent(getServletContext());
+            for (int i = 0; i < listeners.length; i++) {
+                int j = (listeners.length - 1) - i;
+                if (listeners[j] == null)
+                    continue;
+                if (listeners[j] instanceof ServletContextListener) {
+                    ServletContextListener listener =
+                        (ServletContextListener) listeners[j];
+                    try {
+                        fireContainerEvent("beforeContextDestroyed", listener);
+                        listener.contextDestroyed(event);
+                        fireContainerEvent("afterContextDestroyed", listener);
+                    } catch (Throwable t) {
+                        ExceptionUtils.handleThrowable(t);
+                        fireContainerEvent("afterContextDestroyed", listener);
+                        getLogger().error
+                            (sm.getString("standardContext.listenerStop",
+                                listeners[j].getClass().getName()), t);
+                        ok = false;
+                    }
+                }
+                try {
+                    getInstanceManager().destroyInstance(listeners[j]);
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    getLogger().error
+                       (sm.getString("standardContext.listenerStop",
+                            listeners[j].getClass().getName()), t);
+                    ok = false;
+                }
+            }
+        }
+
+        // Annotation processing
+        listeners = getApplicationEventListeners();
+        if (listeners != null) {
+            for (int i = 0; i < listeners.length; i++) {
+                int j = (listeners.length - 1) - i;
+                if (listeners[j] == null)
+                    continue;
+                try {
+                    getInstanceManager().destroyInstance(listeners[j]);
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    getLogger().error
+                        (sm.getString("standardContext.listenerStop",
+                            listeners[j].getClass().getName()), t);
+                    ok = false;
+                }
+            }
+        }
+        
+        setApplicationEventListeners(null);
+        setApplicationLifecycleListeners(null);
+
+        return (ok);
+
+    }
+
+
+    /**
+     * Allocate resources, including proxy.
+     * Return <code>true</code> if initialization was successfull,
+     * or <code>false</code> otherwise.
+     */
+    public boolean resourcesStart() {
+
+        boolean ok = true;
+
+        Hashtable<String, String> env = new Hashtable<String, String>();
+        if (getParent() != null)
+            env.put(ProxyDirContext.HOST, getParent().getName());
+        env.put(ProxyDirContext.CONTEXT, getName());
+
+        try {
+            ProxyDirContext proxyDirContext =
+                new ProxyDirContext(env, webappResources);
+            if (webappResources instanceof FileDirContext) {
+                filesystemBased = true;
+                ((FileDirContext) webappResources).setAllowLinking
+                    (isAllowLinking());
+            }
+            if (webappResources instanceof BaseDirContext) {
+                ((BaseDirContext) webappResources).setDocBase(getBasePath());
+                ((BaseDirContext) webappResources).setCached
+                    (isCachingAllowed());
+                ((BaseDirContext) webappResources).setCacheTTL(getCacheTTL());
+                ((BaseDirContext) webappResources).setCacheMaxSize
+                    (getCacheMaxSize());
+                ((BaseDirContext) webappResources).allocate();
+                // Alias support
+                ((BaseDirContext) webappResources).setAliases(getAliases());
+                
+                if (effectiveMajorVersion >=3 && addWebinfClassesResources) {
+                    try {
+                        DirContext webInfCtx =
+                            (DirContext) webappResources.lookup(
+                                    "/WEB-INF/classes");
+                        // Do the lookup to make sure it exists
+                        webInfCtx.lookup("META-INF/resources");
+                        ((BaseDirContext) webappResources).addAltDirContext(
+                                webInfCtx);
+                    } catch (NamingException e) {
+                        // Doesn't exist - ignore and carry on
+                    }
+                }
+            }
+            // Register the cache in JMX
+            if (isCachingAllowed()) {
+                String contextName = getName();
+                if (!contextName.startsWith("/")) {
+                    contextName = "/" + contextName;
+                }
+                ObjectName resourcesName = 
+                    new ObjectName(this.getDomain() + ":type=Cache,host=" 
+                                   + getHostname() + ",context=" + contextName);
+                Registry.getRegistry(null, null).registerComponent
+                    (proxyDirContext.getCache(), resourcesName, null);
+            }
+            this.resources = proxyDirContext;
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.error(sm.getString("standardContext.resourcesStart"), t);
+            ok = false;
+        }
+
+        return (ok);
+
+    }
+
+
+    /**
+     * Deallocate resources and destroy proxy.
+     */
+    public boolean resourcesStop() {
+
+        boolean ok = true;
+
+        try {
+            if (resources != null) {
+                if (resources instanceof Lifecycle) {
+                    ((Lifecycle) resources).stop();
+                }
+                if (webappResources instanceof BaseDirContext) {
+                    ((BaseDirContext) webappResources).release();
+                }
+                // Unregister the cache in JMX
+                if (isCachingAllowed()) {
+                    String contextName = getName();
+                    if (!contextName.startsWith("/")) {
+                        contextName = "/" + contextName;
+                    }
+                    ObjectName resourcesName = 
+                        new ObjectName(this.getDomain()
+                                       + ":type=Cache,host=" 
+                                       + getHostname() + ",context=" 
+                                       + contextName);
+                    Registry.getRegistry(null, null)
+                        .unregisterComponent(resourcesName);
+                }
+            }
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.error(sm.getString("standardContext.resourcesStop"), t);
+            ok = false;
+        }
+
+        this.resources = null;
+
+        return (ok);
+
+    }
+
+
+    /**
+     * Load and initialize all servlets marked "load on startup" in the
+     * web application deployment descriptor.
+     *
+     * @param children Array of wrappers for all currently defined
+     *  servlets (including those not declared load on startup)
+     */
+    public void loadOnStartup(Container children[]) {
+
+        // Collect "load on startup" servlets that need to be initialized
+        TreeMap<Integer, ArrayList<Wrapper>> map =
+            new TreeMap<Integer, ArrayList<Wrapper>>();
+        for (int i = 0; i < children.length; i++) {
+            Wrapper wrapper = (Wrapper) children[i];
+            int loadOnStartup = wrapper.getLoadOnStartup();
+            if (loadOnStartup < 0)
+                continue;
+            Integer key = Integer.valueOf(loadOnStartup);
+            ArrayList<Wrapper> list = map.get(key);
+            if (list == null) {
+                list = new ArrayList<Wrapper>();
+                map.put(key, list);
+            }
+            list.add(wrapper);
+        }
+
+        // Load the collected "load on startup" servlets
+        for (ArrayList<Wrapper> list : map.values()) {
+            for (Wrapper wrapper : list) {
+                try {
+                    wrapper.load();
+                } catch (ServletException e) {
+                    getLogger().error(sm.getString("standardWrapper.loadException",
+                                      getName()), StandardWrapper.getRootCause(e));
+                    // NOTE: load errors (including a servlet that throws
+                    // UnavailableException from tht init() method) are NOT
+                    // fatal to application startup
+                }
+            }
+        }
+
+    }
+
+
+    /**
+     * 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 {
+
+        if(log.isDebugEnabled())
+            log.debug("Starting " + getBaseName());
+
+        // Send j2ee.state.starting notification 
+        if (this.getObjectName() != null) {
+            Notification notification = new Notification("j2ee.state.starting",
+                    this.getObjectName(), sequenceNumber.getAndIncrement());
+            broadcaster.sendNotification(notification);
+        }
+
+        setConfigured(false);
+        boolean ok = true;
+
+        // Currently this is effectively a NO-OP but needs to be called to
+        // ensure the NamingResources follows the correct lifecycle
+        if (namingResources != null) {
+            namingResources.start();
+        }
+        
+        // Add missing components as necessary
+        if (webappResources == null) {   // (1) Required by Loader
+            if (log.isDebugEnabled())
+                log.debug("Configuring default Resources");
+            try {
+                if ((getDocBase() != null) && (getDocBase().endsWith(".war")) &&
+                        (!(new File(getBasePath())).isDirectory()))
+                    setResources(new WARDirContext());
+                else
+                    setResources(new FileDirContext());
+            } catch (IllegalArgumentException e) {
+                log.error("Error initializing resources: " + e.getMessage());
+                ok = false;
+            }
+        }
+        if (ok) {
+            if (!resourcesStart()) {
+                log.error( "Error in resourceStart()");
+                ok = false;
+            }
+        }
+
+        if (getLoader() == null) {
+            WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
+            webappLoader.setDelegate(getDelegate());
+            setLoader(webappLoader);
+        }
+
+        // Initialize character set mapper
+        getCharsetMapper();
+
+        // Post work directory
+        postWorkDirectory();
+
+        // Validate required extensions
+        boolean dependencyCheck = true;
+        try {
+            dependencyCheck = ExtensionValidator.validateApplication
+                (getResources(), this);
+        } catch (IOException ioe) {
+            log.error("Error in dependencyCheck", ioe);
+            dependencyCheck = false;
+        }
+
+        if (!dependencyCheck) {
+            // do not make application available if depency check fails
+            ok = false;
+        }
+
+        // Reading the "catalina.useNaming" environment variable
+        String useNamingProperty = System.getProperty("catalina.useNaming");
+        if ((useNamingProperty != null)
+            && (useNamingProperty.equals("false"))) {
+            useNaming = false;
+        }
+
+        if (ok && isUseNaming()) {
+            if (getNamingContextListener() == null) {
+                NamingContextListener ncl = new NamingContextListener();
+                ncl.setName(getNamingContextName());
+                addLifecycleListener(ncl);
+                setNamingContextListener(ncl);
+            }
+        }
+        
+        // Standard container startup
+        if (log.isDebugEnabled())
+            log.debug("Processing standard container startup");
+
+        
+        // Binding thread
+        ClassLoader oldCCL = bindThread();
+
+        try {
+
+            if (ok) {
+                
+                // Start our subordinate components, if any
+                if ((loader != null) && (loader instanceof Lifecycle))
+                    ((Lifecycle) loader).start();
+
+                // since the loader just started, the webapp classloader is now
+                // created.
+                // By calling unbindThread and bindThread in a row, we setup the
+                // current Thread CCL to be the webapp classloader
+                unbindThread(oldCCL);
+                oldCCL = bindThread();
+
+                // Initialize logger again. Other components might have used it too early, 
+                // so it should be reset.
+                logger = null;
+                getLogger();
+                if ((logger != null) && (logger instanceof Lifecycle))
+                    ((Lifecycle) logger).start();
+                
+                if ((cluster != null) && (cluster instanceof Lifecycle))
+                    ((Lifecycle) cluster).start();
+                if ((realm != null) && (realm instanceof Lifecycle))
+                    ((Lifecycle) realm).start();
+                if ((resources != null) && (resources instanceof Lifecycle))
+                    ((Lifecycle) resources).start();
+
+                // Notify our interested LifecycleListeners
+                fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
+                
+                // Start our child containers, if not already started
+                for (Container child : findChildren()) {
+                    if (!child.getState().isAvailable()) {
+                        child.start();
+                    }
+                }
+
+                // Start the Valves in our pipeline (including the basic),
+                // if any
+                if (pipeline instanceof Lifecycle) {
+                    ((Lifecycle) pipeline).start();
+                }
+                
+                // Acquire clustered manager
+                Manager contextManager = null;
+                if (manager == null) {
+                    if (log.isDebugEnabled()) {
+                        log.debug(sm.getString("standardContext.cluster.noManager",
+                                Boolean.valueOf((getCluster() != null)),
+                                Boolean.valueOf(distributable)));
+                    }
+                    if ( (getCluster() != null) && distributable) {
+                        try {
+                            contextManager = getCluster().createManager(getName());
+                        } catch (Exception ex) {
+                            log.error("standardContext.clusterFail", ex);
+                            ok = false;
+                        }
+                    } else {
+                        contextManager = new StandardManager();
+                    }
+                } 
+                
+                // Configure default manager if none was specified
+                if (contextManager != null) {
+                    if (log.isDebugEnabled()) {
+                        log.debug(sm.getString("standardContext.manager",
+                                contextManager.getClass().getName()));
+                    }
+                    setManager(contextManager);
+                }
+
+                if (manager!=null && (getCluster() != null) && distributable) {
+                    //let the cluster know that there is a context that is distributable
+                    //and that it has its own manager
+                    getCluster().registerManager(manager);
+                }
+            }
+
+        } finally {
+            // Unbinding thread
+            unbindThread(oldCCL);
+        }
+
+        if (!getConfigured()) {
+            log.error( "Error getConfigured");
+            ok = false;
+        }
+
+        // We put the resources into the servlet context
+        if (ok)
+            getServletContext().setAttribute
+                (Globals.RESOURCES_ATTR, getResources());
+
+        // Initialize associated mapper
+        mapper.setContext(getPath(), welcomeFiles, resources);
+
+        // Binding thread
+        oldCCL = bindThread();
+
+        if (ok ) {
+            if (getInstanceManager() == null) {
+                javax.naming.Context context = null;
+                if (isUseNaming() && getNamingContextListener() != null) {
+                    context = getNamingContextListener().getEnvContext();
+                }
+                Map<String, Map<String, String>> injectionMap = buildInjectionMap(
+                        getIgnoreAnnotations() ? new NamingResources(): getNamingResources());
+                setInstanceManager(new DefaultInstanceManager(context,
+                        injectionMap, this, this.getClass().getClassLoader()));
+                getServletContext().setAttribute(
+                        InstanceManager.class.getName(), getInstanceManager());
+            }
+        }
+
+        DedicatedThreadExecutor temporaryExecutor = new DedicatedThreadExecutor();
+        try {
+            
+            // Create context attributes that will be required
+            if (ok) {
+                getServletContext().setAttribute(
+                        JarScanner.class.getName(), getJarScanner());
+            }
+
+            // Set up the context init params
+            mergeParameters();
+
+            // Call ServletContainerInitializers
+            for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
+                initializers.entrySet()) {
+                try {
+                    entry.getKey().onStartup(entry.getValue(),
+                            getServletContext());
+                } catch (ServletException e) {
+                    // TODO: Log error
+                    ok = false;
+                    break;
+                }
+            }
+
+            // Configure and call application event listeners
+            if (ok) {
+                // we do it in a dedicated thread for memory leak protection, in
+                // case the Listeners registers some ThreadLocals that they
+                // forget to cleanup
+                Boolean listenerStarted =
+                    temporaryExecutor.execute(new Callable<Boolean>() {
+                        @Override
+                        public Boolean call() throws Exception {
+                            ClassLoader old = bindThread();
+                            try {
+                                return Boolean.valueOf(listenerStart());
+                            } finally {
+                                unbindThread(old);
+                            }
+                        }
+                    });
+                if (!listenerStarted.booleanValue()) {
+                    log.error( "Error listenerStart");
+                    ok = false;
+                }
+            }
+            
+            try {
+                // Start manager
+                if ((manager != null) && (manager instanceof Lifecycle)) {
+                    ((Lifecycle) getManager()).start();
+                }
+    
+                // Start ContainerBackgroundProcessor thread
+                super.threadStart();
+            } catch(Exception e) {
+                log.error("Error manager.start()", e);
+                ok = false;
+            }
+
+            // Configure and call application filters
+            if (ok) {
+                // we do it in a dedicated thread for memory leak protection, in
+                // case the Filters register some ThreadLocals that they forget
+                // to cleanup
+                Boolean filterStarted =
+                    temporaryExecutor.execute(new Callable<Boolean>() {
+                        @Override
+                        public Boolean call() throws Exception {
+                            ClassLoader old = bindThread();
+                            try {
+                                return Boolean.valueOf(filterStart());
+                            } finally {
+                                unbindThread(old);
+                            }
+                        }
+                    });
+                if (!filterStarted.booleanValue()) {
+                    log.error("Error filterStart");
+                    ok = false;
+                }
+            }
+            
+            // Load and initialize all "load on startup" servlets
+            if (ok) {
+                // we do it in a dedicated thread for memory leak protection, in
+                // case the Servlets register some ThreadLocals that they forget
+                // to cleanup
+                temporaryExecutor.execute(new Callable<Void>() {
+                    @Override
+                    public Void call() throws Exception {
+                        ClassLoader old = bindThread();
+                        try {
+                            loadOnStartup(findChildren());
+                            return null;
+                        } finally {
+                            unbindThread(old);
+                        }
+                    }
+                });
+            }
+            
+        } finally {
+            // Unbinding thread
+            unbindThread(oldCCL);
+            temporaryExecutor.shutdown();
+        }
+
+        // Set available status depending upon startup success
+        if (ok) {
+            if (log.isDebugEnabled())
+                log.debug("Starting completed");
+        } else {
+            log.error(sm.getString("standardContext.startFailed", getName()));
+        }
+
+        startTime=System.currentTimeMillis();
+        
+        // Send j2ee.state.running notification 
+        if (ok && (this.getObjectName() != null)) {
+            Notification notification = 
+                new Notification("j2ee.state.running", this.getObjectName(),
+                                 sequenceNumber.getAndIncrement());
+            broadcaster.sendNotification(notification);
+        }
+
+        // Close all JARs right away to avoid always opening a peak number 
+        // of files on startup
+        if (getLoader() instanceof WebappLoader) {
+            ((WebappLoader) getLoader()).closeJARs(true);
+        }
+
+        // Reinitializing if something went wrong
+        if (!ok) {
+            setState(LifecycleState.FAILED);
+        } else {
+            setState(LifecycleState.STARTING);
+        }
+    }
+
+    private Map<String, Map<String, String>> buildInjectionMap(NamingResources namingResources) {
+        Map<String, Map<String, String>> injectionMap = new HashMap<String, Map<String, String>>();
+        for (Injectable resource: namingResources.findLocalEjbs()) {
+            addInjectionTarget(resource, injectionMap);
+        }
+        for (Injectable resource: namingResources.findEjbs()) {
+            addInjectionTarget(resource, injectionMap);
+        }
+        for (Injectable resource: namingResources.findEnvironments()) {
+            addInjectionTarget(resource, injectionMap);
+        }
+        for (Injectable resource: namingResources.findMessageDestinationRefs()) {
+            addInjectionTarget(resource, injectionMap);
+        }
+        for (Injectable resource: namingResources.findResourceEnvRefs()) {
+            addInjectionTarget(resource, injectionMap);
+        }
+        for (Injectable resource: namingResources.findResources()) {
+            addInjectionTarget(resource, injectionMap);
+        }
+        for (Injectable resource: namingResources.findServices()) {
+            addInjectionTarget(resource, injectionMap);
+        }
+        return injectionMap;
+    }
+
+    private void addInjectionTarget(Injectable resource, Map<String, Map<String, String>> injectionMap) {
+        List<InjectionTarget> injectionTargets = resource.getInjectionTargets();
+        if (injectionTargets != null && injectionTargets.size() > 0) {
+            String jndiName = resource.getName();
+            for (InjectionTarget injectionTarget: injectionTargets) {
+                String clazz = injectionTarget.getTargetClass();
+                Map<String, String> injections = injectionMap.get(clazz);
+                if (injections == null) {
+                    injections = new HashMap<String, String>();
+                    injectionMap.put(clazz, injections);
+                }
+                injections.put(injectionTarget.getTargetName(), jndiName);
+            }
+        }
+    }
+
+    
+
+    /**
+     * Merge the context initialization parameters specified in the application
+     * deployment descriptor with the application parameters described in the
+     * server configuration, respecting the <code>override</code> property of
+     * the application parameters appropriately.
+     */
+    private void mergeParameters() {
+        Map<String,String> mergedParams = new HashMap<String,String>();
+        
+        String names[] = findParameters();
+        for (int i = 0; i < names.length; i++) {
+            mergedParams.put(names[i], findParameter(names[i]));
+        }
+
+        ApplicationParameter params[] = findApplicationParameters();
+        for (int i = 0; i < params.length; i++) {
+            if (params[i].getOverride()) {
+                if (mergedParams.get(params[i].getName()) == null) {
+                    mergedParams.put(params[i].getName(),
+                            params[i].getValue());
+                }
+            } else {
+                mergedParams.put(params[i].getName(), params[i].getValue());
+            }
+        }
+        
+        ServletContext sc = getServletContext();
+        for (Map.Entry<String,String> entry : mergedParams.entrySet()) {
+            sc.setInitParameter(entry.getKey(), entry.getValue());
+        }
+
+    }
+
+
+    /**
+     * 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 {
+
+        // Send j2ee.state.stopping notification 
+        if (this.getObjectName() != null) {
+            Notification notification = 
+                new Notification("j2ee.state.stopping", this.getObjectName(), 
+                                 sequenceNumber.getAndIncrement());
+            broadcaster.sendNotification(notification);
+        }
+        
+        setState(LifecycleState.STOPPING);
+
+        // Binding thread
+        ClassLoader oldCCL = bindThread();
+
+        try {
+
+            // Stop our child containers, if any
+            final Container[] children = findChildren();
+            // we do it in a dedicated thread for memory leak protection, in
+            // case some webapp code registers some ThreadLocals that they
+            // forget to cleanup
+            // TODO Figure out why DedicatedThreadExecutor hangs randomly in the
+            //      unit tests if used here
+            RunnableWithLifecycleException stop =
+                    new RunnableWithLifecycleException() {
+                @Override
+                public void run() {
+                    ClassLoader old = bindThread();
+                    try {
+                        for (int i = 0; i < children.length; i++) {
+                            try {
+                                children[i].stop();
+                            } catch (LifecycleException e) {
+                                le = e;
+                                return;
+                            }
+                        }
+            
+                        // Stop our filters
+                        filterStop();
+            
+                        // Stop ContainerBackgroundProcessor thread
+                        threadStop();
+            
+                        if (manager != null && manager instanceof Lifecycle) {
+                            try {
+                                ((Lifecycle) manager).stop();
+                            } catch (LifecycleException e) {
+                                le = e;
+                                return;
+                            }
+                        }
+            
+                        // Stop our application listeners
+                        listenerStop();
+                    }finally{
+                        unbindThread(old);
+                    }
+                }
+            };
+            
+            Thread t = new Thread(stop);
+            t.setName("stop children - " + getObjectName().toString());
+            t.run();
+            try {
+                t.join();
+            } catch (InterruptedException e) {
+                // Shouldn't happen
+                throw new LifecycleException(e);
+            }
+            if (stop.getLifecycleException() != null) {
+                throw stop.getLifecycleException();
+            }
+
+            // Finalize our character set mapper
+            setCharsetMapper(null);
+
+            // Normal container shutdown processing
+            if (log.isDebugEnabled())
+                log.debug("Processing standard container shutdown");
+
+            // JNDI resources are unbound in CONFIGURE_STOP_EVENT so stop
+            // naming resoucres before they are unbound since NamingResoucres
+            // does a JNDI lookup to retrieve the resource. This needs to be
+            // after the application has finished with the resource 
+            if (namingResources != null) {
+                namingResources.stop();
+            }
+            
+            fireLifecycleEvent(Lifecycle.CONFIGURE_STOP_EVENT, null);
+
+            // Stop the Valves in our pipeline (including the basic), if any
+            if (pipeline instanceof Lifecycle) {
+                ((Lifecycle) pipeline).stop();
+            }
+
+            // Clear all application-originated servlet context attributes
+            if (context != null)
+                context.clearAttributes();
+
+            // Stop resources
+            resourcesStop();
+
+            if ((realm != null) && (realm instanceof Lifecycle)) {
+                ((Lifecycle) realm).stop();
+            }
+            if ((cluster != null) && (cluster instanceof Lifecycle)) {
+                ((Lifecycle) cluster).stop();
+            }
+            if ((logger != null) && (logger instanceof Lifecycle)) {
+                ((Lifecycle) logger).stop();
+            }
+            if ((loader != null) && (loader instanceof Lifecycle)) {
+                ((Lifecycle) loader).stop();
+            }
+
+        } finally {
+
+            // Unbinding thread
+            unbindThread(oldCCL);
+
+        }
+
+        // Send j2ee.state.stopped notification 
+        if (this.getObjectName() != null) {
+            Notification notification = 
+                new Notification("j2ee.state.stopped", this.getObjectName(), 
+                                sequenceNumber.getAndIncrement());
+            broadcaster.sendNotification(notification);
+        }
+        
+        // Reset application context
+        context = null;
+
+        // This object will no longer be visible or used. 
+        try {
+            resetContext();
+        } catch( Exception ex ) {
+            log.error( "Error reseting context " + this + " " + ex, ex );
+        }
+        
+        //reset the instance manager
+        instanceManager = null;
+
+        if (log.isDebugEnabled())
+            log.debug("Stopping complete");
+
+    }
+
+    /** Destroy needs to clean up the context completely.
+     * 
+     * The problem is that undoing all the config in start() and restoring 
+     * a 'fresh' state is impossible. After stop()/destroy()/init()/start()
+     * we should have the same state as if a fresh start was done - i.e
+     * read modified web.xml, etc. This can only be done by completely 
+     * removing the context object and remapping a new one, or by cleaning
+     * up everything.
+     * 
+     * XXX Should this be done in stop() ?
+     * 
+     */ 
+    @Override
+    protected void destroyInternal() throws LifecycleException {
+        
+        if ((manager != null) && (manager instanceof Lifecycle)) {
+            ((Lifecycle) manager).destroy();
+        }
+        if ((realm != null) && (realm instanceof Lifecycle)) {
+            ((Lifecycle) realm).destroy();
+        }
+        if ((cluster != null) && (cluster instanceof Lifecycle)) {
+            ((Lifecycle) cluster).destroy();
+        }
+        if ((logger != null) && (logger instanceof Lifecycle)) {
+            ((Lifecycle) logger).destroy();
+        }
+        if ((loader != null) && (loader instanceof Lifecycle)) {
+            ((Lifecycle) loader).destroy();
+        }
+
+        // If in state NEW when destroy is called, the object name will never
+        // have been set so the notification can't be created
+        if (getObjectName() != null) { 
+            // Send j2ee.object.deleted notification 
+            Notification notification = 
+                new Notification("j2ee.object.deleted", this.getObjectName(), 
+                                 sequenceNumber.getAndIncrement());
+            broadcaster.sendNotification(notification);
+        }
+
+        if (namingResources != null) {
+            namingResources.destroy();
+        }
+
+        synchronized (instanceListenersLock) {
+            instanceListeners = new String[0];
+        }
+
+        super.destroyInternal();
+    }
+    
+    private void resetContext() throws Exception {
+        // Restore the original state ( pre reading web.xml in start )
+        // If you extend this - override this method and make sure to clean up
+        
+        // Don't reset anything that is read from a <Context.../> element since
+        // <Context .../> elements are read at initialisation will not be read
+        // again for this object
+        children = new HashMap<String, Container>();
+        startupTime = 0;
+        startTime = 0;
+        tldScanTime = 0;
+
+        // Bugzilla 32867
+        distributable = false;
+
+        applicationListeners = new String[0];
+        applicationEventListenersObjects = new Object[0];
+        applicationLifecycleListenersObjects = new Object[0];
+        jspConfigDescriptor = new ApplicationJspConfigDescriptor();
+        
+        initializers.clear();
+        
+        createdServlets.clear();
+
+        if(log.isDebugEnabled())
+            log.debug("resetContext " + getObjectName());
+    }
+
+    /**
+     * Return a String representation of this component.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder();
+        if (getParent() != null) {
+            sb.append(getParent().toString());
+            sb.append(".");
+        }
+        sb.append("StandardContext[");
+        sb.append(getName());
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Adjust the URL pattern to begin with a leading slash, if appropriate
+     * (i.e. we are running a servlet 2.2 application).  Otherwise, return
+     * the specified URL pattern unchanged.
+     *
+     * @param urlPattern The URL pattern to be adjusted (if needed)
+     *  and returned
+     */
+    protected String adjustURLPattern(String urlPattern) {
+
+        if (urlPattern == null)
+            return (urlPattern);
+        if (urlPattern.startsWith("/") || urlPattern.startsWith("*."))
+            return (urlPattern);
+        if (!isServlet22())
+            return (urlPattern);
+        if(log.isDebugEnabled())
+            log.debug(sm.getString("standardContext.urlPattern.patternWarning",
+                         urlPattern));
+        return ("/" + urlPattern);
+
+    }
+
+
+    /**
+     * Are we processing a version 2.2 deployment descriptor?
+     */
+    @Override
+    public boolean isServlet22() {
+
+        if (this.publicId == null)
+            return (false);
+        if (this.publicId.equals
+            (org.apache.catalina.startup.Constants.WebDtdPublicId_22))
+            return (true);
+        else
+            return (false);
+
+    }
+
+    @Override
+    public Set<String> addServletSecurity(
+            ApplicationServletRegistration registration,
+            ServletSecurityElement servletSecurityElement) {
+
+        Set<String> conflicts = new HashSet<String>();
+
+        Collection<String> urlPatterns = registration.getMappings();
+        for (String urlPattern : urlPatterns) {
+            boolean foundConflict = false;
+
+            SecurityConstraint[] securityConstraints =
+                findConstraints();
+            for (SecurityConstraint securityConstraint : securityConstraints) {
+
+                SecurityCollection[] collections =
+                    securityConstraint.findCollections();
+                for (SecurityCollection collection : collections) {
+                    if (collection.findPattern(urlPattern)) {
+                        // First pattern found will indicate if there is a
+                        // conflict since for any given pattern all matching
+                        // constraints will be from either the descriptor or
+                        // not. It is not permitted to have a mixture
+                        if (collection.isFromDescriptor()) {
+                            // Skip this pattern
+                            foundConflict = true;
+                            conflicts.add(urlPattern);
+                        } else {
+                            // Need to overwrite constraint for this pattern
+                            // so remove every pattern found
+
+                            // TODO spec 13.4.2 appears to say only the
+                            // conflicting pattern is overwritten, not the
+                            // entire security constraint.
+                            removeConstraint(securityConstraint);
+                        }
+                    }
+                    if (foundConflict) {
+                        break;
+                    }
+                }
+                if (foundConflict) {
+                    break;
+                }
+            }
+            // TODO spec 13.4.2 appears to say that non-conflicting patterns are
+            // still used.
+            // TODO you can't calculate the eventual security constraint now,
+            // you have to wait until the context is started, since application
+            // code can add url patterns after calling setSecurity.
+            if (!foundConflict) {
+                SecurityConstraint[] newSecurityConstraints =
+                        SecurityConstraint.createConstraints(
+                                servletSecurityElement,
+                                urlPattern);
+                for (SecurityConstraint securityConstraint :
+                        newSecurityConstraints) {
+                    addConstraint(securityConstraint);
+                }
+            }
+        }
+
+        return conflicts;
+
+    }
+
+
+    /**
+     * Return a File object representing the base directory for the
+     * entire servlet container (i.e. the Engine container if present).
+     */
+    protected File engineBase() {
+        String base=System.getProperty(Globals.CATALINA_BASE_PROP);
+        if( base == null ) {
+            StandardEngine eng=(StandardEngine)this.getParent().getParent();
+            base=eng.getBaseDir();
+        }
+        return (new File(base));
+    }
+
+
+    /**
+     * Bind current thread, both for CL purposes and for JNDI ENC support
+     * during : startup, shutdown and realoading of the context.
+     *
+     * @return the previous context class loader
+     */
+    protected ClassLoader bindThread() {
+
+        ClassLoader oldContextClassLoader =
+            Thread.currentThread().getContextClassLoader();
+
+        if (getResources() == null)
+            return oldContextClassLoader;
+
+        if (getLoader().getClassLoader() != null) {
+            Thread.currentThread().setContextClassLoader
+                (getLoader().getClassLoader());
+        }
+
+        DirContextURLStreamHandler.bindThread(getResources());
+
+        if (isUseNaming()) {
+            try {
+                ContextBindings.bindThread(this, this);
+            } catch (NamingException e) {
+                // Silent catch, as this is a normal case during the early
+                // startup stages
+            }
+        }
+
+        return oldContextClassLoader;
+
+    }
+
+
+    /**
+     * Unbind thread.
+     */
+    protected void unbindThread(ClassLoader oldContextClassLoader) {
+
+        if (isUseNaming()) {
+            ContextBindings.unbindThread(this, this);
+        }
+
+        DirContextURLStreamHandler.unbindThread();
+
+        Thread.currentThread().setContextClassLoader(oldContextClassLoader);
+    }
+
+
+
+    /**
+     * Get base path.
+     */
+    protected String getBasePath() {
+        String docBase = null;
+        Container container = this;
+        while (container != null) {
+            if (container instanceof Host)
+                break;
+            container = container.getParent();
+        }
+        File file = new File(getDocBase());
+        if (!file.isAbsolute()) {
+            if (container == null) {
+                docBase = (new File(engineBase(), getDocBase())).getPath();
+            } else {
+                // Use the "appBase" property of this container
+                String appBase = ((Host) container).getAppBase();
+                file = new File(appBase);
+                if (!file.isAbsolute())
+                    file = new File(engineBase(), appBase);
+                docBase = (new File(file, getDocBase())).getPath();
+            }
+        } else {
+            docBase = file.getPath();
+        }
+        return docBase;
+    }
+
+
+    /**
+     * Get app base.
+     */
+    protected String getAppBase() {
+        String appBase = null;
+        Container container = this;
+        while (container != null) {
+            if (container instanceof Host)
+                break;
+            container = container.getParent();
+        }
+        if (container != null) {
+            appBase = ((Host) container).getAppBase();
+        }
+        return appBase;
+    }
+
+
+    /**
+     * Get naming context full name.
+     */
+    private String getNamingContextName() {
+    if (namingContextName == null) {
+        Container parent = getParent();
+        if (parent == null) {
+        namingContextName = getName();
+        } else {
+        Stack<String> stk = new Stack<String>();
+        StringBuilder buff = new StringBuilder();
+        while (parent != null) {
+            stk.push(parent.getName());
+            parent = parent.getParent();
+        }
+        while (!stk.empty()) {
+            buff.append("/" + stk.pop());
+        }
+        buff.append(getName());
+        namingContextName = buff.toString();
+        }
+    }
+    return namingContextName;
+    }
+
+    
+    /**
+     * Naming context listener accessor.
+     */
+    public NamingContextListener getNamingContextListener() {
+        return namingContextListener;
+    }
+    
+
+    /**
+     * Naming context listener setter.
+     */
+    public void setNamingContextListener(NamingContextListener namingContextListener) {
+        this.namingContextListener = namingContextListener;
+    }
+    
+
+    /**
+     * Return the request processing paused flag for this Context.
+     */
+    @Override
+    public boolean getPaused() {
+
+        return (this.paused);
+
+    }
+
+
+    public String getHostname() {
+        Container parentHost = getParent();
+        if (parentHost != null) {
+            hostName = parentHost.getName();
+        }
+        if ((hostName == null) || (hostName.length() < 1))
+            hostName = "_";
+        return hostName;
+    }
+
+
+    @Override
+    public boolean fireRequestInitEvent(ServletRequest request) {
+
+        Object instances[] = getApplicationEventListeners();
+
+        if ((instances != null) && (instances.length > 0)) {
+
+            ServletRequestEvent event = 
+                    new ServletRequestEvent(getServletContext(), request);
+
+            for (int i = 0; i < instances.length; i++) {
+                if (instances[i] == null)
+                    continue;
+                if (!(instances[i] instanceof ServletRequestListener))
+                    continue;
+                ServletRequestListener listener =
+                    (ServletRequestListener) instances[i];
+                
+                try {
+                    listener.requestInitialized(event);
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    getLogger().error(sm.getString(
+                            "standardContext.requestListener.requestInit",
+                            instances[i].getClass().getName()), t);
+                    request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+
+    @Override
+    public boolean fireRequestDestroyEvent(ServletRequest request) {
+        Object instances[] = getApplicationEventListeners();
+
+        if ((instances != null) && (instances.length > 0)) {
+
+            ServletRequestEvent event = 
+                new ServletRequestEvent(getServletContext(), request);
+
+            for (int i = 0; i < instances.length; i++) {
+                int j = (instances.length -1) -i;
+                if (instances[j] == null)
+                    continue;
+                if (!(instances[j] instanceof ServletRequestListener))
+                    continue;
+                ServletRequestListener listener =
+                    (ServletRequestListener) instances[j];
+                
+                try {
+                    listener.requestDestroyed(event);
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    getLogger().error(sm.getString(
+                            "standardContext.requestListener.requestInit",
+                            instances[j].getClass().getName()), t);
+                    request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+
+    /**
+     * Set the appropriate context attribute for our work directory.
+     */
+    private void postWorkDirectory() {
+
+        // Acquire (or calculate) the work directory path
+        String workDir = getWorkDir();
+        if (workDir == null || workDir.length() == 0) {
+
+            // Retrieve our parent (normally a host) name
+            String hostName = null;
+            String engineName = null;
+            String hostWorkDir = null;
+            Container parentHost = getParent();
+            if (parentHost != null) {
+                hostName = parentHost.getName();
+                if (parentHost instanceof StandardHost) {
+                    hostWorkDir = ((StandardHost)parentHost).getWorkDir();
+                }
+                Container parentEngine = parentHost.getParent();
+                if (parentEngine != null) {
+                   engineName = parentEngine.getName();
+                }
+            }
+            if ((hostName == null) || (hostName.length() < 1))
+                hostName = "_";
+            if ((engineName == null) || (engineName.length() < 1))
+                engineName = "_";
+
+            String temp = getName();
+            if (temp.startsWith("/"))
+                temp = temp.substring(1);
+            temp = temp.replace('/', '_');
+            temp = temp.replace('\\', '_');
+            if (temp.length() < 1)
+                temp = "_";
+            if (hostWorkDir != null ) {
+                workDir = hostWorkDir + File.separator + temp;
+            } else {
+                workDir = "work" + File.separator + engineName +
+                    File.separator + hostName + File.separator + temp;
+            }
+            setWorkDir(workDir);
+        }
+
+        // Create this directory if necessary
+        File dir = new File(workDir);
+        if (!dir.isAbsolute()) {
+            File catalinaHome = engineBase();
+            String catalinaHomePath = null;
+            try {
+                catalinaHomePath = catalinaHome.getCanonicalPath();
+                dir = new File(catalinaHomePath, workDir);
+            } catch (IOException e) {
+                log.warn(sm.getString("standardContext.workCreateException",
+                        workDir, catalinaHomePath, getName()), e);
+            }
+        }
+        if (!dir.exists() && !dir.mkdirs()) {
+            log.warn(sm.getString("standardContext.workCreateFail", dir,
+                    getName()));
+        }
+
+        // Set the appropriate servlet context attribute
+        if (context == null) {
+            getServletContext();
+        }
+        context.setAttribute(ServletContext.TEMPDIR, dir);
+        context.setAttributeReadOnly(ServletContext.TEMPDIR);
+    }
+
+
+    /**
+     * Set the request processing paused flag for this Context.
+     *
+     * @param paused The new request processing paused flag
+     */
+    private void setPaused(boolean paused) {
+
+        this.paused = paused;
+
+    }
+
+
+    /**
+     * Validate the syntax of a proposed <code>&lt;url-pattern&gt;</code>
+     * for conformance with specification requirements.
+     *
+     * @param urlPattern URL pattern to be validated
+     */
+    private boolean validateURLPattern(String urlPattern) {
+
+        if (urlPattern == null)
+            return (false);
+        if (urlPattern.indexOf('\n') >= 0 || urlPattern.indexOf('\r') >= 0) {
+            return (false);
+        }
+        if (urlPattern.startsWith("*.")) {
+            if (urlPattern.indexOf('/') < 0) {
+                checkUnusualURLPattern(urlPattern);
+                return (true);
+            } else
+                return (false);
+        }
+        if ( (urlPattern.startsWith("/")) &&
+                (urlPattern.indexOf("*.") < 0)) {
+            checkUnusualURLPattern(urlPattern);
+            return (true);
+        } else
+            return (false);
+
+    }
+
+
+    /**
+     * Check for unusual but valid <code>&lt;url-pattern&gt;</code>s.
+     * See Bugzilla 34805, 43079 & 43080
+     */
+    private void checkUnusualURLPattern(String urlPattern) {
+        if (log.isInfoEnabled()) {
+            if(urlPattern.endsWith("*") && (urlPattern.length() < 2 ||
+                    urlPattern.charAt(urlPattern.length()-2) != '/')) {
+                log.info("Suspicious url pattern: \"" + urlPattern + "\"" +
+                        " in context [" + getName() + "] - see" +
+                        " section SRV.11.2 of the Servlet specification" );
+            }
+        }
+    }
+
+
+    // ------------------------------------------------------------- Operations
+
+
+    /**
+     * JSR77 deploymentDescriptor attribute
+     *
+     * @return string deployment descriptor 
+     */
+    public String getDeploymentDescriptor() {
+    
+        InputStream stream = null;
+        ServletContext servletContext = getServletContext();
+        if (servletContext != null) {
+            stream = servletContext.getResourceAsStream(
+                org.apache.catalina.startup.Constants.ApplicationWebXml);
+        }
+        if (stream == null) {
+            return "";
+        }
+        StringBuilder sb = new StringBuilder();
+        BufferedReader br = null;
+        try {
+            br = new BufferedReader(new InputStreamReader(stream));
+            String strRead = "";
+            while (strRead != null) {
+                sb.append(strRead);
+                strRead = br.readLine();
+            }
+        } catch (IOException e) {
+            return "";
+        } finally {
+            if (br != null) {
+                try {
+                    br.close();
+                } catch (IOException ioe) {/*Ignore*/}
+            }
+        }
+
+        return sb.toString(); 
+    }
+    
+    
+    /**
+     * JSR77 servlets attribute
+     *
+     * @return list of all servlets ( we know about )
+     */
+    public String[] getServlets() {
+        
+        String[] result = null;
+
+        Container[] children = findChildren();
+        if (children != null) {
+            result = new String[children.length];
+            for( int i=0; i< children.length; i++ ) {
+                result[i] = children[i].getObjectName().toString();
+            }
+        }
+
+        return result;
+    }
+    
+
+    @Override
+    protected String getObjectNameKeyProperties() {
+
+        StringBuilder keyProperties =
+            new StringBuilder("j2eeType=WebModule,");
+        keyProperties.append(getObjectKeyPropertiesNameOnly());
+        keyProperties.append(",J2EEApplication=");
+        keyProperties.append(getJ2EEApplication());
+        keyProperties.append(",J2EEServer=");
+        keyProperties.append(getJ2EEServer());
+
+        return keyProperties.toString();
+    }
+    
+    private String getObjectKeyPropertiesNameOnly() {
+        StringBuilder result = new StringBuilder("name=//");
+        String hostname = getParent().getName();
+        if (hostname == null) {
+            result.append("DEFAULT");
+        } else {
+            result.append(hostname);
+        }
+        
+        String contextName = getName();
+        if (!contextName.startsWith("/")) {
+            result.append('/');
+        }
+        result.append(contextName);
+
+        return result.toString();
+    }
+
+    @Override
+    protected void initInternal() throws LifecycleException {
+        super.initInternal();
+        
+        if (processTlds) {
+            this.addLifecycleListener(new TldConfig());
+        }
+
+        // Register the naming resources
+        if (namingResources != null) {
+            namingResources.init();
+        }
+
+        // Send j2ee.object.created notification 
+        if (this.getObjectName() != null) {
+            Notification notification = new Notification("j2ee.object.created",
+                    this.getObjectName(), sequenceNumber.getAndIncrement());
+            broadcaster.sendNotification(notification);
+        }
+    }
+
+
+    /* Remove a JMX notficationListener 
+     * @see javax.management.NotificationEmitter#removeNotificationListener(javax.management.NotificationListener, javax.management.NotificationFilter, java.lang.Object)
+     */
+    @Override
+    public void removeNotificationListener(NotificationListener listener, 
+            NotificationFilter filter, Object object) throws ListenerNotFoundException {
+        broadcaster.removeNotificationListener(listener,filter,object);
+    }
+    
+    private MBeanNotificationInfo[] notificationInfo;
+    
+    /* Get JMX Broadcaster Info
+     * @TODO use StringManager for international support!
+     * @TODO This two events we not send j2ee.state.failed and j2ee.attribute.changed!
+     * @see javax.management.NotificationBroadcaster#getNotificationInfo()
+     */
+    @Override
+    public MBeanNotificationInfo[] getNotificationInfo() {
+        // FIXME: i18n
+        if(notificationInfo == null) {
+            notificationInfo = new MBeanNotificationInfo[]{
+                    new MBeanNotificationInfo(new String[] {
+                    "j2ee.object.created"},
+                    Notification.class.getName(),
+                    "web application is created"
+                    ), 
+                    new MBeanNotificationInfo(new String[] {
+                    "j2ee.state.starting"},
+                    Notification.class.getName(),
+                    "change web application is starting"
+                    ),
+                    new MBeanNotificationInfo(new String[] {
+                    "j2ee.state.running"},
+                    Notification.class.getName(),
+                    "web application is running"
+                    ),
+                    new MBeanNotificationInfo(new String[] {
+                    "j2ee.state.stopping"},
+                    Notification.class.getName(),
+                    "web application start to stopped"
+                    ),
+                    new MBeanNotificationInfo(new String[] {
+                    "j2ee.object.stopped"},
+                    Notification.class.getName(),
+                    "web application is stopped"
+                    ),
+                    new MBeanNotificationInfo(new String[] {
+                    "j2ee.object.deleted"},
+                    Notification.class.getName(),
+                    "web application is deleted"
+                    )
+            };
+            
+        }
+        
+        return notificationInfo;
+    }
+    
+    
+    /* Add a JMX-NotificationListener
+     * @see javax.management.NotificationBroadcaster#addNotificationListener(javax.management.NotificationListener, javax.management.NotificationFilter, java.lang.Object)
+     */
+    @Override
+    public void addNotificationListener(NotificationListener listener, 
+            NotificationFilter filter, Object object) throws IllegalArgumentException {
+        broadcaster.addNotificationListener(listener,filter,object);
+    }
+    
+    
+    /**
+     * Remove a JMX-NotificationListener 
+     * @see javax.management.NotificationBroadcaster#removeNotificationListener(javax.management.NotificationListener)
+     */
+    @Override
+    public void removeNotificationListener(NotificationListener listener) 
+    throws ListenerNotFoundException {
+        broadcaster.removeNotificationListener(listener);
+    }
+    
+    
+    // ------------------------------------------------------------- Attributes
+
+
+    /**
+     * Return the naming resources associated with this web application.
+     */
+    public javax.naming.directory.DirContext getStaticResources() {
+
+        return getResources();
+
+    }
+
+
+    /**
+     * Return the naming resources associated with this web application.
+     * FIXME: Fooling introspection ... 
+     */
+    public javax.naming.directory.DirContext findStaticResources() {
+
+        return getResources();
+
+    }
+
+
+    /**
+     * Return the naming resources associated with this web application.
+     */
+    public String[] getWelcomeFiles() {
+
+        return findWelcomeFiles();
+
+    }
+
+     /**
+     * Set the validation feature of the XML parser used when
+     * parsing xml instances.
+     * @param webXmlValidation true to enable xml instance validation
+     */
+    @Override
+    public void setXmlValidation(boolean webXmlValidation){
+        
+        this.webXmlValidation = webXmlValidation;
+
+    }
+
+    /**
+     * Get the server.xml <context> attribute's xmlValidation.
+     * @return true if validation is enabled.
+     *
+     */
+    @Override
+    public boolean getXmlValidation(){
+        return webXmlValidation;
+    }
+
+
+    /**
+     * Get the server.xml <context> attribute's xmlNamespaceAware.
+     * @return true if namespace awarenes is enabled.
+     */
+    @Override
+    public boolean getXmlNamespaceAware(){
+        return webXmlNamespaceAware;
+    }
+
+
+    /**
+     * Set the namespace aware feature of the XML parser used when
+     * parsing xml instances.
+     * @param webXmlNamespaceAware true to enable namespace awareness
+     */
+    @Override
+    public void setXmlNamespaceAware(boolean webXmlNamespaceAware){
+        this.webXmlNamespaceAware= webXmlNamespaceAware;
+    }    
+
+
+    /**
+     * Set the validation feature of the XML parser used when
+     * parsing tlds files. 
+     * @param tldValidation true to enable xml instance validation
+     */
+    @Override
+    public void setTldValidation(boolean tldValidation){
+        
+        this.tldValidation = tldValidation;
+
+    }
+
+    /**
+     * Get the server.xml <context> attribute's webXmlValidation.
+     * @return true if validation is enabled.
+     *
+     */
+    @Override
+    public boolean getTldValidation(){
+        return tldValidation;
+    }
+
+    /**
+     * Sets the process TLDs attribute.
+     *
+     * @param newProcessTlds The new value
+     */
+    public void setProcessTlds(boolean newProcessTlds) {
+        processTlds = newProcessTlds;
+    }
+
+    /**
+     * Returns the processTlds attribute value.
+     */
+    public boolean getProcessTlds() {
+        return processTlds;
+    }
+
+    /**
+     * Get the server.xml &lt;host&gt; attribute's xmlNamespaceAware.
+     * @return true if namespace awarenes is enabled.
+     */
+    @Override
+    public boolean getTldNamespaceAware(){
+        return tldNamespaceAware;
+    }
+
+
+    /**
+     * Set the namespace aware feature of the XML parser used when
+     * parsing xml instances.
+     * @param tldNamespaceAware true to enable namespace awareness
+     */
+    @Override
+    public void setTldNamespaceAware(boolean tldNamespaceAware){
+        this.tldNamespaceAware= tldNamespaceAware;
+    }    
+
+
+    /** 
+     * Support for "stateManageable" JSR77 
+     */
+    public boolean isStateManageable() {
+        return true;
+    }
+    
+    public void startRecursive() throws LifecycleException {
+        // nothing to start recursive, the servlets will be started by load-on-startup
+        start();
+    }
+    
+    /**
+     * The J2EE Server ObjectName this module is deployed on.
+     */     
+    private String server = null;
+    
+    /**
+     * The Java virtual machines on which this module is running.
+     */       
+    private String[] javaVMs = null;
+    
+    public String getServer() {
+        return server;
+    }
+        
+    public String setServer(String server) {
+        return this.server=server;
+    }
+        
+    public String[] getJavaVMs() {
+        return javaVMs;
+    }
+        
+    public String[] setJavaVMs(String[] javaVMs) {
+        return this.javaVMs = javaVMs;
+    }
+    
+    /**
+     * Gets the time this context was started.
+     *
+     * @return Time (in milliseconds since January 1, 1970, 00:00:00) when this
+     * context was started 
+     */
+    public long getStartTime() {
+        return startTime;
+    }
+    
+    public boolean isEventProvider() {
+        return false;
+    }
+    
+    public boolean isStatisticsProvider() {
+        return false;
+    }
+
+    private abstract static class RunnableWithLifecycleException
+            implements Runnable {
+        
+        protected LifecycleException le = null;
+        
+        public LifecycleException getLifecycleException() {
+            return le;
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardContextValve.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardContextValve.java
new file mode 100644
index 0000000..fbbfe0c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardContextValve.java
@@ -0,0 +1,232 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+
+import java.io.IOException;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.comet.CometEvent;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.valves.ValveBase;
+import org.apache.tomcat.util.buf.MessageBytes;
+
+/**
+ * Valve that implements the default basic behavior for the
+ * <code>StandardContext</code> container implementation.
+ * <p>
+ * <b>USAGE CONSTRAINT</b>:  This implementation is likely to be useful only
+ * when processing HTTP requests.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: StandardContextValve.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+final class StandardContextValve
+    extends ValveBase {
+
+    //------------------------------------------------------ Constructor
+    public StandardContextValve() {
+        super(true);
+    }
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The descriptive information related to this implementation.
+     */
+    private static final String info =
+        "org.apache.catalina.core.StandardContextValve/1.0";
+
+
+    private StandardContext context = null;
+    
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return descriptive information about this Valve implementation.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Cast to a StandardContext right away, as it will be needed later.
+     * 
+     * @see org.apache.catalina.Contained#setContainer(org.apache.catalina.Container)
+     */
+    @Override
+    public void setContainer(Container container) {
+        super.setContainer(container);
+        context = (StandardContext) container;
+    }
+
+    
+    /**
+     * Select the appropriate child Wrapper to process this request,
+     * based on the specified request URI.  If no matching Wrapper can
+     * be found, return an appropriate HTTP error.
+     *
+     * @param request Request to be processed
+     * @param response Response to be produced
+     *
+     * @exception IOException if an input/output error occurred
+     * @exception ServletException if a servlet error occurred
+     */
+    @Override
+    public final void invoke(Request request, Response response)
+        throws IOException, ServletException {
+
+        // Disallow any direct access to resources under WEB-INF or META-INF
+        MessageBytes requestPathMB = request.getRequestPathMB();
+        if ((requestPathMB.startsWithIgnoreCase("/META-INF/", 0))
+            || (requestPathMB.equalsIgnoreCase("/META-INF"))
+            || (requestPathMB.startsWithIgnoreCase("/WEB-INF/", 0))
+            || (requestPathMB.equalsIgnoreCase("/WEB-INF"))) {
+            notFound(response);
+            return;
+        }
+
+        // Wait if we are reloading
+        boolean reloaded = false;
+        while (context.getPaused()) {
+            reloaded = true;
+            try {
+                Thread.sleep(1000);
+            } catch (InterruptedException e) {
+                // Ignore
+            }
+        }
+
+        // Reloading will have stopped the old webappclassloader and
+        // created a new one
+        if (reloaded &&
+                context.getLoader() != null &&
+                context.getLoader().getClassLoader() != null) {
+            Thread.currentThread().setContextClassLoader(
+                    context.getLoader().getClassLoader());
+        }
+
+        // Select the Wrapper to be used for this Request
+        Wrapper wrapper = request.getWrapper();
+        if (wrapper == null) {
+            notFound(response);
+            return;
+        } else if (wrapper.isUnavailable()) {
+            // May be as a result of a reload, try and find the new wrapper
+            wrapper = (Wrapper) container.findChild(wrapper.getName());
+            if (wrapper == null) {
+                notFound(response);
+                return;
+            }
+        }
+
+        // Don't fire listeners during async processing
+        // If a request init listener throws an exception, the request is
+        // aborted
+        boolean asyncAtStart = request.isAsync(); 
+        if (asyncAtStart || context.fireRequestInitEvent(request)) {
+            if (request.isAsyncSupported()) {
+                request.setAsyncSupported(wrapper.getPipeline().isAsyncSupported());
+            }
+            wrapper.getPipeline().getFirst().invoke(request, response);
+
+            // If the request was async at the start and an error occurred then
+            // the async error handling will kick-in and that will fire the
+            // request destroyed event *after* the error handling has taken
+            // place
+            if (!(request.isAsync() || (asyncAtStart && request.getAttribute(
+                        RequestDispatcher.ERROR_EXCEPTION) != null))) {
+                context.fireRequestDestroyEvent(request);
+            }
+        }
+    }
+
+
+    /**
+     * Select the appropriate child Wrapper to process this request,
+     * based on the specified request URI.  If no matching Wrapper can
+     * be found, return an appropriate HTTP error.
+     *
+     * @param request Request to be processed
+     * @param response Response to be produced
+     * @param event
+     *
+     * @exception IOException if an input/output error occurred
+     * @exception ServletException if a servlet error occurred
+     */
+    @Override
+    public final void event(Request request, Response response, CometEvent event)
+        throws IOException, ServletException {
+
+        // Select the Wrapper to be used for this Request
+        Wrapper wrapper = request.getWrapper();
+
+        // Normal request processing
+        // FIXME: Firing request listeners could be an addition to the core
+        // comet API
+        
+        //if (context.fireRequestInitEvent(request)) {
+            wrapper.getPipeline().getFirst().event(request, response, event);
+        //    context.fireRequestDestroyEvent(request);
+        //}
+    }
+
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Report a "not found" error for the specified resource.  FIXME:  We
+     * should really be using the error reporting settings for this web
+     * application, but currently that code runs at the wrapper level rather
+     * than the context level.
+     *
+     * @param response The response we are creating
+     */
+    private void notFound(HttpServletResponse response) {
+
+        try {
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+        } catch (IllegalStateException e) {
+            // Ignore
+        } catch (IOException e) {
+            // Ignore
+        }
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardEngine.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardEngine.java
new file mode 100644
index 0000000..40e2507
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardEngine.java
@@ -0,0 +1,499 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.catalina.AccessLog;
+import org.apache.catalina.Container;
+import org.apache.catalina.ContainerEvent;
+import org.apache.catalina.ContainerListener;
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Globals;
+import org.apache.catalina.Host;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.Realm;
+import org.apache.catalina.Service;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.realm.JAASRealm;
+import org.apache.catalina.util.ServerInfo;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * Standard implementation of the <b>Engine</b> interface.  Each
+ * child container must be a Host implementation to process the specific
+ * fully qualified host name of that virtual host. <br/>
+ * You can set the jvmRoute direct or with the System.property <b>jvmRoute</b>.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: StandardEngine.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+public class StandardEngine extends ContainerBase implements Engine {
+
+    private static final Log log = LogFactory.getLog(StandardEngine.class);
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Create a new StandardEngine component with the default basic Valve.
+     */
+    public StandardEngine() {
+
+        super();
+        pipeline.setBasic(new StandardEngineValve());
+        /* Set the jmvRoute using the system property jvmRoute */
+        try {
+            setJvmRoute(System.getProperty("jvmRoute"));
+        } catch(Exception ex) {
+            log.warn(sm.getString("standardEngine.jvmRouteFail"));
+        }
+        // By default, the engine will hold the reloading thread
+        backgroundProcessorDelay = 10;
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Host name to use when no server host, or an unknown host,
+     * is specified in the request.
+     */
+    private String defaultHost = null;
+
+
+    /**
+     * The descriptive information string for this implementation.
+     */
+    private static final String info =
+        "org.apache.catalina.core.StandardEngine/1.0";
+
+
+    /**
+     * The <code>Service</code> that owns this Engine, if any.
+     */
+    private Service service = null;
+
+    /** Allow the base dir to be specified explicitly for
+     * each engine. In time we should stop using catalina.base property -
+     * otherwise we loose some flexibility.
+     */
+    private String baseDir = null;
+
+    /**
+     * The JVM Route ID for this Tomcat instance. All Route ID's must be unique
+     * across the cluster.
+     */
+    private String jvmRouteId;
+
+    /**
+     * Default access log to use for request/response pairs where we can't ID
+     * the intended host and context.
+     */
+    private final AtomicReference<AccessLog> defaultAccessLog =
+        new AtomicReference<AccessLog>();
+
+    // ------------------------------------------------------------- Properties
+
+    /** Provide a default in case no explicit configuration is set
+     *
+     * @return configured realm, or a JAAS realm by default
+     */
+    @Override
+    public Realm getRealm() {
+        Realm configured=super.getRealm();
+        // If no set realm has been called - default to JAAS
+        // This can be overridden at engine, context and host level  
+        if( configured==null ) {
+            configured=new JAASRealm();
+            this.setRealm( configured );
+        }
+        return configured;
+    }
+
+
+    /**
+     * Return the default host.
+     */
+    @Override
+    public String getDefaultHost() {
+
+        return (defaultHost);
+
+    }
+
+
+    /**
+     * Set the default host.
+     *
+     * @param host The new default host
+     */
+    @Override
+    public void setDefaultHost(String host) {
+
+        String oldDefaultHost = this.defaultHost;
+        if (host == null) {
+            this.defaultHost = null;
+        } else {
+            this.defaultHost = host.toLowerCase(Locale.ENGLISH);
+        }
+        support.firePropertyChange("defaultHost", oldDefaultHost,
+                                   this.defaultHost);
+
+    }
+    
+
+    /**
+     * Set the cluster-wide unique identifier for this Engine.
+     * This value is only useful in a load-balancing scenario.
+     * <p>
+     * This property should not be changed once it is set.
+     */
+    @Override
+    public void setJvmRoute(String routeId) {
+        jvmRouteId = routeId;
+    }
+
+
+    /**
+     * Retrieve the cluster-wide unique identifier for this Engine.
+     * This value is only useful in a load-balancing scenario.
+     */
+    @Override
+    public String getJvmRoute() {
+        return jvmRouteId;
+    }
+
+
+    /**
+     * Return the <code>Service</code> with which we are associated (if any).
+     */
+    @Override
+    public Service getService() {
+
+        return (this.service);
+
+    }
+
+
+    /**
+     * Set the <code>Service</code> with which we are associated (if any).
+     *
+     * @param service The service that owns this Engine
+     */
+    @Override
+    public void setService(Service service) {
+        this.service = service;
+    }
+
+    public String getBaseDir() {
+        if( baseDir==null ) {
+            baseDir=System.getProperty(Globals.CATALINA_BASE_PROP);
+        }
+        if( baseDir==null ) {
+            baseDir=System.getProperty(Globals.CATALINA_HOME_PROP);
+        }
+        return baseDir;
+    }
+
+    public void setBaseDir(String baseDir) {
+        this.baseDir = baseDir;
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add a child Container, only if the proposed child is an implementation
+     * of Host.
+     *
+     * @param child Child container to be added
+     */
+    @Override
+    public void addChild(Container child) {
+
+        if (!(child instanceof Host))
+            throw new IllegalArgumentException
+                (sm.getString("standardEngine.notHost"));
+        super.addChild(child);
+
+    }
+
+
+    /**
+     * Return descriptive information about this Container implementation and
+     * the corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+    /**
+     * Disallow any attempt to set a parent for this Container, since an
+     * Engine is supposed to be at the top of the Container hierarchy.
+     *
+     * @param container Proposed parent Container
+     */
+    @Override
+    public void setParent(Container container) {
+
+        throw new IllegalArgumentException
+            (sm.getString("standardEngine.notParent"));
+
+    }
+
+
+    /**
+     * 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 {
+        
+        // Log our server identification information
+        if(log.isInfoEnabled())
+            log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());
+
+        // Standard container startup
+        super.startInternal();
+    }
+
+    
+    /**
+     * Return a String representation of this component.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("StandardEngine[");
+        sb.append(getName());
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+    /**
+     * Override the default implementation. If no access log is defined for the
+     * Engine, look for one in the Engine's default host and then the default
+     * host's ROOT context. If still none is found, return the default NoOp
+     * access log.
+     */
+    @Override
+    public void logAccess(Request request, Response response, long time,
+            boolean useDefault) {
+
+        boolean logged = false;
+        
+        if (getAccessLog() != null) {
+            accessLog.log(request, response, time);
+            logged = true;
+        }
+
+        if (!logged && useDefault) {
+            AccessLog newDefaultAccessLog = defaultAccessLog.get();
+            if (newDefaultAccessLog == null) {
+                // If we reached this point, this Engine can't have an AccessLog
+                // Look in the defaultHost
+                Host host = (Host) findChild(getDefaultHost());
+                Context context = null;
+                if (host != null && host.getState().isAvailable()) {
+                    newDefaultAccessLog = host.getAccessLog();
+
+                    if (newDefaultAccessLog != null) {
+                        if (defaultAccessLog.compareAndSet(null,
+                                newDefaultAccessLog)) {
+                            AccessLogListener l = new AccessLogListener(this,
+                                    host, null);
+                            l.install();
+                        }
+                    } else {
+                        // Try the ROOT context of default host
+                        context = (Context) host.findChild("");
+                        if (context != null &&
+                                context.getState().isAvailable()) {
+                            newDefaultAccessLog = context.getAccessLog();
+                            if (newDefaultAccessLog != null) {
+                                if (defaultAccessLog.compareAndSet(null,
+                                        newDefaultAccessLog)) {
+                                    AccessLogListener l = new AccessLogListener(
+                                            this, null, context);
+                                    l.install();
+                                }
+                            }
+                        }
+                    }
+                }
+
+                if (newDefaultAccessLog == null) {
+                    newDefaultAccessLog = new NoopAccessLog();
+                    if (defaultAccessLog.compareAndSet(null,
+                            newDefaultAccessLog)) {
+                        AccessLogListener l = new AccessLogListener(this, host,
+                                context);
+                        l.install();
+                    }
+                }
+            }
+
+            newDefaultAccessLog.log(request, response, time);
+        }
+    }
+
+
+    /**
+     * Return the parent class loader for this component.
+     */
+    @Override
+    public ClassLoader getParentClassLoader() {
+        if (parentClassLoader != null)
+            return (parentClassLoader);
+        if (service != null) {
+            return (service.getParentClassLoader());
+        }
+        return (ClassLoader.getSystemClassLoader());
+    }
+
+    // -------------------- JMX registration  --------------------
+
+    @Override
+    protected String getObjectNameKeyProperties() {
+        return "type=Engine";
+    }
+
+    // ----------------------------------------------------------- Inner classes
+    protected static final class NoopAccessLog implements AccessLog {
+
+        @Override
+        public void log(Request request, Response response, long time) {
+            // NOOP
+        }
+
+        @Override
+        public void setRequestAttributesEnabled(
+                boolean requestAttributesEnabled) {
+            // NOOP
+            
+        }
+
+        @Override
+        public boolean getRequestAttributesEnabled() {
+            // NOOP
+            return false;
+        }
+    }
+    
+    protected static final class AccessLogListener
+            implements PropertyChangeListener, LifecycleListener,
+            ContainerListener {
+
+        private StandardEngine engine;
+        private Host host;
+        private Context context;
+        private volatile boolean disabled = false;
+
+        public AccessLogListener(StandardEngine engine, Host host,
+                Context context) {
+            this.engine = engine;
+            this.host = host;
+            this.context = context;
+        }
+
+        public void install() {
+            engine.addPropertyChangeListener(this);
+            if (host != null) {
+                host.addContainerListener(this);
+                host.addLifecycleListener(this);
+            }
+            if (context != null) {
+                context.addLifecycleListener(this);
+            }
+        }
+
+        private void uninstall() {
+            disabled = true;
+            if (context != null) {
+                context.removeLifecycleListener(this);
+            }
+            if (host != null) {
+                host.removeLifecycleListener(this);
+                host.removeContainerListener(this);
+            }
+            engine.removePropertyChangeListener(this);
+        }
+
+        @Override
+        public void lifecycleEvent(LifecycleEvent event) {
+            if (disabled) return;
+
+            String type = event.getType();
+            if (Lifecycle.AFTER_START_EVENT.equals(type) ||
+                    Lifecycle.BEFORE_STOP_EVENT.equals(type) ||
+                    Lifecycle.BEFORE_DESTROY_EVENT.equals(type)) {
+                // Container is being started/stopped/removed
+                // Force re-calculation and disable listener since it won't
+                // be re-used
+                engine.defaultAccessLog.set(null);
+                uninstall();
+            }
+        }
+
+        @Override
+        public void propertyChange(PropertyChangeEvent evt) {
+            if (disabled) return;
+            if ("defaultHost".equals(evt.getPropertyName())) {
+                // Force re-calculation and disable listener since it won't
+                // be re-used
+                engine.defaultAccessLog.set(null);
+                uninstall();
+            }
+        }
+
+        @Override
+        public void containerEvent(ContainerEvent event) {
+            // Only useful for hosts
+            if (disabled) return;
+            if (Container.ADD_CHILD_EVENT.equals(event.getType())) {
+                Context context = (Context) event.getData();
+                if ("".equals(context.getPath())) {
+                    // Force re-calculation and disable listener since it won't
+                    // be re-used
+                    engine.defaultAccessLog.set(null);
+                    uninstall();
+                }
+            }
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardEngineValve.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardEngineValve.java
new file mode 100644
index 0000000..b38e465
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardEngineValve.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.core;
+
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.Host;
+import org.apache.catalina.comet.CometEvent;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.valves.ValveBase;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Valve that implements the default basic behavior for the
+ * <code>StandardEngine</code> container implementation.
+ * <p>
+ * <b>USAGE CONSTRAINT</b>:  This implementation is likely to be useful only
+ * when processing HTTP requests.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: StandardEngineValve.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+final class StandardEngineValve
+    extends ValveBase {
+
+    //------------------------------------------------------ Constructor
+    public StandardEngineValve() {
+        super(true);
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The descriptive information related to this implementation.
+     */
+    private static final String info =
+        "org.apache.catalina.core.StandardEngineValve/1.0";
+
+
+    /**
+     * The string manager for this package.
+     */
+    private static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return descriptive information about this Valve implementation.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Select the appropriate child Host to process this request,
+     * based on the requested server name.  If no matching Host can
+     * be found, return an appropriate HTTP error.
+     *
+     * @param request Request to be processed
+     * @param response Response to be produced
+     *
+     * @exception IOException if an input/output error occurred
+     * @exception ServletException if a servlet error occurred
+     */
+    @Override
+    public final void invoke(Request request, Response response)
+        throws IOException, ServletException {
+
+        // Select the Host to be used for this Request
+        Host host = request.getHost();
+        if (host == null) {
+            response.sendError
+                (HttpServletResponse.SC_BAD_REQUEST,
+                 sm.getString("standardEngine.noHost", 
+                              request.getServerName()));
+            return;
+        }
+        if (request.isAsyncSupported()) {
+            request.setAsyncSupported(host.getPipeline().isAsyncSupported());
+        }
+
+        // Ask this Host to process this request
+        host.getPipeline().getFirst().invoke(request, response);
+
+    }
+
+
+    /**
+     * Process Comet event.
+     *
+     * @param request Request to be processed
+     * @param response Response to be produced
+     * @param event the event
+     *
+     * @exception IOException if an input/output error occurred
+     * @exception ServletException if a servlet error occurred
+     */
+    @Override
+    public final void event(Request request, Response response, CometEvent event)
+        throws IOException, ServletException {
+
+        // Ask this Host to process this request
+        request.getHost().getPipeline().getFirst().event(request, response, event);
+
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardHost.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardHost.java
new file mode 100644
index 0000000..fc6c136
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardHost.java
@@ -0,0 +1,814 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.regex.Pattern;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Context;
+import org.apache.catalina.Host;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.Valve;
+import org.apache.catalina.loader.WebappClassLoader;
+import org.apache.catalina.mbeans.MBeanUtils;
+import org.apache.catalina.valves.ValveBase;
+import org.apache.tomcat.util.ExceptionUtils;
+
+
+/**
+ * Standard implementation of the <b>Host</b> interface.  Each
+ * child container must be a Context implementation to process the
+ * requests directed to a particular web application.
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @version $Id: StandardHost.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+public class StandardHost extends ContainerBase implements Host {
+
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( StandardHost.class );
+    
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Create a new StandardHost component with the default basic Valve.
+     */
+    public StandardHost() {
+
+        super();
+        pipeline.setBasic(new StandardHostValve());
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The set of aliases for this Host.
+     */
+    private String[] aliases = new String[0];
+    
+    private final Object aliasesLock = new Object();
+
+
+    /**
+     * The application root for this Host.
+     */
+    private String appBase = "webapps";
+
+    /**
+     * The XML root for this Host.
+     */
+    private String xmlBase = null;
+
+    /**
+     * The auto deploy flag for this Host.
+     */
+    private boolean autoDeploy = true;
+
+
+    /**
+     * The Java class name of the default context configuration class
+     * for deployed web applications.
+     */
+    private String configClass =
+        "org.apache.catalina.startup.ContextConfig";
+
+
+    /**
+     * The Java class name of the default Context implementation class for
+     * deployed web applications.
+     */
+    private String contextClass =
+        "org.apache.catalina.core.StandardContext";
+
+
+    /**
+     * The deploy on startup flag for this Host.
+     */
+    private boolean deployOnStartup = true;
+
+
+    /**
+     * deploy Context XML config files property.
+     */
+    private boolean deployXML = true;
+
+
+    /**
+     * Should XML files be copied to $CATALINA_BASE/conf/<engine>/<host> by
+     * default when a web application is deployed?
+     */
+    private boolean copyXML = false;
+
+
+    /**
+     * The Java class name of the default error reporter implementation class 
+     * for deployed web applications.
+     */
+    private String errorReportValveClass =
+        "org.apache.catalina.valves.ErrorReportValve";
+
+    /**
+     * The descriptive information string for this implementation.
+     */
+    private static final String info =
+        "org.apache.catalina.core.StandardHost/1.0";
+
+
+    /**
+     * Unpack WARs property.
+     */
+    private boolean unpackWARs = true;
+
+
+    /**
+     * Work Directory base for applications.
+     */
+    private String workDir = null;
+
+
+    /**
+     * Should we create directories upon startup for appBase and xmlBase
+     */
+     private boolean createDirs = true;
+
+     
+     /**
+      * Track the class loaders for the child web applications so memory leaks
+      * can be detected.
+      */
+     private Map<ClassLoader, String> childClassLoaders =
+         new WeakHashMap<ClassLoader, String>();
+     
+     
+     /**
+      * Any file or directory in {@link #appBase} that this pattern matches will
+      * be ignored by the automatic deployment process (both
+      * {@link #deployOnStartup} and {@link #autoDeploy}).
+      */
+     private Pattern deployIgnore = null;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the application root for this Host.  This can be an absolute
+     * pathname, a relative pathname, or a URL.
+     */
+    @Override
+    public String getAppBase() {
+
+        return (this.appBase);
+
+    }
+
+    /**
+     * Return the XML root for this Host.  This can be an absolute
+     * pathname, a relative pathname, or a URL.
+     * If null, defaults to ${catalina.base}/conf/ directory
+     */
+    @Override
+    public String getXmlBase() {
+
+        return (this.xmlBase);
+
+    }
+
+    /**
+     * Set the application root for this Host.  This can be an absolute
+     * pathname, a relative pathname, or a URL.
+     *
+     * @param appBase The new application root
+     */
+    @Override
+    public void setAppBase(String appBase) {
+
+        String oldAppBase = this.appBase;
+        this.appBase = appBase;
+        support.firePropertyChange("appBase", oldAppBase, this.appBase);
+
+    }
+    
+    /**
+     * Set the Xml root for this Host.  This can be an absolute
+     * pathname, a relative pathname, or a URL.
+     * If null, defaults to ${catalina.base}/conf/ directory
+     *
+     * @param xmlBase The new XML root
+     */
+    @Override
+    public void setXmlBase(String xmlBase) {
+
+        String oldXmlBase = this.xmlBase;
+        this.xmlBase = xmlBase;
+        support.firePropertyChange("xmlBase", oldXmlBase, this.xmlBase);
+
+    }
+
+    /**
+     * Returns true if the Host will attempt to create directories for appBase and xmlBase
+     * unless they already exist.
+     */
+    @Override
+    public boolean getCreateDirs() {
+        return createDirs;
+    }
+
+    /**
+     * Set to true if the Host should attempt to create directories for xmlBase and appBase upon startup
+     * @param createDirs
+     */
+    @Override
+    public void setCreateDirs(boolean createDirs) {
+        this.createDirs = createDirs;
+    }
+
+    /**
+     * Return the value of the auto deploy flag.  If true, it indicates that 
+     * this host's child webapps will be dynamically deployed.
+     */
+    @Override
+    public boolean getAutoDeploy() {
+
+        return (this.autoDeploy);
+
+    }
+
+
+    /**
+     * Set the auto deploy flag value for this host.
+     * 
+     * @param autoDeploy The new auto deploy flag
+     */
+    @Override
+    public void setAutoDeploy(boolean autoDeploy) {
+
+        boolean oldAutoDeploy = this.autoDeploy;
+        this.autoDeploy = autoDeploy;
+        support.firePropertyChange("autoDeploy", oldAutoDeploy, 
+                                   this.autoDeploy);
+
+    }
+
+
+    /**
+     * Return the Java class name of the context configuration class
+     * for new web applications.
+     */
+    @Override
+    public String getConfigClass() {
+
+        return (this.configClass);
+
+    }
+
+
+    /**
+     * Set the Java class name of the context configuration class
+     * for new web applications.
+     *
+     * @param configClass The new context configuration class
+     */
+    @Override
+    public void setConfigClass(String configClass) {
+
+        String oldConfigClass = this.configClass;
+        this.configClass = configClass;
+        support.firePropertyChange("configClass",
+                                   oldConfigClass, this.configClass);
+
+    }
+
+
+    /**
+     * Return the Java class name of the Context implementation class
+     * for new web applications.
+     */
+    public String getContextClass() {
+
+        return (this.contextClass);
+
+    }
+
+
+    /**
+     * Set the Java class name of the Context implementation class
+     * for new web applications.
+     *
+     * @param contextClass The new context implementation class
+     */
+    public void setContextClass(String contextClass) {
+
+        String oldContextClass = this.contextClass;
+        this.contextClass = contextClass;
+        support.firePropertyChange("contextClass",
+                                   oldContextClass, this.contextClass);
+
+    }
+
+
+    /**
+     * Return the value of the deploy on startup flag.  If true, it indicates 
+     * that this host's child webapps should be discovered and automatically 
+     * deployed at startup time.
+     */
+    @Override
+    public boolean getDeployOnStartup() {
+
+        return (this.deployOnStartup);
+
+    }
+
+
+    /**
+     * Set the deploy on startup flag value for this host.
+     * 
+     * @param deployOnStartup The new deploy on startup flag
+     */
+    @Override
+    public void setDeployOnStartup(boolean deployOnStartup) {
+
+        boolean oldDeployOnStartup = this.deployOnStartup;
+        this.deployOnStartup = deployOnStartup;
+        support.firePropertyChange("deployOnStartup", oldDeployOnStartup, 
+                                   this.deployOnStartup);
+
+    }
+
+
+    /**
+     * Deploy XML Context config files flag accessor.
+     */
+    public boolean isDeployXML() {
+
+        return (deployXML);
+
+    }
+
+
+    /**
+     * Deploy XML Context config files flag mutator.
+     */
+    public void setDeployXML(boolean deployXML) {
+
+        this.deployXML = deployXML;
+
+    }
+
+
+    /**
+     * Return the copy XML config file flag for this component.
+     */
+    public boolean isCopyXML() {
+
+        return (this.copyXML);
+
+    }
+
+
+    /**
+     * Set the copy XML config file flag for this component.
+     *
+     * @param copyXML The new copy XML flag
+     */
+    public void setCopyXML(boolean copyXML) {
+
+        this.copyXML= copyXML;
+
+    }
+    
+    
+    /**
+     * Return the Java class name of the error report valve class
+     * for new web applications.
+     */
+    public String getErrorReportValveClass() {
+
+        return (this.errorReportValveClass);
+
+    }
+
+
+    /**
+     * Set the Java class name of the error report valve class
+     * for new web applications.
+     *
+     * @param errorReportValveClass The new error report valve class
+     */
+    public void setErrorReportValveClass(String errorReportValveClass) {
+
+        String oldErrorReportValveClassClass = this.errorReportValveClass;
+        this.errorReportValveClass = errorReportValveClass;
+        support.firePropertyChange("errorReportValveClass",
+                                   oldErrorReportValveClassClass, 
+                                   this.errorReportValveClass);
+
+    }
+    
+    
+    /**
+     * Return the canonical, fully qualified, name of the virtual host
+     * this Container represents.
+     */
+    @Override
+    public String getName() {
+
+        return (name);
+
+    }
+
+
+    /**
+     * Set the canonical, fully qualified, name of the virtual host
+     * this Container represents.
+     *
+     * @param name Virtual host name
+     *
+     * @exception IllegalArgumentException if name is null
+     */
+    @Override
+    public void setName(String name) {
+
+        if (name == null)
+            throw new IllegalArgumentException
+                (sm.getString("standardHost.nullName"));
+
+        name = name.toLowerCase(Locale.ENGLISH);      // Internally all names are lower case
+
+        String oldName = this.name;
+        this.name = name;
+        support.firePropertyChange("name", oldName, this.name);
+
+    }
+
+
+    /**
+     * Unpack WARs flag accessor.
+     */
+    public boolean isUnpackWARs() {
+
+        return (unpackWARs);
+
+    }
+
+
+    /**
+     * Unpack WARs flag mutator.
+     */
+    public void setUnpackWARs(boolean unpackWARs) {
+
+        this.unpackWARs = unpackWARs;
+
+    }
+
+
+    /**
+     * Host work directory base.
+     */
+    public String getWorkDir() {
+
+        return (workDir);
+    }
+
+
+    /**
+     * Host work directory base.
+     */
+    public void setWorkDir(String workDir) {
+
+        this.workDir = workDir;
+    }
+
+
+    /**
+     * Return the regular expression that defines the files and directories in
+     * the host's {@link #appBase} that will be ignored by the automatic
+     * deployment process.
+     */
+    @Override
+    public String getDeployIgnore() {
+        if (deployIgnore == null) {
+            return null;
+        } 
+        return this.deployIgnore.toString();
+    }
+
+
+    /**
+     * Return the compiled regular expression that defines the files and
+     * directories in the host's {@link #appBase} that will be ignored by the
+     * automatic deployment process.
+     */
+    @Override
+    public Pattern getDeployIgnorePattern() {
+        return this.deployIgnore;
+    }
+
+
+    /**
+     * Set the regular expression that defines the files and directories in
+     * the host's {@link #appBase} that will be ignored by the automatic
+     * deployment process.
+     */
+    @Override
+    public void setDeployIgnore(String deployIgnore) {
+        String oldDeployIgnore;
+        if (this.deployIgnore == null) {
+            oldDeployIgnore = null;
+        } else {
+            oldDeployIgnore = this.deployIgnore.toString();
+        }
+        if (deployIgnore == null) {
+            this.deployIgnore = null;
+        } else {
+            this.deployIgnore = Pattern.compile(deployIgnore);
+        }
+        support.firePropertyChange("deployIgnore",
+                                   oldDeployIgnore, 
+                                   deployIgnore);
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add an alias name that should be mapped to this same Host.
+     *
+     * @param alias The alias to be added
+     */
+    @Override
+    public void addAlias(String alias) {
+
+        alias = alias.toLowerCase(Locale.ENGLISH);
+
+        synchronized (aliasesLock) {
+            // Skip duplicate aliases
+            for (int i = 0; i < aliases.length; i++) {
+                if (aliases[i].equals(alias))
+                    return;
+            }
+            // Add this alias to the list
+            String newAliases[] = new String[aliases.length + 1];
+            for (int i = 0; i < aliases.length; i++)
+                newAliases[i] = aliases[i];
+            newAliases[aliases.length] = alias;
+            aliases = newAliases;
+        }
+        // Inform interested listeners
+        fireContainerEvent(ADD_ALIAS_EVENT, alias);
+
+    }
+
+
+    /**
+     * Add a child Container, only if the proposed child is an implementation
+     * of Context.
+     *
+     * @param child Child container to be added
+     */
+    @Override
+    public void addChild(Container child) {
+
+        child.addLifecycleListener(new MemoryLeakTrackingListener());
+
+        if (!(child instanceof Context))
+            throw new IllegalArgumentException
+                (sm.getString("standardHost.notContext"));
+        super.addChild(child);
+
+    }
+
+
+    /**
+     * Used to ensure the regardless of {@link Context} implementation, a record
+     * is kept of the class loader used every time a context starts.
+     */
+    private class MemoryLeakTrackingListener implements LifecycleListener {
+        @Override
+        public void lifecycleEvent(LifecycleEvent event) {
+            if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
+                if (event.getSource() instanceof Context) {
+                    Context context = ((Context) event.getSource());
+                    childClassLoaders.put(context.getLoader().getClassLoader(),
+                            context.getServletContext().getContextPath());
+                }
+            }
+        }
+    }
+    
+    
+    /**
+     * Attempt to identify the contexts that have a class loader memory leak.
+     * This is usually triggered on context reload. Note: This method attempts
+     * to force a full garbage collection. This should be used with extreme
+     * caution on a production system.
+     */
+    public String[] findReloadedContextMemoryLeaks() {
+        
+        System.gc();
+        
+        List<String> result = new ArrayList<String>();
+        
+        for (Map.Entry<ClassLoader, String> entry :
+                childClassLoaders.entrySet()) {
+            ClassLoader cl = entry.getKey();
+            if (cl instanceof WebappClassLoader) {
+                if (!((WebappClassLoader) cl).isStarted()) {
+                    result.add(entry.getValue());
+                }
+            }
+        }
+        
+        return result.toArray(new String[result.size()]);
+    }
+
+    /**
+     * Return the set of alias names for this Host.  If none are defined,
+     * a zero length array is returned.
+     */
+    @Override
+    public String[] findAliases() {
+
+        synchronized (aliasesLock) {
+            return (this.aliases);
+        }
+
+    }
+
+
+    /**
+     * Return descriptive information about this Container implementation and
+     * the corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    /**
+     * Remove the specified alias name from the aliases for this Host.
+     *
+     * @param alias Alias name to be removed
+     */
+    @Override
+    public void removeAlias(String alias) {
+
+        alias = alias.toLowerCase(Locale.ENGLISH);
+
+        synchronized (aliasesLock) {
+
+            // Make sure this alias is currently present
+            int n = -1;
+            for (int i = 0; i < aliases.length; i++) {
+                if (aliases[i].equals(alias)) {
+                    n = i;
+                    break;
+                }
+            }
+            if (n < 0)
+                return;
+
+            // Remove the specified alias
+            int j = 0;
+            String results[] = new String[aliases.length - 1];
+            for (int i = 0; i < aliases.length; i++) {
+                if (i != n)
+                    results[j++] = aliases[i];
+            }
+            aliases = results;
+
+        }
+
+        // Inform interested listeners
+        fireContainerEvent(REMOVE_ALIAS_EVENT, alias);
+
+    }
+
+
+    /**
+     * Return a String representation of this component.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder();
+        if (getParent() != null) {
+            sb.append(getParent().toString());
+            sb.append(".");
+        }
+        sb.append("StandardHost[");
+        sb.append(getName());
+        sb.append("]");
+        return (sb.toString());
+
+    }
+    
+    /**
+     * 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 {
+        
+        // Set error report valve
+        String errorValve = getErrorReportValveClass();
+        if ((errorValve != null) && (!errorValve.equals(""))) {
+            try {
+                boolean found = false;
+                Valve[] valves = getPipeline().getValves();
+                for (Valve valve : valves) {
+                    if (errorValve.equals(valve.getClass().getName())) {
+                        found = true;
+                        break;
+                    }
+                }
+                if(!found) {
+                    Valve valve =
+                        (Valve) Class.forName(errorValve).newInstance();
+                    getPipeline().addValve(valve);
+                }
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                log.error(sm.getString(
+                        "standardHost.invalidErrorReportValveClass",
+                        errorValve), t);
+            }
+        }
+        super.startInternal();
+    }
+
+
+    // -------------------- JMX  --------------------
+    /**
+      * Return the MBean Names of the Valves associated with this Host
+      *
+      * @exception Exception if an MBean cannot be created or registered
+      */
+     public String [] getValveNames()
+         throws Exception
+    {
+         Valve [] valves = this.getPipeline().getValves();
+         String [] mbeanNames = new String[valves.length];
+         for (int i = 0; i < valves.length; i++) {
+             if( valves[i] == null ) continue;
+             if( ((ValveBase)valves[i]).getObjectName() == null ) continue;
+             mbeanNames[i] = ((ValveBase)valves[i]).getObjectName().toString();
+         }
+
+         return mbeanNames;
+
+     }
+
+    public String[] getAliases() {
+        synchronized (aliasesLock) {
+            return aliases;
+        }
+    }
+
+    @Override
+    protected String getObjectNameKeyProperties() {
+
+        StringBuilder keyProperties = new StringBuilder("type=Host");
+        keyProperties.append(MBeanUtils.getContainerKeyProperties(this));
+
+        return keyProperties.toString();
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardHostValve.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardHostValve.java
new file mode 100644
index 0000000..64ebb88
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardHostValve.java
@@ -0,0 +1,500 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+
+import java.io.IOException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Globals;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.comet.CometEvent;
+import org.apache.catalina.connector.ClientAbortException;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.deploy.ErrorPage;
+import org.apache.catalina.valves.ValveBase;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Valve that implements the default basic behavior for the
+ * <code>StandardHost</code> container implementation.
+ * <p>
+ * <b>USAGE CONSTRAINT</b>:  This implementation is likely to be useful only
+ * when processing HTTP requests.
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @version $Id: StandardHostValve.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+final class StandardHostValve
+    extends ValveBase {
+
+
+    private static final Log log = LogFactory.getLog(StandardHostValve.class);
+
+    protected static final boolean STRICT_SERVLET_COMPLIANCE;
+
+    protected static final boolean ACCESS_SESSION;
+
+    static {
+        STRICT_SERVLET_COMPLIANCE = Globals.STRICT_SERVLET_COMPLIANCE;
+        
+        String accessSession = System.getProperty(
+                "org.apache.catalina.core.StandardHostValve.ACCESS_SESSION");
+        if (accessSession == null) {
+            ACCESS_SESSION = STRICT_SERVLET_COMPLIANCE;
+        } else {
+            ACCESS_SESSION =
+                Boolean.valueOf(accessSession).booleanValue();
+        }
+    }
+
+    //------------------------------------------------------ Constructor
+    public StandardHostValve() {
+        super(true);
+    }
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The descriptive information related to this implementation.
+     */
+    private static final String info =
+        "org.apache.catalina.core.StandardHostValve/1.0";
+
+
+    /**
+     * The string manager for this package.
+     */
+    private static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return descriptive information about this Valve implementation.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Select the appropriate child Context to process this request,
+     * based on the specified request URI.  If no matching Context can
+     * be found, return an appropriate HTTP error.
+     *
+     * @param request Request to be processed
+     * @param response Response to be produced
+     *
+     * @exception IOException if an input/output error occurred
+     * @exception ServletException if a servlet error occurred
+     */
+    @Override
+    public final void invoke(Request request, Response response)
+        throws IOException, ServletException {
+
+        // Select the Context to be used for this Request
+        Context context = request.getContext();
+        if (context == null) {
+            response.sendError
+                (HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                 sm.getString("standardHost.noContext"));
+            return;
+        }
+
+        // Bind the context CL to the current thread
+        if( context.getLoader() != null ) {
+            // Not started - it should check for availability first
+            // This should eventually move to Engine, it's generic.
+            if (Globals.IS_SECURITY_ENABLED) {
+                PrivilegedAction<Void> pa = new PrivilegedSetTccl(
+                        context.getLoader().getClassLoader());
+                AccessController.doPrivileged(pa);                
+            } else {
+                Thread.currentThread().setContextClassLoader
+                        (context.getLoader().getClassLoader());
+            }
+        }
+        if (request.isAsyncSupported()) {
+            request.setAsyncSupported(context.getPipeline().isAsyncSupported());
+        }
+
+
+        // Ask this Context to process this request
+        context.getPipeline().getFirst().invoke(request, response);
+
+        // Access a session (if present) to update last accessed time, based on a
+        // strict interpretation of the specification
+        if (ACCESS_SESSION) {
+            request.getSession(false);
+        }
+
+        // Error page processing
+        response.setSuspended(false);
+
+        Throwable t = (Throwable) request.getAttribute(
+                RequestDispatcher.ERROR_EXCEPTION);
+
+        if (t != null) {
+            throwable(request, response, t);
+        } else {
+            status(request, response);
+        }
+
+        // Restore the context classloader
+        if (Globals.IS_SECURITY_ENABLED) {
+            PrivilegedAction<Void> pa = new PrivilegedSetTccl(
+                    StandardHostValve.class.getClassLoader());
+            AccessController.doPrivileged(pa);                
+        } else {
+            Thread.currentThread().setContextClassLoader
+                    (StandardHostValve.class.getClassLoader());
+        }
+
+    }
+
+
+    /**
+     * Process Comet event.
+     *
+     * @param request Request to be processed
+     * @param response Response to be produced
+     * @param event the event
+     *
+     * @exception IOException if an input/output error occurred
+     * @exception ServletException if a servlet error occurred
+     */
+    @Override
+    public final void event(Request request, Response response, CometEvent event)
+        throws IOException, ServletException {
+
+        // Select the Context to be used for this Request
+        Context context = request.getContext();
+
+        // Bind the context CL to the current thread
+        if( context.getLoader() != null ) {
+            // Not started - it should check for availability first
+            // This should eventually move to Engine, it's generic.
+            Thread.currentThread().setContextClassLoader
+                    (context.getLoader().getClassLoader());
+        }
+
+        // Ask this Context to process this request
+        context.getPipeline().getFirst().event(request, response, event);
+
+        // Access a session (if present) to update last accessed time, based on a
+        // strict interpretation of the specification
+        if (ACCESS_SESSION) {
+            request.getSession(false);
+        }
+
+        // Error page processing
+        response.setSuspended(false);
+
+        Throwable t = (Throwable) request.getAttribute(
+                RequestDispatcher.ERROR_EXCEPTION);
+
+        if (t != null) {
+            throwable(request, response, t);
+        } else {
+            status(request, response);
+        }
+
+        // Restore the context classloader
+        Thread.currentThread().setContextClassLoader
+            (StandardHostValve.class.getClassLoader());
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Handle the specified Throwable encountered while processing
+     * the specified Request to produce the specified Response.  Any
+     * exceptions that occur during generation of the exception report are
+     * logged and swallowed.
+     *
+     * @param request The request being processed
+     * @param response The response being generated
+     * @param throwable The exception that occurred (which possibly wraps
+     *  a root cause exception
+     */
+    protected void throwable(Request request, Response response,
+                             Throwable throwable) {
+        Context context = request.getContext();
+        if (context == null)
+            return;
+
+        Throwable realError = throwable;
+
+        if (realError instanceof ServletException) {
+            realError = ((ServletException) realError).getRootCause();
+            if (realError == null) {
+                realError = throwable;
+            }
+        }
+
+        // If this is an aborted request from a client just log it and return
+        if (realError instanceof ClientAbortException ) {
+            if (log.isDebugEnabled()) {
+                log.debug
+                    (sm.getString("standardHost.clientAbort",
+                        realError.getCause().getMessage()));
+            }
+            return;
+        }
+
+        ErrorPage errorPage = findErrorPage(context, throwable);
+        if ((errorPage == null) && (realError != throwable)) {
+            errorPage = findErrorPage(context, realError);
+        }
+
+        if (errorPage != null) {
+            response.setAppCommitted(false);
+            request.setAttribute
+                (ApplicationFilterFactory.DISPATCHER_REQUEST_PATH_ATTR,
+                 errorPage.getLocation());
+            request.setAttribute(ApplicationFilterFactory.DISPATCHER_TYPE_ATTR,
+                              DispatcherType.ERROR);
+            request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,
+                    new Integer(HttpServletResponse.SC_INTERNAL_SERVER_ERROR));
+            request.setAttribute(RequestDispatcher.ERROR_MESSAGE,
+                              throwable.getMessage());
+            request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,
+                              realError);
+            Wrapper wrapper = request.getWrapper();
+            if (wrapper != null)
+                request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME,
+                                  wrapper.getName());
+            request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI,
+                                 request.getRequestURI());
+            request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,
+                              realError.getClass());
+            if (custom(request, response, errorPage)) {
+                try {
+                    response.flushBuffer();
+                } catch (IOException e) {
+                    container.getLogger().warn("Exception Processing " + errorPage, e);
+                }
+            }
+        } else {
+            // A custom error-page has not been defined for the exception
+            // that was thrown during request processing. Check if an
+            // error-page for error code 500 was specified and if so,
+            // send that page back as the response.
+            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            // The response is an error
+            response.setError();
+
+            status(request, response);
+        }
+
+
+    }
+
+
+    /**
+     * Handle the HTTP status code (and corresponding message) generated
+     * while processing the specified Request to produce the specified
+     * Response.  Any exceptions that occur during generation of the error
+     * report are logged and swallowed.
+     *
+     * @param request The request being processed
+     * @param response The response being generated
+     */
+    protected void status(Request request, Response response) {
+
+        int statusCode = response.getStatus();
+
+        // Handle a custom error page for this status code
+        Context context = request.getContext();
+        if (context == null)
+            return;
+
+        /* Only look for error pages when isError() is set.
+         * isError() is set when response.sendError() is invoked. This
+         * allows custom error pages without relying on default from
+         * web.xml.
+         */
+        if (!response.isError())
+            return;
+
+        ErrorPage errorPage = context.findErrorPage(statusCode);
+        if (errorPage != null) {
+            response.setAppCommitted(false);
+            request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,
+                              Integer.valueOf(statusCode));
+
+            String message = response.getMessage();
+            if (message == null)
+                message = "";
+            request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message);
+            request.setAttribute
+                (ApplicationFilterFactory.DISPATCHER_REQUEST_PATH_ATTR,
+                 errorPage.getLocation());
+            request.setAttribute(ApplicationFilterFactory.DISPATCHER_TYPE_ATTR,
+                              DispatcherType.ERROR);
+
+
+            Wrapper wrapper = request.getWrapper();
+            if (wrapper != null)
+                request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME,
+                                  wrapper.getName());
+            request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI,
+                                 request.getRequestURI());
+            if (custom(request, response, errorPage)) {
+                try {
+                    response.flushBuffer();
+                } catch (ClientAbortException e) {
+                    // Ignore
+                } catch (IOException e) {
+                    container.getLogger().warn("Exception Processing " + errorPage, e);
+                }
+            }
+        }
+
+    }
+
+
+    /**
+     * Find and return the ErrorPage instance for the specified exception's
+     * class, or an ErrorPage instance for the closest superclass for which
+     * there is such a definition.  If no associated ErrorPage instance is
+     * found, return <code>null</code>.
+     *
+     * @param context The Context in which to search
+     * @param exception The exception for which to find an ErrorPage
+     */
+    protected static ErrorPage findErrorPage
+        (Context context, Throwable exception) {
+
+        if (exception == null)
+            return (null);
+        Class<?> clazz = exception.getClass();
+        String name = clazz.getName();
+        while (!Object.class.equals(clazz)) {
+            ErrorPage errorPage = context.findErrorPage(name);
+            if (errorPage != null)
+                return (errorPage);
+            clazz = clazz.getSuperclass();
+            if (clazz == null)
+                break;
+            name = clazz.getName();
+        }
+        return (null);
+
+    }
+
+
+    /**
+     * Handle an HTTP status code or Java exception by forwarding control
+     * to the location included in the specified errorPage object.  It is
+     * assumed that the caller has already recorded any request attributes
+     * that are to be forwarded to this page.  Return <code>true</code> if
+     * we successfully utilized the specified error page location, or
+     * <code>false</code> if the default error report should be rendered.
+     *
+     * @param request The request being processed
+     * @param response The response being generated
+     * @param errorPage The errorPage directive we are obeying
+     */
+    protected boolean custom(Request request, Response response,
+                             ErrorPage errorPage) {
+
+        if (container.getLogger().isDebugEnabled())
+            container.getLogger().debug("Processing " + errorPage);
+
+        request.setPathInfo(errorPage.getLocation());
+
+        try {
+            // Forward control to the specified location
+            ServletContext servletContext =
+                request.getContext().getServletContext();
+            RequestDispatcher rd =
+                servletContext.getRequestDispatcher(errorPage.getLocation());
+
+            if (response.isCommitted()) {
+                // Response is committed - including the error page is the
+                // best we can do 
+                rd.include(request.getRequest(), response.getResponse());
+            } else {
+                // Reset the response (keeping the real error code and message)
+                response.resetBuffer(true);
+
+                rd.forward(request.getRequest(), response.getResponse());
+
+                // If we forward, the response is suspended again
+                response.setSuspended(false);
+            }
+
+            // Indicate that we have successfully processed this custom page
+            return (true);
+
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            // Report our failure to process this custom page
+            container.getLogger().error("Exception Processing " + errorPage, t);
+            return (false);
+
+        }
+
+    }
+
+    
+    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/catalina/core/StandardPipeline.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardPipeline.java
new file mode 100644
index 0000000..db1a8b9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardPipeline.java
@@ -0,0 +1,478 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+
+import java.util.ArrayList;
+
+import javax.management.ObjectName;
+
+import org.apache.catalina.Contained;
+import org.apache.catalina.Container;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.Pipeline;
+import org.apache.catalina.Valve;
+import org.apache.catalina.util.LifecycleBase;
+import org.apache.catalina.valves.ValveBase;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+
+
+/**
+ * Standard implementation of a processing <b>Pipeline</b> that will invoke
+ * a series of Valves that have been configured to be called in order.  This
+ * implementation can be used for any type of Container.
+ *
+ * <b>IMPLEMENTATION WARNING</b> - This implementation assumes that no
+ * calls to <code>addValve()</code> or <code>removeValve</code> are allowed
+ * while a request is currently being processed.  Otherwise, the mechanism
+ * by which per-thread state is maintained will need to be modified.
+ *
+ * @author Craig R. McClanahan
+ */
+
+public class StandardPipeline extends LifecycleBase
+        implements Pipeline, Contained {
+
+    private static final Log log = LogFactory.getLog(StandardPipeline.class);
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new StandardPipeline instance with no associated Container.
+     */
+    public StandardPipeline() {
+
+        this(null);
+
+    }
+
+
+    /**
+     * Construct a new StandardPipeline instance that is associated with the
+     * specified Container.
+     *
+     * @param container The container we should be associated with
+     */
+    public StandardPipeline(Container container) {
+
+        super();
+        setContainer(container);
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The basic Valve (if any) associated with this Pipeline.
+     */
+    protected Valve basic = null;
+
+
+    /**
+     * The Container with which this Pipeline is associated.
+     */
+    protected Container container = null;
+
+
+    /**
+     * Descriptive information about this implementation.
+     */
+    protected static final String info = "org.apache.catalina.core.StandardPipeline/1.0";
+
+
+    /**
+     * The first valve associated with this Pipeline.
+     */
+    protected Valve first = null;
+    
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return descriptive information about this implementation class.
+     */
+    public String getInfo() {
+
+        return info;
+
+    }
+    
+    @Override
+    public boolean isAsyncSupported() {
+        Valve valve = (first!=null)?first:basic;
+        boolean supported = true;
+        while (supported && valve!=null) {
+            supported = supported & valve.isAsyncSupported();
+            valve = valve.getNext();
+        }
+        return supported; 
+    }
+
+
+    // ------------------------------------------------------ Contained Methods
+
+
+    /**
+     * Return the Container with which this Pipeline is associated.
+     */
+    @Override
+    public Container getContainer() {
+
+        return (this.container);
+
+    }
+
+
+    /**
+     * Set the Container with which this Pipeline is associated.
+     *
+     * @param container The new associated container
+     */
+    @Override
+    public void setContainer(Container container) {
+
+        this.container = container;
+
+    }
+
+
+    @Override
+    protected void initInternal() {
+        // NOOP
+    }
+
+    
+    /**
+     * Start {@link Valve}s) in this pipeline and implement the requirements
+     * of {@link 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 {
+
+        // Start the Valves in our pipeline (including the basic), if any
+        Valve current = first;
+        if (current == null) {
+            current = basic;
+        }
+        while (current != null) {
+            if (current instanceof Lifecycle)
+                ((Lifecycle) current).start();
+            current = current.getNext();
+        }
+
+        setState(LifecycleState.STARTING);
+    }
+
+
+    /**
+     * Stop {@link Valve}s) in this pipeline and implement the requirements
+     * of {@link 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 {
+
+        setState(LifecycleState.STOPPING);
+
+        // Stop the Valves in our pipeline (including the basic), if any
+        Valve current = first;
+        if (current == null) {
+            current = basic;
+        }
+        while (current != null) {
+            if (current instanceof Lifecycle)
+                ((Lifecycle) current).stop();
+            current = current.getNext();
+        }
+    }
+
+    
+    @Override
+    protected void destroyInternal() {
+        Valve[] valves = getValves();
+        for (Valve valve : valves) {
+            removeValve(valve);
+        }
+    }
+
+    
+    /**
+     * Return a String representation of this component.
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder("Pipeline[");
+        sb.append(container);
+        sb.append(']');
+        return sb.toString();
+    }
+
+
+    // ------------------------------------------------------- Pipeline Methods
+
+
+    /**
+     * <p>Return the Valve instance that has been distinguished as the basic
+     * Valve for this Pipeline (if any).
+     */
+    @Override
+    public Valve getBasic() {
+
+        return (this.basic);
+
+    }
+
+
+    /**
+     * <p>Set the Valve instance that has been distinguished as the basic
+     * Valve for this Pipeline (if any).  Prior to setting the basic Valve,
+     * the Valve's <code>setContainer()</code> will be called, if it
+     * implements <code>Contained</code>, with the owning Container as an
+     * argument.  The method may throw an <code>IllegalArgumentException</code>
+     * if this Valve chooses not to be associated with this Container, or
+     * <code>IllegalStateException</code> if it is already associated with
+     * a different Container.</p>
+     *
+     * @param valve Valve to be distinguished as the basic Valve
+     */
+    @Override
+    public void setBasic(Valve valve) {
+
+        // Change components if necessary
+        Valve oldBasic = this.basic;
+        if (oldBasic == valve)
+            return;
+
+        // Stop the old component if necessary
+        if (oldBasic != null) {
+            if (getState().isAvailable() && (oldBasic instanceof Lifecycle)) {
+                try {
+                    ((Lifecycle) oldBasic).stop();
+                } catch (LifecycleException e) {
+                    log.error("StandardPipeline.setBasic: stop", e);
+                }
+            }
+            if (oldBasic instanceof Contained) {
+                try {
+                    ((Contained) oldBasic).setContainer(null);
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                }
+            }
+        }
+
+        // Start the new component if necessary
+        if (valve == null)
+            return;
+        if (valve instanceof Contained) {
+            ((Contained) valve).setContainer(this.container);
+        }
+        if (getState().isAvailable() && valve instanceof Lifecycle) {
+            try {
+                ((Lifecycle) valve).start();
+            } catch (LifecycleException e) {
+                log.error("StandardPipeline.setBasic: start", e);
+                return;
+            }
+        }
+
+        // Update the pipeline
+        Valve current = first;
+        while (current != null) {
+            if (current.getNext() == oldBasic) {
+                current.setNext(valve);
+                break;
+            }
+            current = current.getNext();
+        }
+        
+        this.basic = valve;
+
+    }
+
+
+    /**
+     * <p>Add a new Valve to the end of the pipeline associated with this
+     * Container.  Prior to adding the Valve, the Valve's
+     * <code>setContainer()</code> method will be called, if it implements
+     * <code>Contained</code>, with the owning Container as an argument.
+     * The method may throw an
+     * <code>IllegalArgumentException</code> if this Valve chooses not to
+     * be associated with this Container, or <code>IllegalStateException</code>
+     * if it is already associated with a different Container.</p>
+     *
+     * @param valve Valve to be added
+     *
+     * @exception IllegalArgumentException if this Container refused to
+     *  accept the specified Valve
+     * @exception IllegalArgumentException if the specified Valve refuses to be
+     *  associated with this Container
+     * @exception IllegalStateException if the specified Valve is already
+     *  associated with a different Container
+     */
+    @Override
+    public void addValve(Valve valve) {
+    
+        // Validate that we can add this Valve
+        if (valve instanceof Contained)
+            ((Contained) valve).setContainer(this.container);
+
+        // Start the new component if necessary
+        if (getState().isAvailable()) {
+            if (valve instanceof Lifecycle) {
+                try {
+                    ((Lifecycle) valve).start();
+                } catch (LifecycleException e) {
+                    log.error("StandardPipeline.addValve: start: ", e);
+                }
+            }
+        }
+
+        // Add this Valve to the set associated with this Pipeline
+        if (first == null) {
+            first = valve;
+            valve.setNext(basic);
+        } else {
+            Valve current = first;
+            while (current != null) {
+                if (current.getNext() == basic) {
+                    current.setNext(valve);
+                    valve.setNext(basic);
+                    break;
+                }
+                current = current.getNext();
+            }
+        }
+        
+        container.fireContainerEvent(Container.ADD_VALVE_EVENT, valve);
+    }
+
+
+    /**
+     * Return the set of Valves in the pipeline associated with this
+     * Container, including the basic Valve (if any).  If there are no
+     * such Valves, a zero-length array is returned.
+     */
+    @Override
+    public Valve[] getValves() {
+
+        ArrayList<Valve> valveList = new ArrayList<Valve>();
+        Valve current = first;
+        if (current == null) {
+            current = basic;
+        }
+        while (current != null) {
+            valveList.add(current);
+            current = current.getNext();
+        }
+
+        return valveList.toArray(new Valve[0]);
+
+    }
+
+    public ObjectName[] getValveObjectNames() {
+
+        ArrayList<ObjectName> valveList = new ArrayList<ObjectName>();
+        Valve current = first;
+        if (current == null) {
+            current = basic;
+        }
+        while (current != null) {
+            if (current instanceof ValveBase) {
+                valveList.add(((ValveBase) current).getObjectName());
+            }
+            current = current.getNext();
+        }
+
+        return valveList.toArray(new ObjectName[0]);
+
+    }
+
+    /**
+     * Remove the specified Valve from the pipeline associated with this
+     * Container, if it is found; otherwise, do nothing.  If the Valve is
+     * found and removed, the Valve's <code>setContainer(null)</code> method
+     * will be called if it implements <code>Contained</code>.
+     *
+     * @param valve Valve to be removed
+     */
+    @Override
+    public void removeValve(Valve valve) {
+
+        Valve current;
+        if(first == valve) {
+            first = first.getNext();
+            current = null;
+        } else {
+            current = first;
+        }
+        while (current != null) {
+            if (current.getNext() == valve) {
+                current.setNext(valve.getNext());
+                break;
+            }
+            current = current.getNext();
+        }
+
+        if (first == basic) first = null;
+
+        if (valve instanceof Contained)
+            ((Contained) valve).setContainer(null);
+
+        // Stop this valve if necessary
+        if (getState().isAvailable()) {
+            if (valve instanceof Lifecycle) {
+                try {
+                    ((Lifecycle) valve).stop();
+                } catch (LifecycleException e) {
+                    log.error("StandardPipeline.removeValve: stop: ", e);
+                }
+            }
+        }
+        try {
+            ((Lifecycle) valve).destroy();
+        } catch (LifecycleException e) {
+            log.error("StandardPipeline.removeValve: destroy: ", e);
+        }
+        
+        container.fireContainerEvent(Container.REMOVE_VALVE_EVENT, valve);
+    }
+
+
+    @Override
+    public Valve getFirst() {
+        if (first != null) {
+            return first;
+        }
+        
+        return basic;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardServer.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardServer.java
new file mode 100644
index 0000000..32daa52
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardServer.java
@@ -0,0 +1,860 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.security.AccessControlException;
+import java.util.Random;
+
+import javax.management.ObjectName;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.Server;
+import org.apache.catalina.Service;
+import org.apache.catalina.deploy.NamingResources;
+import org.apache.catalina.mbeans.MBeanFactory;
+import org.apache.catalina.mbeans.MBeanUtils;
+import org.apache.catalina.startup.Catalina;
+import org.apache.catalina.util.LifecycleMBeanBase;
+import org.apache.catalina.util.ServerInfo;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.buf.StringCache;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Standard implementation of the <b>Server</b> interface, available for use
+ * (but not required) when deploying and starting Catalina.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: StandardServer.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+public final class StandardServer extends LifecycleMBeanBase implements Server {
+
+    private static final Log log = LogFactory.getLog(StandardServer.class);
+   
+
+    // ------------------------------------------------------------ Constructor
+
+
+    /**
+     * Construct a default instance of this class.
+     */
+    public StandardServer() {
+
+        super();
+
+        globalNamingResources = new NamingResources();
+        globalNamingResources.setContainer(this);
+
+        if (isUseNaming()) {
+            if (namingContextListener == null) {
+                namingContextListener = new NamingContextListener();
+                addLifecycleListener(namingContextListener);
+            }
+        }
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Global naming resources context.
+     */
+    private javax.naming.Context globalNamingContext = null;
+
+
+    /**
+     * Global naming resources.
+     */
+    private NamingResources globalNamingResources = null;
+
+
+    /**
+     * Descriptive information about this Server implementation.
+     */
+    private static final String info =
+        "org.apache.catalina.core.StandardServer/1.0";
+
+
+    /**
+     * The naming context listener for this web application.
+     */
+    private NamingContextListener namingContextListener = null;
+
+
+    /**
+     * The port number on which we wait for shutdown commands.
+     */
+    private int port = 8005;
+
+    /**
+     * The address on which we wait for shutdown commands.
+     */
+    private String address = "localhost";
+
+
+    /**
+     * A random number generator that is <strong>only</strong> used if
+     * the shutdown command string is longer than 1024 characters.
+     */
+    private Random random = null;
+
+
+    /**
+     * The set of Services associated with this Server.
+     */
+    private Service services[] = new Service[0];
+
+
+    /**
+     * The shutdown command string we are looking for.
+     */
+    private String shutdown = "SHUTDOWN";
+
+
+    /**
+     * The string manager for this package.
+     */
+    private static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
+     * The property change support for this component.
+     */
+    protected PropertyChangeSupport support = new PropertyChangeSupport(this);
+
+    private volatile boolean stopAwait = false;
+    
+    private Catalina catalina = null;
+
+    private ClassLoader parentClassLoader = null;
+
+    /**
+     * Thread that currently is inside our await() method.
+     */
+    private volatile Thread awaitThread = null;
+
+    /**
+     * Server socket that is used to wait for the shutdown command.
+     */
+    private volatile ServerSocket awaitSocket = null;
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the global naming resources context.
+     */
+    @Override
+    public javax.naming.Context getGlobalNamingContext() {
+
+        return (this.globalNamingContext);
+
+    }
+
+
+    /**
+     * Set the global naming resources context.
+     *
+     * @param globalNamingContext The new global naming resource context
+     */
+    public void setGlobalNamingContext
+        (javax.naming.Context globalNamingContext) {
+
+        this.globalNamingContext = globalNamingContext;
+
+    }
+
+
+    /**
+     * Return the global naming resources.
+     */
+    @Override
+    public NamingResources getGlobalNamingResources() {
+
+        return (this.globalNamingResources);
+
+    }
+
+
+    /**
+     * Set the global naming resources.
+     *
+     * @param globalNamingResources The new global naming resources
+     */
+    @Override
+    public void setGlobalNamingResources
+        (NamingResources globalNamingResources) {
+
+        NamingResources oldGlobalNamingResources =
+            this.globalNamingResources;
+        this.globalNamingResources = globalNamingResources;
+        this.globalNamingResources.setContainer(this);
+        support.firePropertyChange("globalNamingResources",
+                                   oldGlobalNamingResources,
+                                   this.globalNamingResources);
+
+    }
+
+
+    /**
+     * Return descriptive information about this Server implementation and
+     * the corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+    /**
+     * Report the current Tomcat Server Release number
+     * @return Tomcat release identifier
+     */
+    public String getServerInfo() {
+
+        return ServerInfo.getServerInfo();
+    }
+
+    /**
+     * Return the port number we listen to for shutdown commands.
+     */
+    @Override
+    public int getPort() {
+
+        return (this.port);
+
+    }
+
+
+    /**
+     * Set the port number we listen to for shutdown commands.
+     *
+     * @param port The new port number
+     */
+    @Override
+    public void setPort(int port) {
+
+        this.port = port;
+
+    }
+
+
+    /**
+     * Return the address on which we listen to for shutdown commands.
+     */
+    @Override
+    public String getAddress() {
+
+        return (this.address);
+
+    }
+
+
+    /**
+     * Set the address on which we listen to for shutdown commands.
+     *
+     * @param address The new address
+     */
+    @Override
+    public void setAddress(String address) {
+
+        this.address = address;
+
+    }
+
+    /**
+     * Return the shutdown command string we are waiting for.
+     */
+    @Override
+    public String getShutdown() {
+
+        return (this.shutdown);
+
+    }
+
+
+    /**
+     * Set the shutdown command we are waiting for.
+     *
+     * @param shutdown The new shutdown command
+     */
+    @Override
+    public void setShutdown(String shutdown) {
+
+        this.shutdown = shutdown;
+
+    }
+
+
+    /**
+     * Return the outer Catalina startup/shutdown component if present.
+     */
+    @Override
+    public Catalina getCatalina() {
+        return catalina;
+    }
+    
+    
+    /**
+     * Set the outer Catalina startup/shutdown component if present.
+     */
+    @Override
+    public void setCatalina(Catalina catalina) {
+        this.catalina = catalina;
+    }
+    
+    // --------------------------------------------------------- Server Methods
+
+
+    /**
+     * Add a new Service to the set of defined Services.
+     *
+     * @param service The Service to be added
+     */
+    @Override
+    public void addService(Service service) {
+
+        service.setServer(this);
+
+        synchronized (services) {
+            Service results[] = new Service[services.length + 1];
+            System.arraycopy(services, 0, results, 0, services.length);
+            results[services.length] = service;
+            services = results;
+
+            if (getState().isAvailable()) {
+                try {
+                    service.start();
+                } catch (LifecycleException e) {
+                    // Ignore
+                }
+            }
+
+            // Report this property change to interested listeners
+            support.firePropertyChange("service", null, service);
+        }
+
+    }
+
+    public void stopAwait() {
+        stopAwait=true;
+        Thread t = awaitThread;
+        if (t != null) {
+            ServerSocket s = awaitSocket;
+            if (s != null) {
+                awaitSocket = null;
+                try {
+                    s.close();
+                } catch (IOException e) {
+                    // Ignored
+                }
+            }
+            t.interrupt();
+            try {
+                t.join(1000);
+            } catch (InterruptedException e) {
+                // Ignored
+            }
+        }
+    }
+
+    /**
+     * Wait until a proper shutdown command is received, then return.
+     * This keeps the main thread alive - the thread pool listening for http 
+     * connections is daemon threads.
+     */
+    @Override
+    public void await() {
+        // Negative values - don't wait on port - tomcat is embedded or we just don't like ports
+        if( port == -2 ) {
+            // undocumented yet - for embedding apps that are around, alive.
+            return;
+        }
+        if( port==-1 ) {
+            try {
+                awaitThread = Thread.currentThread();
+                while(!stopAwait) {
+                    try {
+                        Thread.sleep( 10000 );
+                    } catch( InterruptedException ex ) {
+                        // continue and check the flag
+                    }
+                }
+            } finally {
+                awaitThread = null;
+            }
+            return;
+        }
+
+        // Set up a server socket to wait on
+        try {
+            awaitSocket = new ServerSocket(port, 1,
+                    InetAddress.getByName(address));
+        } catch (IOException e) {
+            log.error("StandardServer.await: create[" + address
+                               + ":" + port
+                               + "]: ", e);
+            return;
+        }
+
+        try {
+            awaitThread = Thread.currentThread();
+
+            // Loop waiting for a connection and a valid command
+            while (!stopAwait) {
+                ServerSocket serverSocket = awaitSocket;
+                if (serverSocket == null) {
+                    break;
+                }
+    
+                // Wait for the next connection
+                Socket socket = null;
+                StringBuilder command = new StringBuilder();
+                try {
+                    InputStream stream;
+                    try {
+                        socket = serverSocket.accept();
+                        socket.setSoTimeout(10 * 1000);  // Ten seconds
+                        stream = socket.getInputStream();
+                    } catch (AccessControlException ace) {
+                        log.warn("StandardServer.accept security exception: "
+                                + ace.getMessage(), ace);
+                        continue;
+                    } catch (IOException e) {
+                        if (stopAwait) {
+                            // Wait was aborted with socket.close()
+                            break;
+                        }
+                        log.error("StandardServer.await: accept: ", e);
+                        break;
+                    }
+
+                    // Read a set of characters from the socket
+                    int expected = 1024; // Cut off to avoid DoS attack
+                    while (expected < shutdown.length()) {
+                        if (random == null)
+                            random = new Random();
+                        expected += (random.nextInt() % 1024);
+                    }
+                    while (expected > 0) {
+                        int ch = -1;
+                        try {
+                            ch = stream.read();
+                        } catch (IOException e) {
+                            log.warn("StandardServer.await: read: ", e);
+                            ch = -1;
+                        }
+                        if (ch < 32)  // Control character or EOF terminates loop
+                            break;
+                        command.append((char) ch);
+                        expected--;
+                    }
+                } finally {
+                    // Close the socket now that we are done with it
+                    try {
+                        if (socket != null) {
+                            socket.close();
+                        }
+                    } catch (IOException e) {
+                        // Ignore
+                    }
+                }
+
+                // Match against our command string
+                boolean match = command.toString().equals(shutdown);
+                if (match) {
+                    log.info(sm.getString("standardServer.shutdownViaPort"));
+                    break;
+                } else
+                    log.warn("StandardServer.await: Invalid command '"
+                            + command.toString() + "' received");
+            }
+        } finally {
+            ServerSocket serverSocket = awaitSocket;
+            awaitThread = null;
+            awaitSocket = null;
+
+            // Close the server socket and return
+            if (serverSocket != null) {
+                try {
+                    serverSocket.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Return the specified Service (if it exists); otherwise return
+     * <code>null</code>.
+     *
+     * @param name Name of the Service to be returned
+     */
+    @Override
+    public Service findService(String name) {
+
+        if (name == null) {
+            return (null);
+        }
+        synchronized (services) {
+            for (int i = 0; i < services.length; i++) {
+                if (name.equals(services[i].getName())) {
+                    return (services[i]);
+                }
+            }
+        }
+        return (null);
+
+    }
+
+
+    /**
+     * Return the set of Services defined within this Server.
+     */
+    @Override
+    public Service[] findServices() {
+
+        return (services);
+
+    }
+    
+    /** 
+     * Return the JMX service names.
+     */
+    public ObjectName[] getServiceNames() {
+        ObjectName onames[]=new ObjectName[ services.length ];
+        for( int i=0; i<services.length; i++ ) {
+            onames[i]=((StandardService)services[i]).getObjectName();
+        }
+        return onames;
+    }
+
+
+    /**
+     * Remove the specified Service from the set associated from this
+     * Server.
+     *
+     * @param service The Service to be removed
+     */
+    @Override
+    public void removeService(Service service) {
+
+        synchronized (services) {
+            int j = -1;
+            for (int i = 0; i < services.length; i++) {
+                if (service == services[i]) {
+                    j = i;
+                    break;
+                }
+            }
+            if (j < 0)
+                return;
+            try {
+                services[j].stop();
+            } catch (LifecycleException e) {
+                // Ignore
+            }
+            int k = 0;
+            Service results[] = new Service[services.length - 1];
+            for (int i = 0; i < services.length; i++) {
+                if (i != j)
+                    results[k++] = services[i];
+            }
+            services = results;
+
+            // Report this property change to interested listeners
+            support.firePropertyChange("service", service, null);
+        }
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add a property change listener to this component.
+     *
+     * @param listener The listener to add
+     */
+    public void addPropertyChangeListener(PropertyChangeListener listener) {
+
+        support.addPropertyChangeListener(listener);
+
+    }
+
+
+    /**
+     * Remove a property change listener from this component.
+     *
+     * @param listener The listener to remove
+     */
+    public void removePropertyChangeListener(PropertyChangeListener listener) {
+
+        support.removePropertyChangeListener(listener);
+
+    }
+
+
+    /**
+     * Return a String representation of this component.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("StandardServer[");
+        sb.append(getPort());
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+    /**
+     * Write the configuration information for this entire <code>Server</code>
+     * out to the server.xml configuration file.
+     *
+     * @exception   javax.management.InstanceNotFoundException
+     *              if the managed resource object cannot be found
+     * @exception   javax.management.MBeanException
+     *              if the initializer of the object throws an exception, or
+     *              persistence is not supported
+     * @exception   javax.management.RuntimeOperationsException
+     *              if an exception is reported by the persistence mechanism
+     */
+    public synchronized void storeConfig() throws Exception {
+        ObjectName sname = new ObjectName("Catalina:type=StoreConfig");
+        mserver.invoke(sname, "storeConfig", null, null);            
+    }
+
+
+    /**
+     * Write the configuration information for <code>Context</code>
+     * out to the specified configuration file.
+     *
+     * @exception javax.management.InstanceNotFoundException if the managed resource object
+     *  cannot be found
+     * @exception javax.management.MBeanException if the initializer of the object throws
+     *  an exception, or persistence is not supported
+     * @exception javax.management.RuntimeOperationsException if an exception is reported
+     *  by the persistence mechanism
+     */
+    public synchronized void storeContext(Context context) throws Exception {
+        
+        ObjectName sname = null;    
+        try {
+           sname = new ObjectName("Catalina:type=StoreConfig");
+           if(mserver.isRegistered(sname)) {
+               mserver.invoke(sname, "store",
+                   new Object[] {context}, 
+                   new String [] { "java.lang.String"});
+           } else
+               log.error("StoreConfig mbean not registered" + sname);
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.error(t);
+        }
+ 
+    }
+
+
+    /**
+     * Return true if naming should be used.
+     */
+    private boolean isUseNaming() {
+        boolean useNaming = true;
+        // Reading the "catalina.useNaming" environment variable
+        String useNamingProperty = System.getProperty("catalina.useNaming");
+        if ((useNamingProperty != null)
+            && (useNamingProperty.equals("false"))) {
+            useNaming = false;
+        }
+        return useNaming;
+    }
+
+
+    /**
+     * Start nested components ({@link Service}s) 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 {
+
+        fireLifecycleEvent(CONFIGURE_START_EVENT, null);
+        setState(LifecycleState.STARTING);
+
+        globalNamingResources.start();
+        
+        // Start our defined Services
+        synchronized (services) {
+            for (int i = 0; i < services.length; i++) {
+                services[i].start();
+            }
+        }
+    }
+
+
+    /**
+     * Stop nested components ({@link Service}s) and implement the requirements
+     * of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
+     *
+     * @exception LifecycleException if this component detects a fatal error
+     *  that needs to be reported
+     */
+    @Override
+    protected void stopInternal() throws LifecycleException {
+
+        setState(LifecycleState.STOPPING);
+        fireLifecycleEvent(CONFIGURE_STOP_EVENT, null);
+        
+        // Stop our defined Services
+        for (int i = 0; i < services.length; i++) {
+            services[i].stop();
+        }
+
+        globalNamingResources.stop();
+        
+        stopAwait();
+    }
+
+    /**
+     * Invoke a pre-startup initialization. This is used to allow connectors
+     * to bind to restricted ports under Unix operating environments.
+     */
+    @Override
+    protected void initInternal() throws LifecycleException {
+        
+        super.initInternal();
+
+        // Register global String cache
+        // Note although the cache is global, if there are multiple Servers
+        // present in the JVM (may happen when embedding) then the same cache
+        // will be registered under multiple names
+        onameStringCache = register(new StringCache(), "type=StringCache");
+
+        // Register the MBeanFactory
+        MBeanFactory factory = new MBeanFactory();
+        factory.setContainer(this);
+        onameMBeanFactory = register(factory, "type=MBeanFactory");
+        
+        // Register the naming resources
+        globalNamingResources.init();
+        
+        // Initialize our defined Services
+        for (int i = 0; i < services.length; i++) {
+            services[i].init();
+        }
+    }
+    
+    @Override
+    protected void destroyInternal() throws LifecycleException {
+        // Destroy our defined Services
+        for (int i = 0; i < services.length; i++) {
+            services[i].destroy();
+        }
+
+        globalNamingResources.destroy();
+        
+        unregister(onameMBeanFactory);
+        
+        unregister(onameStringCache);
+                
+        super.destroyInternal();
+    }
+
+    /**
+     * Return the parent class loader for this component.
+     */
+    @Override
+    public ClassLoader getParentClassLoader() {
+        if (parentClassLoader != null)
+            return (parentClassLoader);
+        if (catalina != null) {
+            return (catalina.getParentClassLoader());
+        }
+        return (ClassLoader.getSystemClassLoader());
+    }
+
+    /**
+     * Set the parent class loader for this server.
+     *
+     * @param parent The new parent class loader
+     */
+    @Override
+    public void setParentClassLoader(ClassLoader parent) {
+        ClassLoader oldParentClassLoader = this.parentClassLoader;
+        this.parentClassLoader = parent;
+        support.firePropertyChange("parentClassLoader", oldParentClassLoader,
+                                   this.parentClassLoader);
+    }
+
+    
+    private ObjectName onameStringCache;
+    private ObjectName onameMBeanFactory;
+    
+    /**
+     * Obtain the MBean domain for this server. The domain is obtained using
+     * the following search order:
+     * <ol>
+     * <li>Name of first {@link org.apache.catalina.Engine}.</li>
+     * <li>Name of first {@link Service}.</li>
+     * </ol>
+     */
+    @Override
+    protected String getDomainInternal() {
+        
+        String domain = null;
+        
+        Service[] services = findServices();
+        if (services.length > 0) {
+            Service service = services[0];
+            if (service != null) {
+                domain = MBeanUtils.getDomain(service);
+            }
+        }
+        return domain;
+    }
+
+    
+    @Override
+    protected final String getObjectNameKeyProperties() {
+        return "type=Server";
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardService.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardService.java
new file mode 100644
index 0000000..86170e3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardService.java
@@ -0,0 +1,634 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.util.ArrayList;
+
+import javax.management.ObjectName;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Executor;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.Server;
+import org.apache.catalina.Service;
+import org.apache.catalina.connector.Connector;
+import org.apache.catalina.mbeans.MBeanUtils;
+import org.apache.catalina.util.LifecycleMBeanBase;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Standard implementation of the <code>Service</code> interface.  The
+ * associated Container is generally an instance of Engine, but this is
+ * not required.
+ *
+ * @author Craig R. McClanahan
+ */
+
+public class StandardService extends LifecycleMBeanBase implements Service {
+
+    private static final Log log = LogFactory.getLog(StandardService.class);
+   
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Descriptive information about this component implementation.
+     */
+    private static final String info =
+        "org.apache.catalina.core.StandardService/1.0";
+
+
+    /**
+     * The name of this service.
+     */
+    private String name = null;
+
+
+    /**
+     * The string manager for this package.
+     */
+    private static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    /**
+     * The <code>Server</code> that owns this Service, if any.
+     */
+    private Server server = null;
+
+    /**
+     * The property change support for this component.
+     */
+    protected PropertyChangeSupport support = new PropertyChangeSupport(this);
+
+
+    /**
+     * The set of Connectors associated with this Service.
+     */
+    protected Connector connectors[] = new Connector[0];
+    
+    /**
+     * 
+     */
+    protected ArrayList<Executor> executors = new ArrayList<Executor>();
+
+    /**
+     * The Container associated with this Service. (In the case of the
+     * org.apache.catalina.startup.Embedded subclass, this holds the most
+     * recently added Engine.)
+     */
+    protected Container container = null;
+
+    private ClassLoader parentClassLoader = null;
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the <code>Container</code> that handles requests for all
+     * <code>Connectors</code> associated with this Service.
+     */
+    @Override
+    public Container getContainer() {
+
+        return (this.container);
+
+    }
+
+
+    /**
+     * Set the <code>Container</code> that handles requests for all
+     * <code>Connectors</code> associated with this Service.
+     *
+     * @param container The new Container
+     */
+    @Override
+    public void setContainer(Container container) {
+
+        Container oldContainer = this.container;
+        if ((oldContainer != null) && (oldContainer instanceof Engine))
+            ((Engine) oldContainer).setService(null);
+        this.container = container;
+        if ((this.container != null) && (this.container instanceof Engine))
+            ((Engine) this.container).setService(this);
+        if (getState().isAvailable() && (this.container != null)) {
+            try {
+                this.container.start();
+            } catch (LifecycleException e) {
+                // Ignore
+            }
+        }
+        if (getState().isAvailable() && (oldContainer != null)) {
+            try {
+                oldContainer.stop();
+            } catch (LifecycleException e) {
+                // Ignore
+            }
+        }
+
+        // Report this property change to interested listeners
+        support.firePropertyChange("container", oldContainer, this.container);
+
+    }
+
+
+    /**
+     * Return descriptive information about this Service 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 name of this Service.
+     */
+    @Override
+    public String getName() {
+
+        return (this.name);
+
+    }
+
+
+    /**
+     * Set the name of this Service.
+     *
+     * @param name The new service name
+     */
+    @Override
+    public void setName(String name) {
+
+        this.name = name;
+
+    }
+
+
+    /**
+     * Return the <code>Server</code> with which we are associated (if any).
+     */
+    @Override
+    public Server getServer() {
+
+        return (this.server);
+
+    }
+
+
+    /**
+     * Set the <code>Server</code> with which we are associated (if any).
+     *
+     * @param server The server that owns this Service
+     */
+    @Override
+    public void setServer(Server server) {
+
+        this.server = server;
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add a new Connector to the set of defined Connectors, and associate it
+     * with this Service's Container.
+     *
+     * @param connector The Connector to be added
+     */
+    @Override
+    public void addConnector(Connector connector) {
+
+        synchronized (connectors) {
+            connector.setService(this);
+            Connector results[] = new Connector[connectors.length + 1];
+            System.arraycopy(connectors, 0, results, 0, connectors.length);
+            results[connectors.length] = connector;
+            connectors = results;
+
+            if (getState().isAvailable()) {
+                try {
+                    connector.start();
+                } catch (LifecycleException e) {
+                    log.error(sm.getString(
+                            "standardService.connector.startFailed",
+                            connector), e);
+                }
+            }
+
+            // Report this property change to interested listeners
+            support.firePropertyChange("connector", null, connector);
+        }
+
+    }
+
+    public ObjectName[] getConnectorNames() {
+        ObjectName results[] = new ObjectName[connectors.length];
+        for (int i=0; i<results.length; i++) {
+            results[i] = connectors[i].getObjectName();
+        }
+        return results;
+    }
+
+
+    /**
+     * Add a property change listener to this component.
+     *
+     * @param listener The listener to add
+     */
+    public void addPropertyChangeListener(PropertyChangeListener listener) {
+
+        support.addPropertyChangeListener(listener);
+
+    }
+
+
+    /**
+     * Find and return the set of Connectors associated with this Service.
+     */
+    @Override
+    public Connector[] findConnectors() {
+
+        return (connectors);
+
+    }
+
+
+    /**
+     * Remove the specified Connector from the set associated from this
+     * Service.  The removed Connector will also be disassociated from our
+     * Container.
+     *
+     * @param connector The Connector to be removed
+     */
+    @Override
+    public void removeConnector(Connector connector) {
+
+        synchronized (connectors) {
+            int j = -1;
+            for (int i = 0; i < connectors.length; i++) {
+                if (connector == connectors[i]) {
+                    j = i;
+                    break;
+                }
+            }
+            if (j < 0)
+                return;
+            if (connectors[j].getState().isAvailable()) {
+                try {
+                    connectors[j].stop();
+                } catch (LifecycleException e) {
+                    log.error(sm.getString(
+                            "standardService.connector.stopFailed",
+                            connectors[j]), e);
+                }
+            }
+            connector.setService(null);
+            int k = 0;
+            Connector results[] = new Connector[connectors.length - 1];
+            for (int i = 0; i < connectors.length; i++) {
+                if (i != j)
+                    results[k++] = connectors[i];
+            }
+            connectors = results;
+
+            // Report this property change to interested listeners
+            support.firePropertyChange("connector", connector, null);
+        }
+
+    }
+
+
+    /**
+     * Remove a property change listener from this component.
+     *
+     * @param listener The listener to remove
+     */
+    public void removePropertyChangeListener(PropertyChangeListener listener) {
+
+        support.removePropertyChangeListener(listener);
+
+    }
+
+
+    /**
+     * Return a String representation of this component.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("StandardService[");
+        sb.append(getName());
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+    /**
+     * Adds a named executor to the service
+     * @param ex Executor
+     */
+    @Override
+    public void addExecutor(Executor ex) {
+        synchronized (executors) {
+            if (!executors.contains(ex)) {
+                executors.add(ex);
+                if (getState().isAvailable())
+                    try {
+                        ex.start();
+                    } catch (LifecycleException x) {
+                        log.error("Executor.start", x);
+                    }
+            }
+        }
+    }
+
+    /**
+     * Retrieves all executors
+     * @return Executor[]
+     */
+    @Override
+    public Executor[] findExecutors() {
+        synchronized (executors) {
+            Executor[] arr = new Executor[executors.size()];
+            executors.toArray(arr);
+            return arr;
+        }
+    }
+
+    /**
+     * Retrieves executor by name, null if not found
+     * @param executorName String
+     * @return Executor
+     */
+    @Override
+    public Executor getExecutor(String executorName) {
+        synchronized (executors) {
+            for (Executor executor: executors) {
+                if (executorName.equals(executor.getName()))
+                    return executor;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Removes an executor from the service
+     * @param ex Executor
+     */
+    @Override
+    public void removeExecutor(Executor ex) {
+        synchronized (executors) {
+            if ( executors.remove(ex) && getState().isAvailable() ) {
+                try {
+                    ex.stop();
+                } catch (LifecycleException e) {
+                    log.error("Executor.stop", e);
+                }
+            }
+        }
+    }
+
+
+
+    /**
+     * Start nested components ({@link Executor}s, {@link Connector}s and
+     * {@link Container}s) 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.isInfoEnabled())
+            log.info(sm.getString("standardService.start.name", this.name));
+        setState(LifecycleState.STARTING);
+
+        // Start our defined Container first
+        if (container != null) {
+            synchronized (container) {
+                container.start();
+            }
+        }
+
+        synchronized (executors) {
+            for (Executor executor: executors) {
+                executor.start();
+            }
+        }
+
+        // Start our defined Connectors second
+        synchronized (connectors) {
+            for (Connector connector: connectors) {
+                try {
+                    // If it has already failed, don't try and start it
+                    if (connector.getState() != LifecycleState.FAILED) {
+                        connector.start();
+                    }
+                } catch (Exception e) {
+                    log.error(sm.getString(
+                            "standardService.connector.startFailed",
+                            connector), e);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Stop nested components ({@link Executor}s, {@link Connector}s and
+     * {@link Container}s) and implement the requirements of
+     * {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
+     *
+     * @exception LifecycleException if this component detects a fatal error
+     *  that needs to be reported
+     */
+    @Override
+    protected void stopInternal() throws LifecycleException {
+
+        // Pause connectors first
+        synchronized (connectors) {
+            for (Connector connector: connectors) {
+                try {
+                    connector.pause();
+                } catch (Exception e) {
+                    log.error(sm.getString(
+                            "standardService.connector.pauseFailed",
+                            connector), e);
+                }
+            }
+        }
+
+        if(log.isInfoEnabled())
+            log.info(sm.getString("standardService.stop.name", this.name));
+        setState(LifecycleState.STOPPING);
+
+        // Stop our defined Container second
+        if (container != null) {
+            synchronized (container) {
+                container.stop();
+            }
+        }
+
+        // Now stop the connectors
+        synchronized (connectors) {
+            for (Connector connector: connectors) {
+                if (!LifecycleState.STARTED.equals(
+                        connector.getState())) {
+                    // Connectors only need stopping if they are currently
+                    // started. They may have failed to start or may have been
+                    // stopped (e.g. via a JMX call)
+                    continue;
+                }
+                try {
+                    connector.stop();
+                } catch (Exception e) {
+                    log.error(sm.getString(
+                            "standardService.connector.stopFailed",
+                            connector), e);
+                }
+            }
+        }
+
+        synchronized (executors) {
+            for (Executor executor: executors) {
+                executor.stop();
+            }
+        }
+    }
+
+
+    /**
+     * Invoke a pre-startup initialization. This is used to allow connectors
+     * to bind to restricted ports under Unix operating environments.
+     */
+    @Override
+    protected void initInternal() throws LifecycleException {
+
+        super.initInternal();
+        
+        if (container != null) {
+            container.init();
+        }
+
+        // Initialize any Executors
+        for (Executor executor : findExecutors()) {
+            if (executor instanceof LifecycleMBeanBase) {
+                ((LifecycleMBeanBase) executor).setDomain(getDomain());
+            }
+            executor.init();
+        }
+
+        // Initialize our defined Connectors
+        synchronized (connectors) {
+            for (Connector connector : connectors) {
+                try {
+                    connector.init();
+                } catch (Exception e) {
+                    String message = sm.getString(
+                            "standardService.connector.initFailed", connector);
+                    log.error(message, e);
+
+                    if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
+                        throw new LifecycleException(message);
+                }
+            }
+        }
+    }
+    
+    @Override
+    protected void destroyInternal() throws LifecycleException {
+        // Destroy our defined Connectors
+        synchronized (connectors) {
+            for (Connector connector : connectors) {
+                try {
+                    connector.destroy();
+                } catch (Exception e) {
+                    log.error(sm.getString(
+                            "standardService.connector.destroyfailed",
+                            connector), e);
+                }
+            }
+        }
+
+        // Destroy any Executors
+        for (Executor executor : findExecutors()) {
+            executor.destroy();
+        }
+
+        if (container != null) {
+            container.destroy();
+        }
+
+        super.destroyInternal();
+    }
+
+    /**
+     * Return the parent class loader for this component.
+     */
+    @Override
+    public ClassLoader getParentClassLoader() {
+        if (parentClassLoader != null)
+            return (parentClassLoader);
+        if (server != null) {
+            return (server.getParentClassLoader());
+        }
+        return (ClassLoader.getSystemClassLoader());
+    }
+
+    /**
+     * Set the parent class loader for this server.
+     *
+     * @param parent The new parent class loader
+     */
+    @Override
+    public void setParentClassLoader(ClassLoader parent) {
+        ClassLoader oldParentClassLoader = this.parentClassLoader;
+        this.parentClassLoader = parent;
+        support.firePropertyChange("parentClassLoader", oldParentClassLoader,
+                                   this.parentClassLoader);
+    }
+    @Override
+    protected String getDomainInternal() {
+        
+        return MBeanUtils.getDomain(this);
+    }
+
+    @Override
+    public final String getObjectNameKeyProperties() {
+        return "type=Service";
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardThreadExecutor.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardThreadExecutor.java
new file mode 100644
index 0000000..e203680
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardThreadExecutor.java
@@ -0,0 +1,334 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.catalina.Executor;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.util.LifecycleMBeanBase;
+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;
+
+public class StandardThreadExecutor extends LifecycleMBeanBase
+        implements Executor, ResizableExecutor {
+    
+    // ---------------------------------------------- Properties
+    /**
+     * Default thread priority
+     */
+    protected int threadPriority = Thread.NORM_PRIORITY;
+
+    /**
+     * Run threads in daemon or non-daemon state
+     */
+    protected boolean daemon = true;
+    
+    /**
+     * Default name prefix for the thread name
+     */
+    protected String namePrefix = "tomcat-exec-";
+    
+    /**
+     * max number of threads
+     */
+    protected int maxThreads = 200;
+    
+    /**
+     * min number of threads
+     */
+    protected int minSpareThreads = 25;
+    
+    /**
+     * idle time in milliseconds
+     */
+    protected int maxIdleTime = 60000;
+    
+    /**
+     * The executor we use for this component
+     */
+    protected ThreadPoolExecutor executor = null;
+    
+    /**
+     * the name of this thread pool
+     */
+    protected String name;
+    
+    /**
+     * prestart threads?
+     */
+    protected boolean prestartminSpareThreads = false;
+
+    /**
+     * The maximum number of elements that can queue up before we reject them
+     */
+    protected int maxQueueSize = Integer.MAX_VALUE;
+    
+    /**
+     * After a context is stopped, threads in the pool are renewed. To avoid
+     * renewing all threads at the same time, this delay is observed between 2
+     * threads being renewed.
+     */
+    protected long threadRenewalDelay = 
+        org.apache.tomcat.util.threads.Constants.DEFAULT_THREAD_RENEWAL_DELAY;
+    
+    private TaskQueue taskqueue = null;
+    // ---------------------------------------------- Constructors
+    public StandardThreadExecutor() {
+        //empty constructor for the digester
+    }
+
+
+    // ---------------------------------------------- Public Methods
+    
+    @Override
+    protected void initInternal() throws LifecycleException {
+        super.initInternal();
+    }
+
+    
+    /**
+     * Start the 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 void startInternal() throws LifecycleException {
+
+        taskqueue = new TaskQueue(maxQueueSize);
+        TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());
+        executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);
+        if (prestartminSpareThreads) {
+            executor.prestartAllCoreThreads();
+        }
+        taskqueue.setParent(executor);
+
+        setState(LifecycleState.STARTING);
+    }
+
+
+    /**
+     * Stop the component and implement the requirements
+     * of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
+     *
+     * @exception LifecycleException if this component detects a fatal error
+     *  that needs to be reported
+     */
+    @Override
+    protected void stopInternal() throws LifecycleException {
+
+        setState(LifecycleState.STOPPING);
+        if ( executor != null ) executor.shutdownNow();
+        executor = null;
+        taskqueue = null;
+    }
+
+    
+    @Override
+    protected void destroyInternal() throws LifecycleException {
+        super.destroyInternal();
+    }
+
+    
+    @Override
+    public void execute(Runnable command, long timeout, TimeUnit unit) {
+        if ( executor != null ) {
+            executor.execute(command,timeout,unit);
+        } else { 
+            throw new IllegalStateException("StandardThreadExecutor not started.");
+        }
+    }
+    
+    
+    @Override
+    public void execute(Runnable command) {
+        if ( executor != null ) {
+            try {
+                executor.execute(command);
+            } catch (RejectedExecutionException rx) {
+                //there could have been contention around the queue
+                if ( !( (TaskQueue) executor.getQueue()).force(command) ) throw new RejectedExecutionException("Work queue full.");
+            }
+        } else throw new IllegalStateException("StandardThreadPool not started.");
+    }
+    
+    public void contextStopping() {
+        if (executor != null) {
+            executor.contextStopping();
+        }
+    }
+
+    public int getThreadPriority() {
+        return threadPriority;
+    }
+
+    public boolean isDaemon() {
+
+        return daemon;
+    }
+
+    public String getNamePrefix() {
+        return namePrefix;
+    }
+
+    public int getMaxIdleTime() {
+        return maxIdleTime;
+    }
+
+    @Override
+    public int getMaxThreads() {
+        return maxThreads;
+    }
+
+    public int getMinSpareThreads() {
+        return minSpareThreads;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    public boolean isPrestartminSpareThreads() {
+
+        return prestartminSpareThreads;
+    }
+    public void setThreadPriority(int threadPriority) {
+        this.threadPriority = threadPriority;
+    }
+
+    public void setDaemon(boolean daemon) {
+        this.daemon = daemon;
+    }
+
+    public void setNamePrefix(String namePrefix) {
+        this.namePrefix = namePrefix;
+    }
+
+    public void setMaxIdleTime(int maxIdleTime) {
+        this.maxIdleTime = maxIdleTime;
+        if (executor != null) {
+            executor.setKeepAliveTime(maxIdleTime, TimeUnit.MILLISECONDS);
+        }
+    }
+
+    public void setMaxThreads(int maxThreads) {
+        this.maxThreads = maxThreads;
+        if (executor != null) {
+            executor.setMaximumPoolSize(maxThreads);
+        }
+    }
+
+    public void setMinSpareThreads(int minSpareThreads) {
+        this.minSpareThreads = minSpareThreads;
+        if (executor != null) {
+            executor.setCorePoolSize(minSpareThreads);
+        }
+    }
+
+    public void setPrestartminSpareThreads(boolean prestartminSpareThreads) {
+        this.prestartminSpareThreads = prestartminSpareThreads;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+    
+    public void setMaxQueueSize(int size) {
+        this.maxQueueSize = size;
+    }
+    
+    public int getMaxQueueSize() {
+        return maxQueueSize;
+    }
+    
+    public long getThreadRenewalDelay() {
+        return threadRenewalDelay;
+    }
+
+    public void setThreadRenewalDelay(long threadRenewalDelay) {
+        this.threadRenewalDelay = threadRenewalDelay;
+        if (executor != null) {
+            executor.setThreadRenewalDelay(threadRenewalDelay);
+        }
+    }
+
+    // Statistics from the thread pool
+    @Override
+    public int getActiveCount() {
+        return (executor != null) ? executor.getActiveCount() : 0;
+    }
+
+    public long getCompletedTaskCount() {
+        return (executor != null) ? executor.getCompletedTaskCount() : 0;
+    }
+
+    public int getCorePoolSize() {
+        return (executor != null) ? executor.getCorePoolSize() : 0;
+    }
+
+    public int getLargestPoolSize() {
+        return (executor != null) ? executor.getLargestPoolSize() : 0;
+    }
+
+    @Override
+    public int getPoolSize() {
+        return (executor != null) ? executor.getPoolSize() : 0;
+    }
+
+    public int getQueueSize() {
+        return (executor != null) ? executor.getQueue().size() : -1;
+    }
+
+
+    @Override
+    public boolean resizePool(int corePoolSize, int maximumPoolSize) {
+        if (executor == null)
+            return false;
+
+        executor.setCorePoolSize(corePoolSize);
+        executor.setMaximumPoolSize(maximumPoolSize);
+        return true;
+    }
+
+
+    @Override
+    public boolean resizeQueue(int capacity) {
+        return false;
+    }
+    
+
+    @Override
+    protected String getDomainInternal() {
+        // No way to navigate to Engine. Needs to have domain set.
+        return null;
+    }
+
+    @Override
+    protected String getObjectNameKeyProperties() {
+        StringBuilder name = new StringBuilder("type=Executor,name=");
+        name.append(getName());
+        return name.toString();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardWrapper.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardWrapper.java
new file mode 100644
index 0000000..84ba023
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardWrapper.java
@@ -0,0 +1,1894 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.core;
+
+import java.io.PrintStream;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Stack;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanNotificationInfo;
+import javax.management.Notification;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationEmitter;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.servlet.MultipartConfigElement;
+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.ServletSecurityElement;
+import javax.servlet.SingleThreadModel;
+import javax.servlet.UnavailableException;
+import javax.servlet.annotation.MultipartConfig;
+import javax.servlet.annotation.ServletSecurity;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.ContainerServlet;
+import org.apache.catalina.Context;
+import org.apache.catalina.Globals;
+import org.apache.catalina.InstanceEvent;
+import org.apache.catalina.InstanceListener;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.mbeans.MBeanUtils;
+import org.apache.catalina.security.SecurityUtil;
+import org.apache.catalina.util.Enumerator;
+import org.apache.catalina.util.InstanceSupport;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.InstanceManager;
+import org.apache.tomcat.PeriodicEventListener;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.log.SystemLogHandler;
+import org.apache.tomcat.util.modeler.Registry;
+
+/**
+ * Standard implementation of the <b>Wrapper</b> interface that represents
+ * an individual servlet definition.  No child Containers are allowed, and
+ * the parent Container must be a Context.
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @version $Id: StandardWrapper.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+@SuppressWarnings("deprecation") // SingleThreadModel
+public class StandardWrapper extends ContainerBase
+    implements ServletConfig, Wrapper, NotificationEmitter {
+
+    private static final Log log = LogFactory.getLog( StandardWrapper.class );
+
+    protected static final String[] DEFAULT_SERVLET_METHODS = new String[] {
+                                                    "GET", "HEAD", "POST" };
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Create a new StandardWrapper component with the default basic Valve.
+     */
+    public StandardWrapper() {
+
+        super();
+        swValve=new StandardWrapperValve();
+        pipeline.setBasic(swValve);
+        broadcaster = new NotificationBroadcasterSupport();
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The date and time at which this servlet will become available (in
+     * milliseconds since the epoch), or zero if the servlet is available.
+     * If this value equals Long.MAX_VALUE, the unavailability of this
+     * servlet is considered permanent.
+     */
+    protected long available = 0L;
+    
+    /**
+     * The broadcaster that sends j2ee notifications. 
+     */
+    protected NotificationBroadcasterSupport broadcaster = null;
+    
+    /**
+     * The count of allocations that are currently active (even if they
+     * are for the same instance, as will be true on a non-STM servlet).
+     */
+    protected AtomicInteger countAllocated = new AtomicInteger(0);
+
+
+    /**
+     * The facade associated with this wrapper.
+     */
+    protected StandardWrapperFacade facade =
+        new StandardWrapperFacade(this);
+
+
+    /**
+     * The descriptive information string for this implementation.
+     */
+    protected static final String info =
+        "org.apache.catalina.core.StandardWrapper/1.0";
+
+
+    /**
+     * The (single) possibly uninitialized instance of this servlet.
+     */
+    protected volatile Servlet instance = null;
+
+
+    /**
+     * Flag that indicates if this instance has been initialized
+     */
+    protected volatile boolean instanceInitialized = false;
+
+    /**
+     * The support object for our instance listeners.
+     */
+    protected InstanceSupport instanceSupport = new InstanceSupport(this);
+
+
+    /**
+     * The load-on-startup order value (negative value means load on
+     * first call) for this servlet.
+     */
+    protected int loadOnStartup = -1;
+
+
+    /**
+     * Mappings associated with the wrapper.
+     */
+    protected ArrayList<String> mappings = new ArrayList<String>();
+
+
+    /**
+     * The initialization parameters for this servlet, keyed by
+     * parameter name.
+     */
+    protected HashMap<String, String> parameters = new HashMap<String, String>();
+
+
+    /**
+     * The security role references for this servlet, keyed by role name
+     * used in the servlet.  The corresponding value is the role name of
+     * the web application itself.
+     */
+    protected HashMap<String, String> references = new HashMap<String, String>();
+
+
+    /**
+     * The run-as identity for this servlet.
+     */
+    protected String runAs = null;
+
+    /**
+     * The notification sequence number.
+     */
+    protected long sequenceNumber = 0;
+
+    /**
+     * The fully qualified servlet class name for this servlet.
+     */
+    protected String servletClass = null;
+
+
+    /**
+     * Does this servlet implement the SingleThreadModel interface?
+     */
+    protected boolean singleThreadModel = false;
+
+
+    /**
+     * Are we unloading our servlet instance at the moment?
+     */
+    protected boolean unloading = false;
+
+
+    /**
+     * Maximum number of STM instances.
+     */
+    protected int maxInstances = 20;
+
+
+    /**
+     * Number of instances currently loaded for a STM servlet.
+     */
+    protected int nInstances = 0;
+
+
+    /**
+     * Stack containing the STM instances.
+     */
+    protected Stack<Servlet> instancePool = null;
+
+    
+    /**
+     * Wait time for servlet unload in ms.
+     */
+    protected long unloadDelay = 2000;
+    
+
+    /**
+     * True if this StandardWrapper is for the JspServlet
+     */
+    protected boolean isJspServlet;
+
+
+    /**
+     * The ObjectName of the JSP monitoring mbean
+     */
+    protected ObjectName jspMonitorON;
+
+
+    /**
+     * Should we swallow System.out
+     */
+    protected boolean swallowOutput = false;
+
+    // To support jmx attributes
+    protected StandardWrapperValve swValve;
+    protected long loadTime=0;
+    protected int classLoadTime=0;
+    
+    /**
+     * Multipart config
+     */
+    protected MultipartConfigElement multipartConfigElement = null;
+    
+    /**
+     * Async support
+     */
+    protected boolean asyncSupported = false;
+
+    /**
+     * Enabled
+     */
+    protected boolean enabled = true;
+
+    protected volatile boolean servletSecurityAnnotationScanRequired = false;
+
+    /**
+     * Static class array used when the SecurityManager is turned on and 
+     * <code>Servlet.init</code> is invoked.
+     */
+    protected static Class<?>[] classType = new Class[]{ServletConfig.class};
+    
+    
+    /**
+     * Static class array used when the SecurityManager is turned on and 
+     * <code>Servlet.service</code>  is invoked.
+     */                                                 
+    protected static Class<?>[] classTypeUsedInService = new Class[]{
+                                                         ServletRequest.class,
+                                                         ServletResponse.class};
+    
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the available date/time for this servlet, in milliseconds since
+     * the epoch.  If this date/time is Long.MAX_VALUE, it is considered to mean
+     * that unavailability is permanent and any request for this servlet will return
+     * an SC_NOT_FOUND error.  If this date/time is in the future, any request for
+     * this servlet will return an SC_SERVICE_UNAVAILABLE error.  If it is zero,
+     * the servlet is currently available.
+     */
+    @Override
+    public long getAvailable() {
+
+        return (this.available);
+
+    }
+
+
+    /**
+     * Set the available date/time for this servlet, in milliseconds since the
+     * epoch.  If this date/time is Long.MAX_VALUE, it is considered to mean
+     * that unavailability is permanent and any request for this servlet will return
+     * an SC_NOT_FOUND error. If this date/time is in the future, any request for
+     * this servlet will return an SC_SERVICE_UNAVAILABLE error.
+     *
+     * @param available The new available date/time
+     */
+    @Override
+    public void setAvailable(long available) {
+
+        long oldAvailable = this.available;
+        if (available > System.currentTimeMillis())
+            this.available = available;
+        else
+            this.available = 0L;
+        support.firePropertyChange("available", Long.valueOf(oldAvailable),
+                                   Long.valueOf(this.available));
+
+    }
+
+
+    /**
+     * Return the number of active allocations of this servlet, even if they
+     * are all for the same instance (as will be true for servlets that do
+     * not implement <code>SingleThreadModel</code>.
+     */
+    public int getCountAllocated() {
+
+        return (this.countAllocated.get());
+
+    }
+
+
+    /**
+     * Return descriptive information about this Container 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 InstanceSupport object for this Wrapper instance.
+     */
+    public InstanceSupport getInstanceSupport() {
+
+        return (this.instanceSupport);
+
+    }
+
+
+    /**
+     * Return the load-on-startup order value (negative value means
+     * load on first call).
+     */
+    @Override
+    public int getLoadOnStartup() {
+
+        if (isJspServlet && loadOnStartup < 0) {
+            /*
+             * JspServlet must always be preloaded, because its instance is
+             * used during registerJMX (when registering the JSP
+             * monitoring mbean)
+             */
+             return Integer.MAX_VALUE;
+        } else {
+            return (this.loadOnStartup);
+        }
+    }
+
+
+    /**
+     * Set the load-on-startup order value (negative value means
+     * load on first call).
+     *
+     * @param value New load-on-startup value
+     */
+    @Override
+    public void setLoadOnStartup(int value) {
+
+        int oldLoadOnStartup = this.loadOnStartup;
+        this.loadOnStartup = value;
+        support.firePropertyChange("loadOnStartup",
+                                   Integer.valueOf(oldLoadOnStartup),
+                                   Integer.valueOf(this.loadOnStartup));
+
+    }
+
+
+
+    /**
+     * Set the load-on-startup order value from a (possibly null) string.
+     * Per the specification, any missing or non-numeric value is converted
+     * to a zero, so that this servlet will still be loaded at startup
+     * time, but in an arbitrary order.
+     *
+     * @param value New load-on-startup value
+     */
+    public void setLoadOnStartupString(String value) {
+
+        try {
+            setLoadOnStartup(Integer.parseInt(value));
+        } catch (NumberFormatException e) {
+            setLoadOnStartup(0);
+        }
+    }
+
+    public String getLoadOnStartupString() {
+        return Integer.toString( getLoadOnStartup());
+    }
+
+
+    /**
+     * Return maximum number of instances that will be allocated when a single
+     * thread model servlet is used.
+     */
+    public int getMaxInstances() {
+
+        return (this.maxInstances);
+
+    }
+
+
+    /**
+     * Set the maximum number of instances that will be allocated when a single
+     * thread model servlet is used.
+     *
+     * @param maxInstances New value of maxInstances
+     */
+    public void setMaxInstances(int maxInstances) {
+
+        int oldMaxInstances = this.maxInstances;
+        this.maxInstances = maxInstances;
+        support.firePropertyChange("maxInstances", oldMaxInstances,
+                                   this.maxInstances);
+
+    }
+
+
+    /**
+     * Set the parent Container of this Wrapper, but only if it is a Context.
+     *
+     * @param container Proposed parent Container
+     */
+    @Override
+    public void setParent(Container container) {
+
+        if ((container != null) &&
+            !(container instanceof Context))
+            throw new IllegalArgumentException
+                (sm.getString("standardWrapper.notContext"));
+        if (container instanceof StandardContext) {
+            swallowOutput = ((StandardContext)container).getSwallowOutput();
+            unloadDelay = ((StandardContext)container).getUnloadDelay();
+        }
+        super.setParent(container);
+
+    }
+
+
+    /**
+     * Return the run-as identity for this servlet.
+     */
+    @Override
+    public String getRunAs() {
+
+        return (this.runAs);
+
+    }
+
+
+    /**
+     * Set the run-as identity for this servlet.
+     *
+     * @param runAs New run-as identity value
+     */
+    @Override
+    public void setRunAs(String runAs) {
+
+        String oldRunAs = this.runAs;
+        this.runAs = runAs;
+        support.firePropertyChange("runAs", oldRunAs, this.runAs);
+
+    }
+
+
+    /**
+     * Return the fully qualified servlet class name for this servlet.
+     */
+    @Override
+    public String getServletClass() {
+
+        return (this.servletClass);
+
+    }
+
+
+    /**
+     * Set the fully qualified servlet class name for this servlet.
+     *
+     * @param servletClass Servlet class name
+     */
+    @Override
+    public void setServletClass(String servletClass) {
+
+        String oldServletClass = this.servletClass;
+        this.servletClass = servletClass;
+        support.firePropertyChange("servletClass", oldServletClass,
+                                   this.servletClass);
+        if (Constants.JSP_SERVLET_CLASS.equals(servletClass)) {
+            isJspServlet = true;
+        }
+    }
+
+
+
+    /**
+     * Set the name of this servlet.  This is an alias for the normal
+     * <code>Container.setName()</code> method, and complements the
+     * <code>getServletName()</code> method required by the
+     * <code>ServletConfig</code> interface.
+     *
+     * @param name The new name of this servlet
+     */
+    public void setServletName(String name) {
+
+        setName(name);
+
+    }
+
+
+    /**
+     * Return <code>true</code> if the servlet class represented by this
+     * component implements the <code>SingleThreadModel</code> interface.
+     */
+    public boolean isSingleThreadModel() {
+
+        try {
+            loadServlet();
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+        }
+        return (singleThreadModel);
+
+    }
+
+
+    /**
+     * Is this servlet currently unavailable?
+     */
+    @Override
+    public boolean isUnavailable() {
+
+        if (!isEnabled())
+            return true;
+        else if (available == 0L)
+            return false;
+        else if (available <= System.currentTimeMillis()) {
+            available = 0L;
+            return false;
+        } else
+            return true;
+
+    }
+
+
+    /**
+     * Gets the names of the methods supported by the underlying servlet.
+     *
+     * This is the same set of methods included in the Allow response header
+     * in response to an OPTIONS request method processed by the underlying
+     * servlet.
+     *
+     * @return Array of names of the methods supported by the underlying
+     * servlet
+     */
+    @Override
+    public String[] getServletMethods() throws ServletException {
+
+        Class<? extends Servlet> servletClazz = loadServlet().getClass();
+        if (!javax.servlet.http.HttpServlet.class.isAssignableFrom(
+                                                        servletClazz)) {
+            return DEFAULT_SERVLET_METHODS;
+        }
+
+        HashSet<String> allow = new HashSet<String>();
+        allow.add("TRACE");
+        allow.add("OPTIONS");
+
+        Method[] methods = getAllDeclaredMethods(servletClazz);
+        for (int i=0; methods != null && i<methods.length; i++) {
+            Method m = methods[i];
+
+            if (m.getName().equals("doGet")) {
+                allow.add("GET");
+                allow.add("HEAD");
+            } else if (m.getName().equals("doPost")) {
+                allow.add("POST");
+            } else if (m.getName().equals("doPut")) {
+                allow.add("PUT");
+            } else if (m.getName().equals("doDelete")) {
+                allow.add("DELETE");
+            }
+        }
+
+        String[] methodNames = new String[allow.size()];
+        return allow.toArray(methodNames);
+
+    }
+
+
+    /**
+     * Return the associated servlet instance.
+     */
+    @Override
+    public Servlet getServlet() {
+        return instance;
+    }
+    
+    
+    /**
+     * Set the associated servlet instance.
+     */
+    @Override
+    public void setServlet(Servlet servlet) {
+        instance = servlet;
+    }
+
+    
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setServletSecurityAnnotationScanRequired(boolean b) {
+        this.servletSecurityAnnotationScanRequired = b;
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * 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() {
+        super.backgroundProcess();
+        
+        if (!getState().isAvailable())
+            return;
+        
+        if (getServlet() != null && (getServlet() instanceof PeriodicEventListener)) {
+            ((PeriodicEventListener) getServlet()).periodicEvent();
+        }
+    }
+    
+    
+    /**
+     * Extract the root cause from a servlet exception.
+     * 
+     * @param e The servlet exception
+     */
+    public static Throwable getRootCause(ServletException e) {
+        Throwable rootCause = e;
+        Throwable rootCauseCheck = null;
+        // Extra aggressive rootCause finding
+        int loops = 0;
+        do {
+            loops++;
+            rootCauseCheck = rootCause.getCause();
+            if (rootCauseCheck != null)
+                rootCause = rootCauseCheck;
+        } while (rootCauseCheck != null && (loops < 20));
+        return rootCause;
+    }
+
+
+    /**
+     * Refuse to add a child Container, because Wrappers are the lowest level
+     * of the Container hierarchy.
+     *
+     * @param child Child container to be added
+     */
+    @Override
+    public void addChild(Container child) {
+
+        throw new IllegalStateException
+            (sm.getString("standardWrapper.notChild"));
+
+    }
+
+
+    /**
+     * Add a new servlet initialization parameter for this servlet.
+     *
+     * @param name Name of this initialization parameter to add
+     * @param value Value of this initialization parameter to add
+     */
+    @Override
+    public void addInitParameter(String name, String value) {
+
+        synchronized (parameters) {
+            parameters.put(name, value);
+        }
+        fireContainerEvent("addInitParameter", name);
+
+    }
+
+
+    /**
+     * Add a new listener interested in InstanceEvents.
+     *
+     * @param listener The new listener
+     */
+    @Override
+    public void addInstanceListener(InstanceListener listener) {
+
+        instanceSupport.addInstanceListener(listener);
+
+    }
+
+
+    /**
+     * Add a mapping associated with the Wrapper.
+     *
+     * @param mapping The new wrapper mapping
+     */
+    @Override
+    public void addMapping(String mapping) {
+
+        synchronized (mappings) {
+            mappings.add(mapping);
+        }
+        if(parent.getState().equals(LifecycleState.STARTED))
+            fireContainerEvent(ADD_MAPPING_EVENT, mapping);
+
+    }
+
+
+    /**
+     * Add a new security role reference record to the set of records for
+     * this servlet.
+     *
+     * @param name Role name used within this servlet
+     * @param link Role name used within the web application
+     */
+    @Override
+    public void addSecurityReference(String name, String link) {
+
+        synchronized (references) {
+            references.put(name, link);
+        }
+        fireContainerEvent("addSecurityReference", name);
+
+    }
+
+
+    /**
+     * Allocate an initialized instance of this Servlet that is ready to have
+     * its <code>service()</code> method called.  If the servlet class does
+     * not implement <code>SingleThreadModel</code>, the (only) initialized
+     * instance may be returned immediately.  If the servlet class implements
+     * <code>SingleThreadModel</code>, the Wrapper implementation must ensure
+     * that this instance is not allocated again until it is deallocated by a
+     * call to <code>deallocate()</code>.
+     *
+     * @exception ServletException if the servlet init() method threw
+     *  an exception
+     * @exception ServletException if a loading error occurs
+     */
+    @Override
+    public Servlet allocate() throws ServletException {
+
+        // If we are currently unloading this servlet, throw an exception
+        if (unloading)
+            throw new ServletException
+              (sm.getString("standardWrapper.unloading", getName()));
+
+        boolean newInstance = false;
+        
+        // If not SingleThreadedModel, return the same instance every time
+        if (!singleThreadModel) {
+
+            // Load and initialize our instance if necessary
+            if (instance == null) {
+                synchronized (this) {
+                    if (instance == null) {
+                        try {
+                            if (log.isDebugEnabled())
+                                log.debug("Allocating non-STM instance");
+
+                            instance = loadServlet();
+                            // For non-STM, increment here to prevent a race
+                            // condition with unload. Bug 43683, test case #3
+                            if (!singleThreadModel) {
+                                newInstance = true;
+                                countAllocated.incrementAndGet();
+                            }
+                        } catch (ServletException e) {
+                            throw e;
+                        } catch (Throwable e) {
+                            ExceptionUtils.handleThrowable(e);
+                            throw new ServletException
+                                (sm.getString("standardWrapper.allocate"), e);
+                        }
+                    }
+                }
+            }
+
+            if (!instanceInitialized) {
+                initServlet(instance);
+            }
+
+            if (!singleThreadModel) {
+                if (log.isTraceEnabled())
+                    log.trace("  Returning non-STM instance");
+                // For new instances, count will have been incremented at the
+                // time of creation
+                if (!newInstance) {
+                    countAllocated.incrementAndGet();
+                }
+                return (instance);
+            }
+        }
+
+        synchronized (instancePool) {
+
+            while (countAllocated.get() >= nInstances) {
+                // Allocate a new instance if possible, or else wait
+                if (nInstances < maxInstances) {
+                    try {
+                        instancePool.push(loadServlet());
+                        nInstances++;
+                    } catch (ServletException e) {
+                        throw e;
+                    } catch (Throwable e) {
+                        ExceptionUtils.handleThrowable(e);
+                        throw new ServletException
+                            (sm.getString("standardWrapper.allocate"), e);
+                    }
+                } else {
+                    try {
+                        instancePool.wait();
+                    } catch (InterruptedException e) {
+                        // Ignore
+                    }
+                }
+            }
+            if (log.isTraceEnabled())
+                log.trace("  Returning allocated STM instance");
+            countAllocated.incrementAndGet();
+            return instancePool.pop();
+
+        }
+
+    }
+
+
+    /**
+     * Return this previously allocated servlet to the pool of available
+     * instances.  If this servlet class does not implement SingleThreadModel,
+     * no action is actually required.
+     *
+     * @param servlet The servlet to be returned
+     *
+     * @exception ServletException if a deallocation error occurs
+     */
+    @Override
+    public void deallocate(Servlet servlet) throws ServletException {
+
+        // If not SingleThreadModel, no action is required
+        if (!singleThreadModel) {
+            countAllocated.decrementAndGet();
+            return;
+        }
+
+        // Unlock and free this instance
+        synchronized (instancePool) {
+            countAllocated.decrementAndGet();
+            instancePool.push(servlet);
+            instancePool.notify();
+        }
+
+    }
+
+
+    /**
+     * Return the value for the specified initialization parameter name,
+     * if any; otherwise return <code>null</code>.
+     *
+     * @param name Name of the requested initialization parameter
+     */
+    @Override
+    public String findInitParameter(String name) {
+
+        synchronized (parameters) {
+            return parameters.get(name);
+        }
+
+    }
+
+
+    /**
+     * Return the names of all defined initialization parameters for this
+     * servlet.
+     */
+    @Override
+    public String[] findInitParameters() {
+
+        synchronized (parameters) {
+            String results[] = new String[parameters.size()];
+            return parameters.keySet().toArray(results);
+        }
+
+    }
+
+
+    /**
+     * Return the mappings associated with this wrapper.
+     */
+    @Override
+    public String[] findMappings() {
+
+        synchronized (mappings) {
+            return mappings.toArray(new String[mappings.size()]);
+        }
+
+    }
+
+
+    /**
+     * Return the security role link for the specified security role
+     * reference name, if any; otherwise return <code>null</code>.
+     *
+     * @param name Security role reference used within this servlet
+     */
+    @Override
+    public String findSecurityReference(String name) {
+
+        synchronized (references) {
+            return references.get(name);
+        }
+
+    }
+
+
+    /**
+     * Return the set of security role reference names associated with
+     * this servlet, if any; otherwise return a zero-length array.
+     */
+    @Override
+    public String[] findSecurityReferences() {
+
+        synchronized (references) {
+            String results[] = new String[references.size()];
+            return references.keySet().toArray(results);
+        }
+
+    }
+
+
+    /**
+     * FIXME: Fooling introspection ...
+     */
+    public Wrapper findMappingObject() {
+        return (Wrapper) getMappingObject();
+    }
+
+
+    /**
+     * Load and initialize an instance of this servlet, if there is not already
+     * at least one initialized instance.  This can be used, for example, to
+     * load servlets that are marked in the deployment descriptor to be loaded
+     * at server startup time.
+     * <p>
+     * <b>IMPLEMENTATION NOTE</b>:  Servlets whose classnames begin with
+     * <code>org.apache.catalina.</code> (so-called "container" servlets)
+     * are loaded by the same classloader that loaded this class, rather than
+     * the classloader for the current web application.
+     * This gives such classes access to Catalina internals, which are
+     * prevented for classes loaded for web applications.
+     *
+     * @exception ServletException if the servlet init() method threw
+     *  an exception
+     * @exception ServletException if some other loading problem occurs
+     */
+    @Override
+    public synchronized void load() throws ServletException {
+        instance = loadServlet();
+        
+        if (isJspServlet) {
+            StringBuilder oname =
+                new StringBuilder(MBeanUtils.getDomain(getParent()));
+            
+            oname.append(":type=JspMonitor,name=");
+            oname.append(getName());
+            
+            oname.append(getWebModuleKeyProperties());
+            
+            try {
+                jspMonitorON = new ObjectName(oname.toString());
+                Registry.getRegistry(null, null)
+                    .registerComponent(instance, jspMonitorON, null);
+            } catch( Exception ex ) {
+                log.info("Error registering JSP monitoring with jmx " +
+                         instance);
+            }
+        }
+    }
+
+
+    /**
+     * Load and initialize an instance of this servlet, if there is not already
+     * at least one initialized instance.  This can be used, for example, to
+     * load servlets that are marked in the deployment descriptor to be loaded
+     * at server startup time.
+     */
+    public synchronized Servlet loadServlet() throws ServletException {
+
+        // Nothing to do if we already have an instance or an instance pool
+        if (!singleThreadModel && (instance != null))
+            return instance;
+
+        PrintStream out = System.out;
+        if (swallowOutput) {
+            SystemLogHandler.startCapture();
+        }
+
+        Servlet servlet;
+        try {
+            long t1=System.currentTimeMillis();
+            // Complain if no servlet class has been specified
+            if (servletClass == null) {
+                unavailable(null);
+                throw new ServletException
+                    (sm.getString("standardWrapper.notClass", getName()));
+            }
+
+            InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
+            try {
+                servlet = (Servlet) instanceManager.newInstance(servletClass);
+            } catch (ClassCastException e) {
+                unavailable(null);
+                // Restore the context ClassLoader
+                throw new ServletException
+                    (sm.getString("standardWrapper.notServlet", servletClass), e);
+            } catch (Throwable e) {
+                ExceptionUtils.handleThrowable(e);
+                unavailable(null);
+
+                // Added extra log statement for Bugzilla 36630:
+                // http://issues.apache.org/bugzilla/show_bug.cgi?id=36630
+                if(log.isDebugEnabled()) {
+                    log.debug(sm.getString("standardWrapper.instantiate", servletClass), e);
+                }
+
+                // Restore the context ClassLoader
+                throw new ServletException
+                    (sm.getString("standardWrapper.instantiate", servletClass), e);
+            }
+
+            if (multipartConfigElement == null) {
+                MultipartConfig annotation =
+                        servlet.getClass().getAnnotation(MultipartConfig.class);
+                if (annotation != null) {
+                    multipartConfigElement =
+                            new MultipartConfigElement(annotation);
+                }
+            }
+
+            processServletSecurityAnnotation(servlet.getClass());
+
+            // Special handling for ContainerServlet instances
+            if ((servlet instanceof ContainerServlet) &&
+                    (isContainerProvidedServlet(servletClass) ||
+                            ((Context) getParent()).getPrivileged() )) {
+                ((ContainerServlet) servlet).setWrapper(this);
+            }
+
+            classLoadTime=(int) (System.currentTimeMillis() -t1);
+
+            initServlet(servlet);
+
+            // Register our newly initialized instance
+            singleThreadModel = servlet instanceof SingleThreadModel;
+            if (singleThreadModel) {
+                if (instancePool == null)
+                    instancePool = new Stack<Servlet>();
+            }
+            fireContainerEvent("load", this);
+
+            loadTime=System.currentTimeMillis() -t1;
+        } finally {
+            if (swallowOutput) {
+                String log = SystemLogHandler.stopCapture();
+                if (log != null && log.length() > 0) {
+                    if (getServletContext() != null) {
+                        getServletContext().log(log);
+                    } else {
+                        out.println(log);
+                    }
+                }
+            }
+        }
+        return servlet;
+
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws ClassNotFoundException 
+     */
+    @Override
+    public void servletSecurityAnnotationScan() throws ServletException {
+        if (getServlet() == null) {
+            Class<?> clazz = null;
+            try {
+                clazz = getParentClassLoader().loadClass(getServletClass());
+                processServletSecurityAnnotation(clazz);
+            } catch (ClassNotFoundException e) {
+                // Safe to ignore. No class means no annotations to process
+            }
+        } else {
+            if (servletSecurityAnnotationScanRequired) {
+                processServletSecurityAnnotation(getServlet().getClass());
+            }
+        }
+    }
+
+    private void processServletSecurityAnnotation(Class<?> clazz) {
+        // Calling this twice isn't harmful so no syncs
+        servletSecurityAnnotationScanRequired = false;
+
+        Context ctxt = (Context) getParent();
+        
+        if (ctxt.getIgnoreAnnotations()) {
+            return;
+        }
+
+        ServletSecurity secAnnotation =
+            clazz.getAnnotation(ServletSecurity.class);
+        if (secAnnotation != null) {
+            ctxt.addServletSecurity(
+                    new ApplicationServletRegistration(this, ctxt),
+                    new ServletSecurityElement(secAnnotation));
+        }
+    }
+
+    private synchronized void initServlet(Servlet servlet)
+            throws ServletException {
+        
+        if (instanceInitialized) return;
+
+        // Call the initialization method of this servlet
+        try {
+            instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT,
+                                              servlet);
+
+            if( Globals.IS_SECURITY_ENABLED) {
+
+                Object[] args = new Object[]{(facade)};
+                SecurityUtil.doAsPrivilege("init",
+                                           servlet,
+                                           classType,
+                                           args);
+                args = null;
+            } else {
+                servlet.init(facade);
+            }
+
+            instanceInitialized = true;
+
+            instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,
+                                              servlet);
+        } catch (UnavailableException f) {
+            instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,
+                                              servlet, f);
+            unavailable(f);
+            throw f;
+        } catch (ServletException f) {
+            instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,
+                                              servlet, f);
+            // If the servlet wanted to be unavailable it would have
+            // said so, so do not call unavailable(null).
+            throw f;
+        } catch (Throwable f) {
+            ExceptionUtils.handleThrowable(f);
+            getServletContext().log("StandardWrapper.Throwable", f );
+            instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,
+                                              servlet, f);
+            // If the servlet wanted to be unavailable it would have
+            // said so, so do not call unavailable(null).
+            throw new ServletException
+                (sm.getString("standardWrapper.initException", getName()), f);
+        }
+    }
+
+    /**
+     * Remove the specified initialization parameter from this servlet.
+     *
+     * @param name Name of the initialization parameter to remove
+     */
+    @Override
+    public void removeInitParameter(String name) {
+
+        synchronized (parameters) {
+            parameters.remove(name);
+        }
+        fireContainerEvent("removeInitParameter", name);
+
+    }
+
+
+    /**
+     * Remove a listener no longer interested in InstanceEvents.
+     *
+     * @param listener The listener to remove
+     */
+    @Override
+    public void removeInstanceListener(InstanceListener listener) {
+
+        instanceSupport.removeInstanceListener(listener);
+
+    }
+
+
+    /**
+     * Remove a mapping associated with the wrapper.
+     *
+     * @param mapping The pattern to remove
+     */
+    @Override
+    public void removeMapping(String mapping) {
+
+        synchronized (mappings) {
+            mappings.remove(mapping);
+        }
+        if(parent.getState().equals(LifecycleState.STARTED))
+            fireContainerEvent(REMOVE_MAPPING_EVENT, mapping);
+
+    }
+
+
+    /**
+     * Remove any security role reference for the specified role name.
+     *
+     * @param name Security role used within this servlet to be removed
+     */
+    @Override
+    public void removeSecurityReference(String name) {
+
+        synchronized (references) {
+            references.remove(name);
+        }
+        fireContainerEvent("removeSecurityReference", name);
+
+    }
+
+
+    /**
+     * Return a String representation of this component.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder();
+        if (getParent() != null) {
+            sb.append(getParent().toString());
+            sb.append(".");
+        }
+        sb.append("StandardWrapper[");
+        sb.append(getName());
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+    /**
+     * Process an UnavailableException, marking this servlet as unavailable
+     * for the specified amount of time.
+     *
+     * @param unavailable The exception that occurred, or <code>null</code>
+     *  to mark this servlet as permanently unavailable
+     */
+    @Override
+    public void unavailable(UnavailableException unavailable) {
+        getServletContext().log(sm.getString("standardWrapper.unavailable", getName()));
+        if (unavailable == null)
+            setAvailable(Long.MAX_VALUE);
+        else if (unavailable.isPermanent())
+            setAvailable(Long.MAX_VALUE);
+        else {
+            int unavailableSeconds = unavailable.getUnavailableSeconds();
+            if (unavailableSeconds <= 0)
+                unavailableSeconds = 60;        // Arbitrary default
+            setAvailable(System.currentTimeMillis() +
+                         (unavailableSeconds * 1000L));
+        }
+
+    }
+
+
+    /**
+     * Unload all initialized instances of this servlet, after calling the
+     * <code>destroy()</code> method for each instance.  This can be used,
+     * for example, prior to shutting down the entire servlet engine, or
+     * prior to reloading all of the classes from the Loader associated with
+     * our Loader's repository.
+     *
+     * @exception ServletException if an exception is thrown by the
+     *  destroy() method
+     */
+    @Override
+    public synchronized void unload() throws ServletException {
+
+        // Nothing to do if we have never loaded the instance
+        if (!singleThreadModel && (instance == null))
+            return;
+        unloading = true;
+
+        // Loaf a while if the current instance is allocated
+        // (possibly more than once if non-STM)
+        if (countAllocated.get() > 0) {
+            int nRetries = 0;
+            long delay = unloadDelay / 20;
+            while ((nRetries < 21) && (countAllocated.get() > 0)) {
+                if ((nRetries % 10) == 0) {
+                    log.info(sm.getString("standardWrapper.waiting",
+                                          countAllocated.toString()));
+                }
+                try {
+                    Thread.sleep(delay);
+                } catch (InterruptedException e) {
+                    // Ignore
+                }
+                nRetries++;
+            }
+        }
+
+        PrintStream out = System.out;
+        if (swallowOutput) {
+            SystemLogHandler.startCapture();
+        }
+
+        // Call the servlet destroy() method
+        try {
+            instanceSupport.fireInstanceEvent
+              (InstanceEvent.BEFORE_DESTROY_EVENT, instance);
+
+            if( Globals.IS_SECURITY_ENABLED) {
+                SecurityUtil.doAsPrivilege("destroy",
+                                           instance);
+                SecurityUtil.remove(instance);                           
+            } else {
+                instance.destroy();
+            }
+            
+            instanceSupport.fireInstanceEvent
+              (InstanceEvent.AFTER_DESTROY_EVENT, instance);
+
+            // Annotation processing
+            if (!((Context) getParent()).getIgnoreAnnotations()) {
+               ((StandardContext)getParent()).getInstanceManager().destroyInstance(instance);
+            }
+
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            instanceSupport.fireInstanceEvent
+              (InstanceEvent.AFTER_DESTROY_EVENT, instance, t);
+            instance = null;
+            instancePool = null;
+            nInstances = 0;
+            fireContainerEvent("unload", this);
+            unloading = false;
+            throw new ServletException
+                (sm.getString("standardWrapper.destroyException", getName()),
+                 t);
+        } finally {
+            // Write captured output
+            if (swallowOutput) {
+                String log = SystemLogHandler.stopCapture();
+                if (log != null && log.length() > 0) {
+                    if (getServletContext() != null) {
+                        getServletContext().log(log);
+                    } else {
+                        out.println(log);
+                    }
+                }
+            }
+        }
+
+        // Deregister the destroyed instance
+        instance = null;
+
+        if (isJspServlet && jspMonitorON != null ) {
+            Registry.getRegistry(null, null).unregisterComponent(jspMonitorON);
+        }
+
+        if (singleThreadModel && (instancePool != null)) {
+            try {
+                while (!instancePool.isEmpty()) {
+                    Servlet s = instancePool.pop();
+                    if (Globals.IS_SECURITY_ENABLED) {
+                        SecurityUtil.doAsPrivilege("destroy", s);
+                        SecurityUtil.remove(instance);                           
+                    } else {
+                        s.destroy();
+                    }
+                    // Annotation processing
+                    if (!((Context) getParent()).getIgnoreAnnotations()) {
+                       ((StandardContext)getParent()).getInstanceManager().destroyInstance(s);
+                    }
+                }
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                instancePool = null;
+                nInstances = 0;
+                unloading = false;
+                fireContainerEvent("unload", this);
+                throw new ServletException
+                    (sm.getString("standardWrapper.destroyException",
+                                  getName()), t);
+            }
+            instancePool = null;
+            nInstances = 0;
+        }
+
+        singleThreadModel = false;
+
+        unloading = false;
+        fireContainerEvent("unload", this);
+
+    }
+
+
+    // -------------------------------------------------- ServletConfig Methods
+
+
+    /**
+     * Return the initialization parameter value for the specified name,
+     * if any; otherwise return <code>null</code>.
+     *
+     * @param name Name of the initialization parameter to retrieve
+     */
+    @Override
+    public String getInitParameter(String name) {
+
+        return (findInitParameter(name));
+
+    }
+
+
+    /**
+     * Return the set of initialization parameter names defined for this
+     * servlet.  If none are defined, an empty Enumeration is returned.
+     */
+    @Override
+    public Enumeration<String> getInitParameterNames() {
+
+        synchronized (parameters) {
+            return (new Enumerator<String>(parameters.keySet()));
+        }
+
+    }
+
+
+    /**
+     * Return the servlet context with which this servlet is associated.
+     */
+    @Override
+    public ServletContext getServletContext() {
+
+        if (parent == null)
+            return (null);
+        else if (!(parent instanceof Context))
+            return (null);
+        else
+            return (((Context) parent).getServletContext());
+
+    }
+
+
+    /**
+     * Return the name of this servlet.
+     */
+    @Override
+    public String getServletName() {
+
+        return (getName());
+
+    }
+
+    public long getProcessingTime() {
+        return swValve.getProcessingTime();
+    }
+
+    public void setProcessingTime(long processingTime) {
+        swValve.setProcessingTime(processingTime);
+    }
+
+    public long getMaxTime() {
+        return swValve.getMaxTime();
+    }
+
+    public void setMaxTime(long maxTime) {
+        swValve.setMaxTime(maxTime);
+    }
+
+    public long getMinTime() {
+        return swValve.getMinTime();
+    }
+
+    public void setMinTime(long minTime) {
+        swValve.setMinTime(minTime);
+    }
+
+    public int getRequestCount() {
+        return swValve.getRequestCount();
+    }
+
+    public void setRequestCount(int requestCount) {
+        swValve.setRequestCount(requestCount);
+    }
+
+    public int getErrorCount() {
+        return swValve.getErrorCount();
+    }
+
+    public void setErrorCount(int errorCount) {
+           swValve.setErrorCount(errorCount);
+    }
+
+    /**
+     * Increment the error count used for monitoring.
+     */
+    @Override
+    public void incrementErrorCount(){
+        swValve.setErrorCount(swValve.getErrorCount() + 1);
+    }
+
+    public long getLoadTime() {
+        return loadTime;
+    }
+
+    public void setLoadTime(long loadTime) {
+        this.loadTime = loadTime;
+    }
+
+    public int getClassLoadTime() {
+        return classLoadTime;
+    }
+
+    @Override
+    public MultipartConfigElement getMultipartConfigElement() {
+        return multipartConfigElement;
+    }
+
+    @Override
+    public void setMultipartConfigElement(
+            MultipartConfigElement multipartConfigElement) {
+        this.multipartConfigElement = multipartConfigElement;
+    }
+
+    @Override
+    public boolean isAsyncSupported() {
+        return asyncSupported;
+    }
+    
+    @Override
+    public void setAsyncSupported(boolean asyncSupported) {
+        this.asyncSupported = asyncSupported;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return enabled;
+    }
+    
+    @Override
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    // -------------------------------------------------------- Package Methods
+
+
+    // -------------------------------------------------------- protected Methods
+
+
+    /**
+     * Return <code>true</code> if the specified class name represents a
+     * container provided servlet class that should be loaded by the
+     * server class loader.
+     *
+     * @param classname Name of the class to be checked
+     */
+    protected boolean isContainerProvidedServlet(String classname) {
+
+        if (classname.startsWith("org.apache.catalina.")) {
+            return (true);
+        }
+        try {
+            Class<?> clazz =
+                this.getClass().getClassLoader().loadClass(classname);
+            return (ContainerServlet.class.isAssignableFrom(clazz));
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            return (false);
+        }
+
+    }
+
+
+    protected Method[] getAllDeclaredMethods(Class<?> c) {
+
+        if (c.equals(javax.servlet.http.HttpServlet.class)) {
+            return null;
+        }
+
+        Method[] parentMethods = getAllDeclaredMethods(c.getSuperclass());
+
+        Method[] thisMethods = c.getDeclaredMethods();
+        if (thisMethods == null) {
+            return parentMethods;
+        }
+
+        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;
+    }
+
+
+    // ------------------------------------------------------ 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 {
+    
+        // Send j2ee.state.starting notification 
+        if (this.getObjectName() != null) {
+            Notification notification = new Notification("j2ee.state.starting", 
+                                                        this.getObjectName(), 
+                                                        sequenceNumber++);
+            broadcaster.sendNotification(notification);
+        }
+        
+        // Start up this component
+        super.startInternal();
+
+        setAvailable(0L);
+
+        // Send j2ee.state.running notification 
+        if (this.getObjectName() != null) {
+            Notification notification = 
+                new Notification("j2ee.state.running", this.getObjectName(), 
+                                sequenceNumber++);
+            broadcaster.sendNotification(notification);
+        }
+
+    }
+
+
+    /**
+     * 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 {
+
+        setAvailable(Long.MAX_VALUE);
+        
+        // Send j2ee.state.stopping notification 
+        if (this.getObjectName() != null) {
+            Notification notification = 
+                new Notification("j2ee.state.stopping", this.getObjectName(), 
+                                sequenceNumber++);
+            broadcaster.sendNotification(notification);
+        }
+        
+        // Shut down our servlet instance (if it has been initialized)
+        try {
+            unload();
+        } catch (ServletException e) {
+            getServletContext().log(sm.getString
+                      ("standardWrapper.unloadException", getName()), e);
+        }
+
+        // Shut down this component
+        super.stopInternal();
+
+        // Send j2ee.state.stoppped notification 
+        if (this.getObjectName() != null) {
+            Notification notification = 
+                new Notification("j2ee.state.stopped", this.getObjectName(), 
+                                sequenceNumber++);
+            broadcaster.sendNotification(notification);
+        }
+        
+        // Send j2ee.object.deleted notification 
+        Notification notification = 
+            new Notification("j2ee.object.deleted", this.getObjectName(), 
+                            sequenceNumber++);
+        broadcaster.sendNotification(notification);
+
+    }
+
+    
+    @Override
+    protected String getObjectNameKeyProperties() {
+
+        StringBuilder keyProperties =
+            new StringBuilder("j2eeType=Servlet,name=");
+        
+        keyProperties.append(getName());
+        
+        keyProperties.append(getWebModuleKeyProperties());
+
+        return keyProperties.toString();
+    }
+        
+
+    private String getWebModuleKeyProperties() {
+        
+        StringBuilder keyProperties = new StringBuilder(",WebModule=//");
+        String hostName = getParent().getParent().getName();
+        if (hostName == null) {
+            keyProperties.append("DEFAULT");
+        } else {
+            keyProperties.append(hostName);
+        }
+        
+        String contextName = ((Context) getParent()).getName();
+        if (!contextName.startsWith("/")) {
+            keyProperties.append('/');
+        }
+        keyProperties.append(contextName);
+
+        StandardContext ctx = null;
+        if (parent instanceof StandardContext) {
+            ctx = (StandardContext) getParent();
+        }
+        
+        keyProperties.append(",J2EEApplication=");
+        if (ctx == null) {
+            keyProperties.append("none");
+        } else {
+            keyProperties.append(ctx.getJ2EEApplication());
+        }
+        keyProperties.append(",J2EEServer=");
+        if (ctx == null) {
+            keyProperties.append("none");
+        } else {
+            keyProperties.append(ctx.getJ2EEServer());
+        }
+        
+        return keyProperties.toString();
+    }
+    
+    
+    /* Remove a JMX notficationListener 
+     * @see javax.management.NotificationEmitter#removeNotificationListener(javax.management.NotificationListener, javax.management.NotificationFilter, java.lang.Object)
+     */
+    @Override
+    public void removeNotificationListener(NotificationListener listener, 
+            NotificationFilter filter, Object object) throws ListenerNotFoundException {
+        broadcaster.removeNotificationListener(listener,filter,object);
+    }
+    
+    protected MBeanNotificationInfo[] notificationInfo;
+    
+    /* Get JMX Broadcaster Info
+     * @TODO use StringManager for international support!
+     * @TODO This two events we not send j2ee.state.failed and j2ee.attribute.changed!
+     * @see javax.management.NotificationBroadcaster#getNotificationInfo()
+     */
+    @Override
+    public MBeanNotificationInfo[] getNotificationInfo() {
+
+        if(notificationInfo == null) {
+            notificationInfo = new MBeanNotificationInfo[]{
+                    new MBeanNotificationInfo(new String[] {
+                    "j2ee.object.created"},
+                    Notification.class.getName(),
+                    "servlet is created"
+                    ), 
+                    new MBeanNotificationInfo(new String[] {
+                    "j2ee.state.starting"},
+                    Notification.class.getName(),
+                    "servlet is starting"
+                    ),
+                    new MBeanNotificationInfo(new String[] {
+                    "j2ee.state.running"},
+                    Notification.class.getName(),
+                    "servlet is running"
+                    ),
+                    new MBeanNotificationInfo(new String[] {
+                    "j2ee.state.stopped"},
+                    Notification.class.getName(),
+                    "servlet start to stopped"
+                    ),
+                    new MBeanNotificationInfo(new String[] {
+                    "j2ee.object.stopped"},
+                    Notification.class.getName(),
+                    "servlet is stopped"
+                    ),
+                    new MBeanNotificationInfo(new String[] {
+                    "j2ee.object.deleted"},
+                    Notification.class.getName(),
+                    "servlet is deleted"
+                    )
+            };
+        }
+
+        return notificationInfo;
+    }
+    
+    
+    /* Add a JMX-NotificationListener
+     * @see javax.management.NotificationBroadcaster#addNotificationListener(javax.management.NotificationListener, javax.management.NotificationFilter, java.lang.Object)
+     */
+    @Override
+    public void addNotificationListener(NotificationListener listener, 
+            NotificationFilter filter, Object object) throws IllegalArgumentException {
+        broadcaster.addNotificationListener(listener,filter,object);
+    }
+    
+    
+    /**
+     * Remove a JMX-NotificationListener 
+     * @see javax.management.NotificationBroadcaster#removeNotificationListener(javax.management.NotificationListener)
+     */
+    @Override
+    public void removeNotificationListener(NotificationListener listener) 
+        throws ListenerNotFoundException {
+        broadcaster.removeNotificationListener(listener);
+    }
+    
+    
+     // ------------------------------------------------------------- Attributes
+        
+        
+    public boolean isEventProvider() {
+        return false;
+    }
+    
+    public boolean isStateManageable() {
+        return false;
+    }
+    
+    public boolean isStatisticsProvider() {
+        return false;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardWrapperFacade.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardWrapperFacade.java
new file mode 100644
index 0000000..5e86335
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardWrapperFacade.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.core;
+
+
+import java.util.Enumeration;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+
+
+/**
+ * Facade for the <b>StandardWrapper</b> object.
+ *
+ * @author Remy Maucharat
+ * @version $Id: StandardWrapperFacade.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+public final class StandardWrapperFacade
+    implements ServletConfig {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Create a new facade around a StandardWrapper.
+     */
+    public StandardWrapperFacade(StandardWrapper config) {
+
+        super();
+        this.config = config;
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Wrapped config.
+     */
+    private ServletConfig config = null;
+
+
+    /**
+     * Wrapped context (facade).
+     */
+    private ServletContext context = null;
+
+
+    // -------------------------------------------------- ServletConfig Methods
+
+
+    @Override
+    public String getServletName() {
+        return config.getServletName();
+    }
+
+
+    @Override
+    public ServletContext getServletContext() {
+        if (context == null) {
+            context = config.getServletContext();
+            if ((context != null) && (context instanceof ApplicationContext))
+                context = ((ApplicationContext) context).getFacade();
+        }
+        return (context);
+    }
+
+
+    @Override
+    public String getInitParameter(String name) {
+        return config.getInitParameter(name);
+    }
+
+
+    @Override
+    public Enumeration<String> getInitParameterNames() {
+        return config.getInitParameterNames();
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardWrapperValve.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardWrapperValve.java
new file mode 100644
index 0000000..83e447d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/StandardWrapperValve.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.catalina.core;
+
+
+import java.io.IOException;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.comet.CometEvent;
+import org.apache.catalina.comet.CometProcessor;
+import org.apache.catalina.connector.ClientAbortException;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.valves.ValveBase;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.log.SystemLogHandler;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Valve that implements the default basic behavior for the
+ * <code>StandardWrapper</code> container implementation.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: StandardWrapperValve.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+final class StandardWrapperValve
+    extends ValveBase {
+
+    //------------------------------------------------------ Constructor
+    public StandardWrapperValve() {
+        super(true);
+    }
+    
+    // ----------------------------------------------------- Instance Variables
+
+
+    // Some JMX statistics. This valve is associated with a StandardWrapper.
+    // We expose the StandardWrapper as JMX ( j2eeType=Servlet ). The fields
+    // are here for performance.
+    private volatile long processingTime;
+    private volatile long maxTime;
+    private volatile long minTime = Long.MAX_VALUE;
+    private volatile int requestCount;
+    private volatile int errorCount;
+
+
+    /**
+     * The string manager for this package.
+     */
+    private static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Invoke the servlet we are managing, respecting the rules regarding
+     * servlet lifecycle and SingleThreadModel support.
+     *
+     * @param request Request to be processed
+     * @param response Response to be produced
+     *
+     * @exception IOException if an input/output error occurred
+     * @exception ServletException if a servlet error occurred
+     */
+    @Override
+    public final void invoke(Request request, Response response)
+        throws IOException, ServletException {
+
+        // Initialize local variables we may need
+        boolean unavailable = false;
+        Throwable throwable = null;
+        // This should be a Request attribute...
+        long t1=System.currentTimeMillis();
+        requestCount++;
+        StandardWrapper wrapper = (StandardWrapper) getContainer();
+        Servlet servlet = null;
+        Context context = (Context) wrapper.getParent();
+        
+        // Check for the application being marked unavailable
+        if (!context.getAvailable()) {
+            response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
+                           sm.getString("standardContext.isUnavailable"));
+            unavailable = true;
+        }
+
+        // Check for the servlet being marked unavailable
+        if (!unavailable && wrapper.isUnavailable()) {
+            container.getLogger().info(sm.getString("standardWrapper.isUnavailable",
+                    wrapper.getName()));
+            long available = wrapper.getAvailable();
+            if ((available > 0L) && (available < Long.MAX_VALUE)) {
+                response.setDateHeader("Retry-After", available);
+                response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
+                        sm.getString("standardWrapper.isUnavailable",
+                                wrapper.getName()));
+            } else if (available == Long.MAX_VALUE) {
+                response.sendError(HttpServletResponse.SC_NOT_FOUND,
+                        sm.getString("standardWrapper.notFound",
+                                wrapper.getName()));
+            }
+            unavailable = true;
+        }
+
+        // Allocate a servlet instance to process this request
+        try {
+            if (!unavailable) {
+                servlet = wrapper.allocate();
+            }
+        } catch (UnavailableException e) {
+            container.getLogger().error(
+                    sm.getString("standardWrapper.allocateException",
+                            wrapper.getName()), e);
+            long available = wrapper.getAvailable();
+            if ((available > 0L) && (available < Long.MAX_VALUE)) {
+                response.setDateHeader("Retry-After", available);
+                response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
+                           sm.getString("standardWrapper.isUnavailable",
+                                        wrapper.getName()));
+            } else if (available == Long.MAX_VALUE) {
+                response.sendError(HttpServletResponse.SC_NOT_FOUND,
+                           sm.getString("standardWrapper.notFound",
+                                        wrapper.getName()));
+            }
+        } catch (ServletException e) {
+            container.getLogger().error(sm.getString("standardWrapper.allocateException",
+                             wrapper.getName()), StandardWrapper.getRootCause(e));
+            throwable = e;
+            exception(request, response, e);
+        } catch (Throwable e) {
+            ExceptionUtils.handleThrowable(e);
+            container.getLogger().error(sm.getString("standardWrapper.allocateException",
+                             wrapper.getName()), e);
+            throwable = e;
+            exception(request, response, e);
+            servlet = null;
+        }
+
+        // Identify if the request is Comet related now that the servlet has been allocated
+        boolean comet = false;
+        if (servlet instanceof CometProcessor 
+                && request.getAttribute("org.apache.tomcat.comet.support") == Boolean.TRUE) {
+            comet = true;
+            request.setComet(true);
+        }
+        
+        // Acknowledge the request
+        try {
+            response.sendAcknowledgement();
+        } catch (IOException e) {
+            container.getLogger().warn(sm.getString("standardWrapper.acknowledgeException",
+                             wrapper.getName()), e);
+            throwable = e;
+            exception(request, response, e);
+        } catch (Throwable e) {
+            ExceptionUtils.handleThrowable(e);
+            container.getLogger().error(sm.getString("standardWrapper.acknowledgeException",
+                             wrapper.getName()), e);
+            throwable = e;
+            exception(request, response, e);
+            servlet = null;
+        }
+        MessageBytes requestPathMB = request.getRequestPathMB();
+        DispatcherType dispatcherType = DispatcherType.REQUEST;
+        if (request.getDispatcherType()==DispatcherType.ASYNC) dispatcherType = DispatcherType.ASYNC; 
+        request.setAttribute
+            (ApplicationFilterFactory.DISPATCHER_TYPE_ATTR,
+             dispatcherType);
+        request.setAttribute
+            (ApplicationFilterFactory.DISPATCHER_REQUEST_PATH_ATTR,
+             requestPathMB);
+        // Create the filter chain for this request
+        ApplicationFilterFactory factory =
+            ApplicationFilterFactory.getInstance();
+        ApplicationFilterChain filterChain =
+            factory.createFilterChain(request, wrapper, servlet);
+        
+        // Reset comet flag value after creating the filter chain
+        request.setComet(false);
+
+        // Call the filter chain for this request
+        // NOTE: This also calls the servlet's service() method
+        try {
+            if ((servlet != null) && (filterChain != null)) {
+                // Swallow output if needed
+                if (context.getSwallowOutput()) {
+                    try {
+                        SystemLogHandler.startCapture();
+                        if (request.isAsyncDispatching()) {
+                            //TODO SERVLET3 - async
+                            ((AsyncContextImpl)request.getAsyncContext()).doInternalDispatch(); 
+                        } else if (comet) {
+                            filterChain.doFilterEvent(request.getEvent());
+                            request.setComet(true);
+                        } else {
+                            filterChain.doFilter(request.getRequest(), 
+                                    response.getResponse());
+                        }
+                    } finally {
+                        String log = SystemLogHandler.stopCapture();
+                        if (log != null && log.length() > 0) {
+                            context.getLogger().info(log);
+                        }
+                    }
+                } else {
+                    if (request.isAsyncDispatching()) {
+                        //TODO SERVLET3 - async
+                        ((AsyncContextImpl)request.getAsyncContext()).doInternalDispatch();
+                    } else if (comet) {
+                        request.setComet(true);
+                        filterChain.doFilterEvent(request.getEvent());
+                    } else {
+                        filterChain.doFilter
+                            (request.getRequest(), response.getResponse());
+                    }
+                }
+
+            }
+        } catch (ClientAbortException e) {
+            throwable = e;
+            exception(request, response, e);
+        } catch (IOException e) {
+            container.getLogger().error(sm.getString(
+                    "standardWrapper.serviceException", wrapper.getName(),
+                    context.getName()), e);
+            throwable = e;
+            exception(request, response, e);
+        } catch (UnavailableException e) {
+            container.getLogger().error(sm.getString(
+                    "standardWrapper.serviceException", wrapper.getName(),
+                    context.getName()), e);
+            //            throwable = e;
+            //            exception(request, response, e);
+            wrapper.unavailable(e);
+            long available = wrapper.getAvailable();
+            if ((available > 0L) && (available < Long.MAX_VALUE)) {
+                response.setDateHeader("Retry-After", available);
+                response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
+                           sm.getString("standardWrapper.isUnavailable",
+                                        wrapper.getName()));
+            } else if (available == Long.MAX_VALUE) {
+                response.sendError(HttpServletResponse.SC_NOT_FOUND,
+                            sm.getString("standardWrapper.notFound",
+                                        wrapper.getName()));
+            }
+            // Do not save exception in 'throwable', because we
+            // do not want to do exception(request, response, e) processing
+        } catch (ServletException e) {
+            Throwable rootCause = StandardWrapper.getRootCause(e);
+            if (!(rootCause instanceof ClientAbortException)) {
+                container.getLogger().error(sm.getString(
+                        "standardWrapper.serviceExceptionRoot",
+                        wrapper.getName(), context.getName(), e.getMessage()),
+                        rootCause);
+            }
+            throwable = e;
+            exception(request, response, e);
+        } catch (Throwable e) {
+            ExceptionUtils.handleThrowable(e);
+            container.getLogger().error(sm.getString(
+                    "standardWrapper.serviceException", wrapper.getName(),
+                    context.getName()), e);
+            throwable = e;
+            exception(request, response, e);
+        }
+
+        // Release the filter chain (if any) for this request
+        if (filterChain != null) {
+            if (request.isComet()) {
+                // If this is a Comet request, then the same chain will be used for the
+                // processing of all subsequent events.
+                filterChain.reuse();
+            } else {
+                filterChain.release();
+            }
+        }
+
+        // Deallocate the allocated servlet instance
+        try {
+            if (servlet != null) {
+                wrapper.deallocate(servlet);
+            }
+        } catch (Throwable e) {
+            ExceptionUtils.handleThrowable(e);
+            container.getLogger().error(sm.getString("standardWrapper.deallocateException",
+                             wrapper.getName()), e);
+            if (throwable == null) {
+                throwable = e;
+                exception(request, response, e);
+            }
+        }
+
+        // If this servlet has been marked permanently unavailable,
+        // unload it and release this instance
+        try {
+            if ((servlet != null) &&
+                (wrapper.getAvailable() == Long.MAX_VALUE)) {
+                wrapper.unload();
+            }
+        } catch (Throwable e) {
+            ExceptionUtils.handleThrowable(e);
+            container.getLogger().error(sm.getString("standardWrapper.unloadException",
+                             wrapper.getName()), e);
+            if (throwable == null) {
+                throwable = e;
+                exception(request, response, e);
+            }
+        }
+        long t2=System.currentTimeMillis();
+
+        long time=t2-t1;
+        processingTime += time;
+        if( time > maxTime) maxTime=time;
+        if( time < minTime) minTime=time;
+
+    }
+
+
+    /**
+     * Process a Comet event. The main differences here are to not use sendError
+     * (the response is committed), to avoid creating a new filter chain
+     * (which would work but be pointless), and a few very minor tweaks. 
+     *
+     * @param request The servlet request to be processed
+     * @param response The servlet response to be created
+     *
+     * @exception IOException if an input/output error occurs, or is thrown
+     *  by a subsequently invoked Valve, Filter, or Servlet
+     * @exception ServletException if a servlet error occurs, or is thrown
+     *  by a subsequently invoked Valve, Filter, or Servlet
+     */
+    @Override
+    public void event(Request request, Response response, CometEvent event)
+        throws IOException, ServletException {
+        
+        // Initialize local variables we may need
+        Throwable throwable = null;
+        // This should be a Request attribute...
+        long t1=System.currentTimeMillis();
+        // FIXME: Add a flag to count the total amount of events processed ? requestCount++;
+        StandardWrapper wrapper = (StandardWrapper) getContainer();
+        Servlet servlet = null;
+        Context context = (Context) wrapper.getParent();
+
+        // Check for the application being marked unavailable
+        boolean unavailable = !context.getAvailable() || wrapper.isUnavailable();
+        
+        // Allocate a servlet instance to process this request
+        try {
+            if (!unavailable) {
+                servlet = wrapper.allocate();
+            }
+        } catch (UnavailableException e) {
+            // The response is already committed, so it's not possible to do anything
+        } catch (ServletException e) {
+            container.getLogger().error(sm.getString("standardWrapper.allocateException",
+                             wrapper.getName()), StandardWrapper.getRootCause(e));
+            throwable = e;
+            exception(request, response, e);
+        } catch (Throwable e) {
+            ExceptionUtils.handleThrowable(e);
+            container.getLogger().error(sm.getString("standardWrapper.allocateException",
+                             wrapper.getName()), e);
+            throwable = e;
+            exception(request, response, e);
+            servlet = null;
+        }
+
+        MessageBytes requestPathMB = request.getRequestPathMB();
+        request.setAttribute
+            (ApplicationFilterFactory.DISPATCHER_TYPE_ATTR,
+             DispatcherType.REQUEST);
+        request.setAttribute
+            (ApplicationFilterFactory.DISPATCHER_REQUEST_PATH_ATTR,
+             requestPathMB);
+        // Get the current (unchanged) filter chain for this request
+        ApplicationFilterChain filterChain = 
+            (ApplicationFilterChain) request.getFilterChain();
+
+        // Call the filter chain for this request
+        // NOTE: This also calls the servlet's event() method
+        try {
+            if ((servlet != null) && (filterChain != null)) {
+
+                // Swallow output if needed
+                if (context.getSwallowOutput()) {
+                    try {
+                        SystemLogHandler.startCapture();
+                        filterChain.doFilterEvent(request.getEvent());
+                    } finally {
+                        String log = SystemLogHandler.stopCapture();
+                        if (log != null && log.length() > 0) {
+                            context.getLogger().info(log);
+                        }
+                    }
+                } else {
+                    filterChain.doFilterEvent(request.getEvent());
+                }
+
+            }
+        } catch (ClientAbortException e) {
+            throwable = e;
+            exception(request, response, e);
+        } catch (IOException e) {
+            container.getLogger().error(sm.getString(
+                    "standardWrapper.serviceException", wrapper.getName(),
+                    context.getName()), e);
+            throwable = e;
+            exception(request, response, e);
+        } catch (UnavailableException e) {
+            container.getLogger().error(sm.getString(
+                    "standardWrapper.serviceException", wrapper.getName(),
+                    context.getName()), e);
+            // Do not save exception in 'throwable', because we
+            // do not want to do exception(request, response, e) processing
+        } catch (ServletException e) {
+            Throwable rootCause = StandardWrapper.getRootCause(e);
+            if (!(rootCause instanceof ClientAbortException)) {
+                container.getLogger().error(sm.getString(
+                        "standardWrapper.serviceExceptionRoot",
+                        wrapper.getName(), context.getName(), e.getMessage()),
+                        rootCause);
+            }
+            throwable = e;
+            exception(request, response, e);
+        } catch (Throwable e) {
+            ExceptionUtils.handleThrowable(e);
+            container.getLogger().error(sm.getString(
+                    "standardWrapper.serviceException", wrapper.getName(),
+                    context.getName()), e);
+            throwable = e;
+            exception(request, response, e);
+        }
+
+        // Release the filter chain (if any) for this request
+        if (filterChain != null) {
+            filterChain.reuse();
+        }
+
+        // Deallocate the allocated servlet instance
+        try {
+            if (servlet != null) {
+                wrapper.deallocate(servlet);
+            }
+        } catch (Throwable e) {
+            ExceptionUtils.handleThrowable(e);
+            container.getLogger().error(sm.getString("standardWrapper.deallocateException",
+                             wrapper.getName()), e);
+            if (throwable == null) {
+                throwable = e;
+                exception(request, response, e);
+            }
+        }
+
+        // If this servlet has been marked permanently unavailable,
+        // unload it and release this instance
+        try {
+            if ((servlet != null) &&
+                (wrapper.getAvailable() == Long.MAX_VALUE)) {
+                wrapper.unload();
+            }
+        } catch (Throwable e) {
+            ExceptionUtils.handleThrowable(e);
+            container.getLogger().error(sm.getString("standardWrapper.unloadException",
+                             wrapper.getName()), e);
+            if (throwable == null) {
+                throwable = e;
+                exception(request, response, e);
+            }
+        }
+
+        long t2=System.currentTimeMillis();
+
+        long time=t2-t1;
+        processingTime += time;
+        if( time > maxTime) maxTime=time;
+        if( time < minTime) minTime=time;
+
+    }
+
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Handle the specified ServletException encountered while processing
+     * the specified Request to produce the specified Response.  Any
+     * exceptions that occur during generation of the exception report are
+     * logged and swallowed.
+     *
+     * @param request The request being processed
+     * @param response The response being generated
+     * @param exception The exception that occurred (which possibly wraps
+     *  a root cause exception
+     */
+    private void exception(Request request, Response response,
+                           Throwable exception) {
+        request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, exception);
+        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+
+    }
+
+    public long getProcessingTime() {
+        return processingTime;
+    }
+
+    public void setProcessingTime(long processingTime) {
+        this.processingTime = processingTime;
+    }
+
+    public long getMaxTime() {
+        return maxTime;
+    }
+
+    public void setMaxTime(long maxTime) {
+        this.maxTime = maxTime;
+    }
+
+    public long getMinTime() {
+        return minTime;
+    }
+
+    public void setMinTime(long minTime) {
+        this.minTime = minTime;
+    }
+
+    public int getRequestCount() {
+        return requestCount;
+    }
+
+    public void setRequestCount(int requestCount) {
+        this.requestCount = requestCount;
+    }
+
+    public int getErrorCount() {
+        return errorCount;
+    }
+
+    public void setErrorCount(int errorCount) {
+        this.errorCount = errorCount;
+    }
+    
+    @Override
+    protected void initInternal() throws LifecycleException {
+        // NOOP - Don't register this Valve in JMX
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/ThreadLocalLeakPreventionListener.java b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ThreadLocalLeakPreventionListener.java
new file mode 100644
index 0000000..42a3eff
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/ThreadLocalLeakPreventionListener.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.core;
+
+import java.util.concurrent.Executor;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.ContainerEvent;
+import org.apache.catalina.ContainerListener;
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Host;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.Server;
+import org.apache.catalina.Service;
+import org.apache.catalina.connector.Connector;
+import org.apache.coyote.ProtocolHandler;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.res.StringManager;
+import org.apache.tomcat.util.threads.ThreadPoolExecutor;
+
+/**
+ * <p>
+ * A {@link LifecycleListener} that triggers the renewal of threads in Executor
+ * pools when a {@link Context} is being stopped to avoid thread-local related
+ * memory leaks.
+ * </p>
+ * <p>
+ * Note : active threads will be renewed one by one when they come back to the
+ * pool after executing their task, see
+ * {@link org.apache.tomcat.util.threads.ThreadPoolExecutor}.afterExecute().
+ * </p>
+ * 
+ * This listener must be declared in server.xml to be active.
+ * 
+ */
+public class ThreadLocalLeakPreventionListener implements LifecycleListener,
+        ContainerListener {
+
+    private static final Log log =
+        LogFactory.getLog(ThreadLocalLeakPreventionListener.class);
+
+    private volatile boolean serverStopping = false;
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    /**
+     * Listens for {@link LifecycleEvent} for the start of the {@link Server} to
+     * initialize itself and then for after_stop events of each {@link Context}.
+     */
+    @Override
+    public void lifecycleEvent(LifecycleEvent event) {
+        try {
+            Lifecycle lifecycle = event.getLifecycle();
+            if (Lifecycle.AFTER_START_EVENT.equals(event.getType()) &&
+                    lifecycle instanceof Server) {
+                // when the server starts, we register ourself as listener for
+                // all context
+                // as well as container event listener so that we know when new
+                // Context are deployed
+                Server server = (Server) lifecycle;
+                registerListenersForServer(server);
+            }
+
+            if (Lifecycle.BEFORE_STOP_EVENT.equals(event.getType()) &&
+                    lifecycle instanceof Server) {
+                // Server is shutting down, so thread pools will be shut down so
+                // there is no need to clean the threads
+                serverStopping = true;
+            }
+
+            if (Lifecycle.AFTER_STOP_EVENT.equals(event.getType()) &&
+                    lifecycle instanceof Context) {
+                stopIdleThreads((Context) lifecycle);
+            }
+        } catch (Exception e) {
+            String msg =
+                sm.getString(
+                    "threadLocalLeakPreventionListener.lifecycleEvent.error",
+                    event);
+            log.error(msg, e);
+        }
+    }
+
+    @Override
+    public void containerEvent(ContainerEvent event) {
+        try {
+            String type = event.getType();
+            if (Container.ADD_CHILD_EVENT.equals(type)) {
+                processContainerAddChild(event.getContainer(),
+                    (Container) event.getData());
+            } else if (Container.REMOVE_CHILD_EVENT.equals(type)) {
+                processContainerRemoveChild(event.getContainer(),
+                    (Container) event.getData());
+            }
+        } catch (Exception e) {
+            String msg =
+                sm.getString(
+                    "threadLocalLeakPreventionListener.containerEvent.error",
+                    event);
+            log.error(msg, e);
+        }
+
+    }
+
+    private void registerListenersForServer(Server server) {
+        for (Service service : server.findServices()) {
+            Engine engine = (Engine) service.getContainer();
+            engine.addContainerListener(this);
+            registerListenersForEngine(engine);
+        }
+
+    }
+
+    private void registerListenersForEngine(Engine engine) {
+        for (Container hostContainer : engine.findChildren()) {
+            Host host = (Host) hostContainer;
+            host.addContainerListener(this);
+            registerListenersForHost(host);
+        }
+    }
+
+    private void registerListenersForHost(Host host) {
+        for (Container contextContainer : host.findChildren()) {
+            Context context = (Context) contextContainer;
+            registerContextListener(context);
+        }
+    }
+
+    private void registerContextListener(Context context) {
+        context.addLifecycleListener(this);
+    }
+
+    protected void processContainerAddChild(Container parent, Container child) {
+        if (log.isDebugEnabled())
+            log.debug("Process addChild[parent=" + parent + ",child=" + child +
+                "]");
+
+        if (child instanceof Context) {
+            registerContextListener((Context) child);
+        } else if (child instanceof Engine) {
+            registerListenersForEngine((Engine) child);
+        } else if (child instanceof Host) {
+            registerListenersForHost((Host) child);
+        }
+
+    }
+
+    protected void processContainerRemoveChild(Container parent,
+        Container child) {
+
+        if (log.isDebugEnabled())
+            log.debug("Process removeChild[parent=" + parent + ",child=" +
+                child + "]");
+
+        if (child instanceof Context) {
+            Context context = (Context) child;
+            context.removeLifecycleListener(this);
+        } else if (child instanceof Host || child instanceof Engine) {
+            child.removeContainerListener(this);
+        }
+    }
+
+    /**
+     * Updates each ThreadPoolExecutor with the current time, which is the time
+     * when a context is being stopped.
+     * 
+     * @param context
+     *            the context being stopped, used to discover all the Connectors
+     *            of its parent Service.
+     */
+    private void stopIdleThreads(Context context) {
+        if (serverStopping) return;
+
+        if (context instanceof StandardContext &&
+            !((StandardContext) context).getRenewThreadsWhenStoppingContext()) {
+            log.debug("Not renewing threads when the context is stopping, "
+                + "it is configured not to do it.");
+            return;
+        }
+
+        Engine engine = (Engine) context.getParent().getParent();
+        Service service = engine.getService();
+        Connector[] connectors = service.findConnectors();
+        if (connectors != null) {
+            for (Connector connector : connectors) {
+                ProtocolHandler handler = connector.getProtocolHandler();
+                Executor executor = null;
+                if (handler != null) {
+                    executor = handler.getExecutor();
+                }
+
+                if (executor instanceof ThreadPoolExecutor) {
+                    ThreadPoolExecutor threadPoolExecutor =
+                        (ThreadPoolExecutor) executor;
+                    threadPoolExecutor.contextStopping();
+                } else if (executor instanceof StandardThreadExecutor) {
+                    StandardThreadExecutor stdThreadExecutor =
+                        (StandardThreadExecutor) executor;
+                    stdThreadExecutor.contextStopping();
+                }
+
+            }
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/core/mbeans-descriptors.xml b/bundles/org.apache.tomcat/src/org/apache/catalina/core/mbeans-descriptors.xml
new file mode 100644
index 0000000..b9253c2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/core/mbeans-descriptors.xml
@@ -0,0 +1,1843 @@
+<?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="ApplicationFilterConfig"
+         description="Wrapper that represents an individual servlet-filter definition"
+         domain="Catalina"
+         group="Filter"
+         type="org.apache.catalina.core.ApplicationFilterConfig">
+                
+     <attribute name="filterName"
+                description="The name used to reference the filter in web.xml"
+                type="java.lang.String"
+                writeable="false"/>    
+
+     <attribute name="filterClass"
+                description="Fully qualified class name of the filter object"
+                type="java.lang.String"
+                writeable="false"/>                               
+
+     <attribute name="filterInitParameterMap"
+                description="Return the initiaization parameters associated with this filter"
+                type="java.util.Map"
+                writeable="false" />
+                    
+  </mbean> 
+
+  <mbean name="NamingContextListener"
+         description="Helper class used to initialize and populate the JNDI context associated with each context and server"
+         domain="Catalina"
+         group="Listener"
+         type="org.apache.catalina.core.NamingContextListener">
+    
+    <attribute name="className"
+               description="Fully qualified class name of the managed object"
+               type="java.lang.String"
+               writeable="false"/>
+
+    <attribute name="name"
+               description="Name of the associated naming context"
+               type="java.lang.String"
+               writeable="false"/> 
+               
+  </mbean>
+
+  <mbean name="StandardContext"
+         description="Standard Context Component"
+         domain="Catalina"
+         group="Context"
+         type="org.apache.catalina.core.StandardContext"
+         className="org.apache.catalina.mbeans.ContextMBean">
+    
+    <attribute name="allowLinking"
+               description="Allow symlinking to outside the webapp root directory, if the webapp is an exploded directory"
+               is="true"
+               type="boolean"/>
+      
+    <attribute name="aliases"
+               description="List of resource aliases"
+               type="java.lang.String" />
+  
+    <attribute name="altDDName"
+               description="The alternate deployment descriptor name."
+               type="java.lang.String" />             
+
+    <attribute name="antiJARLocking"
+               description="Take care to not lock jar files"
+               type="boolean" />
+
+    <attribute name="antiResourceLocking"
+               description="Take care to not lock resources"
+               type="boolean" />
+
+    <attribute name="baseName"
+               description="The base name used for directories, WAR files (with .war appended) and context.xml files (with .xml appended)."
+               type="java.lang.String"
+               writeable="false"/>             
+
+    <attribute name="cacheMaxSize"
+               description="Maximum cache size in KB"
+               type="int"/>
+      
+    <attribute name="cacheObjectMaxSize"
+               description="Maximum cached object size in KB"
+               type="int"/>
+      
+    <attribute name="cacheTTL"
+               description="Time interval in ms between cache refeshes"
+               type="int"/>
+      
+    <attribute name="cachingAllowed"
+               description="Should we cache static resources for this webapp"
+               is="true"
+               type="boolean"/>
+      
+    <attribute name="children"
+               description="Object names of all children"
+               type="[Ljavax.management.ObjectName;"/>
+               
+    <attribute name="clearReferencesStatic"
+               description="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?"
+               type="boolean"/>
+               
+    <attribute name="clearReferencesStopThreads"
+               description="Should Tomcat attempt to terminate threads that have been started by the web application? Advisable to be used only in a development environment."
+               type="boolean"/>
+               
+    <attribute name="clearReferencesStopTimerThreads"
+               description="Should Tomcat attempt to terminate TimerThreads that have been started by the web application? Advisable to be used only in a development environment."
+               type="boolean"/>
+               
+    <attribute name="configFile"
+               description="Location of the context.xml resource or file"
+               type="java.net.URL"/>
+               
+    <attribute name="configured"
+               description="The correctly configured flag for this Context."
+               type="boolean"
+               writeable="false" />
+               
+    <attribute name="cookies"
+               description="Should we attempt to use cookies for session id communication?"
+               type="boolean"/>
+      
+    <attribute name="compilerClasspath"
+               description="The compiler classpath to use"
+               type="java.lang.String"/>
+      
+    <attribute name="crossContext"
+               description="Should we allow the ServletContext.getContext() method to access the context of other web applications in this server?"
+               type="boolean"/>
+
+    <attribute name="defaultContextXml"
+               description="Location of the default context.xml resource or file"
+               type="java.lang.String"/>
+               
+    <attribute name="defaultWebXml"
+               description="Location of the default web.xml resource or file"
+               type="java.lang.String"/>
+               
+    <attribute name="delegate"
+               description=""
+               type="boolean"/>
+               
+    <attribute name="deploymentDescriptor"
+               description="String deployment descriptor "
+               type="java.lang.String"
+               writeable="false" />
+          
+    <attribute name="displayName"
+               description="The display name of this web application"
+               type="java.lang.String"/>
+               
+    <attribute name="distributable"
+               description="The distributable flag for this web application."
+               type="boolean"/>
+                     
+    <attribute name="docBase"
+               description="The document root for this web application"
+               type="java.lang.String"/>
+               
+    <attribute name="encodedPath"
+               description="The encoded path"
+               type="java.lang.String"
+               writeable="false" />
+      
+    <attribute name="eventProvider"
+               description="Event provider support for this managed object"
+               is="true"
+               type="boolean"
+               writeable="false" />
+          
+    <attribute name="ignoreAnnotations"
+               description="Ignore annotations flag."
+               type="boolean" />
+               
+    <attribute name="instanceManager"
+               description="Object that creates and destroys servlets, filters, and listeners. Include dependency injection and postConstruct/preDestory handling"
+               type="org.apache.catalina.instanceManagement.InstanceManager" />
+                              
+    <attribute name="javaVMs"
+               description="The Java virtual machines on which this module is running"
+               type="[Ljava.lang.String;"/>
+
+    <attribute name="loader"
+               description="Associated loader."
+               type="org.apache.catalina.Loader" />
+               
+    <attribute name="logEffectiveWebXml"
+               description="Should the effective web.xml be logged when the context starts?"
+               type="boolean" />
+      
+    <attribute name="logger"
+               description="Associated logger."
+               type="org.apache.commons.logging.Log" />
+      
+    <attribute name="managedResource"
+               description="The managed resource this MBean is associated with"
+               type="java.lang.Object"/>
+      
+    <attribute name="manager"
+               description="Associated manager."
+               type="org.apache.catalina.Manager" />
+      
+    <attribute name="mappingObject"
+               description="The object used for mapping"
+               type="java.lang.Object"/>
+      
+    <attribute name="namingContextListener"
+               description="Associated naming context listener."
+               type="org.apache.catalina.core.NamingContextListener" />
+      
+    <attribute name="objectName"
+               description="Name of the object"
+               type="java.lang.String"
+               writeable="false" />
+          
+    <attribute name="originalDocBase"
+               description="The original document root for this web application"
+               type="java.lang.String" />
+      
+    <attribute name="override"
+               description="The default context.xml override flag for this web application"
+               type="boolean"/>
+      
+    <attribute name="name"
+               description="The name of this Context"
+               type="java.lang.String"/>
+               
+    <attribute name="parentClassLoader"
+               description="Parent class loader."
+               type="java.lang.ClassLoader" />
+      
+    <attribute name="path"
+               description="The context path for this Context"
+               type="java.lang.String"/>
+               
+    <attribute name="paused"
+               description="The request processing pause flag (while reloading occurs)"
+               type="boolean"
+               writeable="false" />
+               
+    <attribute name="privileged"
+               description="Access to tomcat internals"
+               type="boolean"/>
+               
+    <attribute name="processingTime"
+               description="Cumulative execution times of all servlets in this context"
+               type="long"
+               writeable="false" />
+               
+    <attribute name="publicId"
+               description="The public identifier of the DTD for the web application deployment descriptor version that is being parsed"
+               type="java.lang.String"
+               writeable="false" />
+      
+    <attribute name="realm"
+               description="Associated realm."
+               type="org.apache.catalina.Realm" />
+      
+    <attribute name="reloadable"
+               description="The reloadable flag for this web application"
+               type="boolean"/>
+
+    <attribute name="renewThreadsWhenStoppingContext"
+               description="Should Tomcat renew the threads of the thread pool when the application is stopped to avoid memory leaks because of uncleaned ThreadLocal variables." 
+               type="boolean"/>
+
+    <attribute name="saveConfig"
+               description="Should the configuration be written as needed on startup"
+               is="true"
+               type="boolean"/>
+      
+    <attribute name="server"
+               description="The J2EE Server this module is deployed on"
+               type="java.lang.String"/>
+                              
+    <attribute name="servlets"
+               description="JSR77 list of servlets"
+               type="[Ljava.lang.String;"
+               writeable="false"/>               
+                 
+    <attribute name="sessionCookieName"
+               description="The name to use for session cookies.'null' indicates that the name is controlled by the application."
+               type="java.lang.String"/>
+               
+    <attribute name="sessionCookieDomain"
+               description="The domain to use for session cookies.'null' indicates that the domain is controlled by the application."
+               type="java.lang.String"/>               
+               
+    <attribute name="sessionCookiePath"
+               description="The path to use for session cookies.'null' indicates that the path is controlled by the application."
+               type="java.lang.String"/> 
+               
+    <attribute name="sessionTimeout"
+               description="The session timeout (in minutes) for this web application"
+               type="int"/>                                            
+
+    <attribute name="startTime"
+               description="Time (in milliseconds since January 1, 1970, 00:00:00) when this context was started"
+               type="long"
+               writeable="false" />
+          
+    <attribute name="startupTime"
+               description="Time (in milliseconds) it took to start this context"
+               type="long"/>
+          
+    <attribute name="stateManageable"
+               description="State management support for this managed object"
+               is="true"
+               type="boolean"
+               writeable="false" />         
+
+    <attribute name="stateName"
+               description="The name of the LifecycleState that this component is currently in"
+               type="java.lang.String"
+               writeable="false"/>
+          
+    <attribute name="staticResources"
+               description="Static resources associated with the context."
+               type="javax.naming.directory.DirContext"
+               writeable="false"/>
+               
+    <attribute name="statisticsProvider"
+               description="Performance statistics support for this managed object"
+               is="true"
+               type="boolean"
+               writeable="false" />
+                     
+    <attribute name="swallowOutput"
+               description="Flag to set to cause the system.out and system.err to be redirected to the logger when executing a servlet"
+               type="boolean"/>   
+                                           
+    <attribute name="tldNamespaceAware"
+               description="Should TLD XML namespace validation be turned on?"
+               type="boolean"/>
+               
+    <attribute name="tldScanTime"
+               description="Time spend scanning jars for TLDs for this context"
+               type="long"/>
+               
+    <attribute name="tldValidation"
+               description="Should TLD XML validation be turned on?"
+               type="boolean"/>
+               
+    <attribute name="unloadDelay"
+               description="Amount of ms that the container will wait for servlets to unload"
+               type="long"/>
+               
+    <attribute name="unpackWAR"
+               description="Unpack WAR property"
+               type="boolean"/>
+               
+    <attribute name="useHttpOnly"
+               description="Indicates that session cookies should use HttpOnly"
+               type="boolean"/>
+
+    <attribute name="useNaming"
+               description="Create a JNDI naming context for this application?"
+               is="true"
+               type="boolean"/>
+
+    <attribute name="webappVersion"
+               description="The version of this web application - used in parallel deployment to differentiate different versions of the same web application"
+               type="java.lang.String"
+               writeable="false"/>
+               
+    <attribute name="welcomeFiles"
+               description="The welcome files for this context"
+               type="[Ljava.lang.String;"
+               writeable="false"/>
+      
+    <attribute name="workDir"
+               description="The pathname to the work directory for this context"
+               type="java.lang.String"/>
+               
+    <attribute name="xmlValidation"
+               description="Should XML validation be turned on?"
+               type="boolean"/>
+               
+    <attribute name="xmlNamespaceAware"
+               description="Should XML namespace validation be turned on?"
+               type="boolean"/>
+               
+    <operation name="addApplicationListener"
+               description="Add a new Listener class name to the set of Listeners configured for this application."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="listener"
+                 description="Java class name of a listener class"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="addApplicationParameter"
+               description="Add a new application parameter for this application."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="listener"
+                 description="Java class name of a listener class"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="addChild"
+               description="Add a child to this Context"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="type"
+                 description="Type(classname) of the new child to be added"
+                 type="java.lang.String"/>
+      <parameter name="name"
+                 description="Name of the child to be added"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="addInstanceListener"
+               description="Add the classname of an InstanceListener to be added to each Wrapper appended to this Context."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="listener"
+                 description="Java class name of an InstanceListener class"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="addLifecycleListener"
+               description="Add a lifecycle listener to this Context"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="type"
+                 description="Type(classname) of the new lifecycle listener to be added"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="addLocaleEncodingMappingParameter"
+               description="Add a Locale Encoding Mapping"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="locale"
+                 description="Locale to map an encoding for"
+                 type="java.lang.String"/>
+      <parameter name="encoding"
+                 description="Encoding to be used for a give locale"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation name="addMimeMapping"
+               description="Add a new MIME mapping, replacing any existing mapping for the specified extension."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="extension"
+                 description="Filename extension being mapped"
+                 type="java.lang.String"/>
+      <parameter name="mimeType"
+                 description="Corresponding MIME type"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="addParameter"
+               description="Add a new context initialization parameter, replacing any existing value for the specified name."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="name"
+                 description="Name of the new parameter"
+                 type="java.lang.String"/>
+      <parameter name="value"
+                 description="Value of the new  parameter"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="addRoleMapping"
+               description="Add a security role reference for this web application."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="role"
+                 description="Security role used in the application"
+                 type="java.lang.String"/>
+      <parameter name="link"
+                 description="Actual security role to check for"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation name="addSecurityRole"
+               description="Add a new security role for this web application."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="role"
+                 description="New security role"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="addServletMapping"
+               description="Add a new servlet mapping, replacing any existing mapping for the specified pattern."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="pattern"
+                 description="URL pattern to be mapped"
+                 type="java.lang.String"/>
+      <parameter name="name"
+                 description="Name of the corresponding servlet to execute"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="addServletMapping"
+               description="Add a new servlet mapping, replacing any existing mapping for the specified pattern."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="pattern"
+                 description="URL pattern to be mapped"
+                 type="java.lang.String"/>
+      <parameter name="name"
+                 description="Name of the corresponding servlet to execute"
+                 type="java.lang.String"/>
+      <parameter name="jspWildcard"
+                 description="'true' if name identifies the JspServlet and pattern contains a wildcard; 'false' otherwise"
+                 type="boolean"/>
+    </operation>
+    
+    <operation name="addValve"
+               description="Add a valve to this Context"
+               impact="ACTION"
+               returnType="java.lang.String">
+      <parameter name="valveType"
+                 description="Type(classname) of the new valve to be added"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="addWatchedResource"
+               description=" Add a resource which will be watched for reloading by the host auto deployer."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="name"
+                 description="Path to the resource, relative to docBase"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="addWelcomeFile"
+               description="Add a new welcome file to the set recognized by this Context."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="name"
+                 description="New welcome file name"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="addWrapperLifecycle"
+               description="Add the classname of a LifecycleListener to be added to each Wrapper appended to this Context."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="listener"
+                 description="Java class name of a LifecycleListener class"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="addWrapperListener"
+               description="Add the classname of a ContainerListener to be added to each Wrapper appended to this Context."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="listener"
+                 description="Java class name of a ContainerListener class"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="destroy"
+               description="Destroy the context"
+               impact="ACTION"
+               returnType="void">
+    </operation>
+    
+    <operation name="findApplicationListeners"
+               description="Return the set of application listener class names configured for this application."
+               impact="INFO"
+               returnType="[Ljava.lang.String;">    
+    </operation>
+    
+    <operation name="findApplicationParameters"
+               description="Return the set of application parameters for this application."
+               impact="INFO"
+               returnType="java.lang.String">    
+    </operation>
+    
+    <operation name="findConstraints"
+               description="Return the set of security constraints for this web application. If there are none, a zero-length array is returned."
+               impact="INFO"
+               returnType="java.lang.String">    
+    </operation>
+    
+    <operation name="findContainerListenerNames"
+               description="Return the set of container listener class names configured for this application."
+               impact="INFO"
+               returnType="[Ljava.lang.String;">    
+    </operation>
+    
+    <operation name="findErrorPage"
+               description="Return the error page entry for the specified HTTP error code, if any; otherwise return null"
+               impact="ACTION"
+               returnType="java.lang.String">
+      <parameter name="errorCode"
+                 description="Error code to look up"
+                 type="int"/>
+    </operation>
+    
+    <operation name="findErrorPage"
+               description="Return the error page entry for the specified Java exception type, if any; otherwise return null."
+               impact="ACTION"
+               returnType="java.lang.String">
+      <parameter name="exceptionType"
+                 description="Exception type to look up"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="findErrorPages"
+               description="Return the set of defined error pages for all specified error codes and exception types."
+               impact="INFO"
+               returnType="[Ljava.lang.String;">    
+    </operation>
+    
+    <operation name="findFilterDef"
+               description="Return the filter definition for the specified filter name, if any; otherwise return null."
+               impact="ACTION"
+               returnType="java.lang.String">
+      <parameter name="exceptionType"
+                 description="Exception type to look up"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="findFilterDefs"
+               description="Return the set of defined filters for this Context."
+               impact="INFO"
+               returnType="[Ljava.lang.String;">    
+    </operation>
+    
+    <operation name="findFilterMaps"
+               description="Return the set of filter mappings for this Context."
+               impact="INFO"
+               returnType="[Ljava.lang.String;">    
+    </operation>
+    
+    <operation name="findInstanceListeners"
+               description="Return the set of InstanceListener classes that will be added to newly created Wrappers automatically."
+               impact="INFO"
+               returnType="[Ljava.lang.String;">    
+    </operation>
+    
+    <operation name="findLifecycleListenerNames"
+               description="Return the set of lifecycle listener class names configured for this application."
+               impact="INFO"
+               returnType="[Ljava.lang.String;">    
+    </operation>
+    
+    <operation name="findMimeMapping"
+               description="Return the MIME type to which the specified extension is mapped, if any; otherwise return null."
+               impact="ACTION"
+               returnType="java.lang.String">
+      <parameter name="extension"
+                 description="Extension to map to a MIME type"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="findMimeMappings"
+               description="Return the extensions for which MIME mappings are defined."
+               impact="ACTION"
+               returnType="[Ljava.lang.String;">
+    </operation>
+    
+    <operation name="findParameter"
+               description="Return the value for the specified context initialization parameter name, if any; otherwise return null."
+               impact="ACTION"
+               returnType="java.lang.String">
+      <parameter name="name"
+                 description="Name of the parameter to return"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="findParameters"
+               description="Return the names of all defined context initialization parameters for this Context."
+               impact="ACTION"
+               returnType="[Ljava.lang.String;">
+    </operation>
+    
+    <operation name="findRoleMapping"
+               description="For the given security role (as used by an application), return the corresponding role name (as defined by the underlying Realm) if there is one.  Otherwise, return the specified role unchanged."
+               impact="ACTION"
+               returnType="java.lang.String">
+      <parameter name="role"
+                 description="Security role to map"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="findSecurityRole"
+               description="Return 'true' if the specified security role is defined for this application; otherwise return 'false'."
+               impact="ACTION"
+               returnType="boolean">
+      <parameter name="role"
+                 description="Security role to verify"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="findSecurityRoles"
+               description="Return the security roles defined for this application."
+               impact="ACTION"
+               returnType="[Ljava.lang.String;">
+    </operation>
+    
+    <operation name="findServletMapping"
+               description="Return the servlet name mapped by the specified pattern.."
+               impact="ACTION"
+               returnType="java.lang.String">
+      <parameter name="pattern"
+                 description="Pattern for which a mapping is requested"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="findServletMappings"
+               description="Return the patterns of all defined servlet mappings for this Context."
+               impact="ACTION"
+               returnType="[Ljava.lang.String;">
+    </operation>
+    
+    <operation name="findStatusPage"
+               description="Return the context-relative URI of the error page for the specified HTTP status code."
+               impact="ACTION"
+               returnType="java.lang.String">
+      <parameter name="status"
+                 description="HTTP status code to look up"
+                 type="int"/>
+    </operation>
+    
+    <operation name="findStatusPages"
+               description="Return the set of HTTP status codes for which error pages have been specified."
+               impact="ACTION"
+               returnType="[Lint">
+    </operation>
+    
+    <operation name="findWatchedResources"
+               description="Return the set of watched resources for this Context."
+               impact="ACTION"
+               returnType="[Ljava.lang.String;">
+    </operation>
+    
+    <operation name="findWelcomeFile"
+               description="Return 'true' if the specified welcome file is defined for this Context; otherwise return 'false'."
+               impact="ACTION"
+               returnType="boolean">
+      <parameter name="name"
+                 description="Welcome file to verify"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="findWelcomeFiles"
+               description="Return the set of welcome files defined for this Context."
+               impact="ACTION"
+               returnType="[Ljava.lang.String;">
+    </operation>
+    
+    <operation name="findWrapperLifecycles"
+               description="Return the set of LifecycleListener classes that will be added to newly created Wrappers automatically."
+               impact="ACTION"
+               returnType="[Ljava.lang.String;">
+    </operation>
+    
+    <operation name="findWrapperListeners"
+               description="Return the set of ContainerListener classes that will be added to newly created Wrappers automatically."
+               impact="ACTION"
+               returnType="[Ljava.lang.String;">
+    </operation>
+    
+    <operation name="init"
+               description="Register the context into the running server"
+               impact="ACTION"
+               returnType="void">
+    </operation>
+    
+    <operation name="start"
+               description="Start the context"
+               impact="ACTION"
+               returnType="void">
+    </operation>
+    
+    <operation name="stop"
+               description="Stop the context"
+               impact="ACTION"
+               returnType="void">
+    </operation>
+    
+    <operation name="reload"
+               description="Reload the webapplication"
+               impact="ACTION"
+               returnType="void">
+    </operation>
+    
+    <operation name="removeApplicationListener"
+               description="Remove the specified application listener class from the set of listeners for this application."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="listener"
+                 description="Java class name of the listener to be removed"
+                 type="java.lang.String"/>
+    </operation>   
+    
+    <operation name="removeApplicationParameter"
+               description="Remove the application parameter with the specified name from the set for this application."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="name"
+                 description="Name of the application parameter to remove"
+                 type="java.lang.String"/>
+    </operation> 
+    
+    <operation name="removeChild"
+               description="Remove a child from this Context"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="name"
+                 description="Name of the existing child Container to be removed"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="removeInstanceListener"
+               description="Remove the application parameter with the specified name from the set for this application."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="listener"
+                 description="Class name of an InstanceListener class to be removed"
+                 type="java.lang.String"/>
+    </operation>        
+    
+    <operation name="removeLifecycleListeners"
+               description="Removes lifecycle listeners of given class type from this Context"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="listener"
+                 description="Type(classname) of the lifecycle listeners to be removed"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="removeMimeMapping"
+               description="Remove the MIME mapping for the specified extension, if it exists; otherwise, no action is taken.."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="extension"
+                 description="Extension to remove the mapping for"
+                 type="java.lang.String"/>
+    </operation> 
+    
+    <operation name="removeParameter"
+               description="Remove the context initialization parameter with the specified name, if it exists; otherwise, no action is taken."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="name"
+                 description="Name of the parameter to remove"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="removeRoleMapping"
+               description="Remove any security role reference for the specified name"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="role"
+                 description="Security role (as used in the application) to remove"
+                 type="java.lang.String"/>
+    </operation>  
+    
+    <operation name="removeSecurityRole"
+               description="Remove any security role with the specified name."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="role"
+                 description="Security role to remove"
+                 type="java.lang.String"/>
+    </operation> 
+    
+    <operation name="removeServletMapping"
+               description="Remove any servlet mapping for the specified pattern, if it exists; otherwise, no action is taken."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="pattern"
+                 description="URL pattern of the mapping to remove"
+                 type="java.lang.String"/>
+    </operation> 
+    
+    <operation name="removeValve"
+               description="Remove a valve from this Context"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="valveName"
+                 description="Objectname of the valve to be removed"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="removeWatchedResource"
+               description="Remove the specified watched resource name from the list associated with this Context."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="name"
+                 description="Name of the watched resource to be removed"
+                 type="java.lang.String"/>
+    </operation> 
+    
+    <operation name="removeWelcomeFile"
+               description="Remove the specified welcome file name from the list recognized by this Context."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="name"
+                 description="Name of the welcome file to be removed"
+                 type="java.lang.String"/>
+    </operation> 
+    
+    <operation name="removeWrapperLifecycle"
+               description="Remove a class name from the set of LifecycleListener classes that will be added to newly created Wrappers."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="listener"
+                 description="Class name of a LifecycleListener class to be removed"
+                 type="java.lang.String"/>
+    </operation> 
+    
+    <operation name="removeWrapperListener"
+               description="Remove a class name from the set of ContainerListener classes that will be added to newly created Wrappers."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="listener"
+                 description="Class name of a ContainerListener class to be removed"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="start" 
+               description="Start" 
+               impact="ACTION" 
+               returnType="void" /> 
+
+    <operation name="stop" 
+               description="Stop" 
+               impact="ACTION" 
+               returnType="void" /> 
+    
+  </mbean>
+  
+  <mbean name="StandardContextValve"
+         description="Valve that implements the default basic behavior for the
+         StandardContext container implementation"
+         domain="Catalina"
+         group="Valve"
+         type="org.apache.catalina.core.StandardContextValve">
+    
+    <attribute name="asyncSupported"
+               description="Does this valve support async reporting?"
+               is="true"
+               type="boolean"/>
+
+    <attribute name="className"
+               description="Fully qualified class name of the managed object"
+               type="java.lang.String"
+               writeable="false"/>               
+
+    <attribute name="stateName"
+               description="The name of the LifecycleState that this component is currently in"
+               type="java.lang.String"
+               writeable="false"/>      
+  </mbean>
+  
+  <mbean name="StandardEngine"
+         type="org.apache.catalina.core.StandardEngine"
+         description="Standard Engine Component"
+         domain="Catalina"
+         group="Engine"
+         className="org.apache.catalina.mbeans.ContainerMBean">
+    
+    <attribute name="backgroundProcessorDelay"
+               description="The processor delay for this component."
+               type="int"/>  
+         
+    <attribute name="baseDir"
+               description="Base dir for this engine, typically same as catalina.base system property"
+               type="java.lang.String"/>
+    
+    <attribute name="defaultHost"
+               description="Name of the default Host for this Engine"
+               type="java.lang.String"/>
+               
+    <attribute name="jvmRoute"
+               description="Route used for load balancing"
+               type="java.lang.String"/>
+      
+    <attribute name="managedResource"
+               description="The managed resource this MBean is associated with"
+               type="java.lang.Object"/>
+      
+    <attribute name="name"
+               description="Unique name of this Engine"
+               type="java.lang.String"/>          
+      
+    <attribute name="realm"
+               description="Associated realm."
+               type="org.apache.catalina.Realm" />    
+               
+    <attribute name="startChildren"
+               description="Will children be started automatically when they are added."
+               type="boolean"/>  
+
+    <attribute name="stateName"
+               description="The name of the LifecycleState that this component is currently in"
+               type="java.lang.String"
+               writeable="false"/>
+               
+    <operation name="addChild"
+               description="Add a virtual host"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="type"
+                 description="Type(classname) of the new child to be added"
+                 type="java.lang.String"/>
+      <parameter name="name"
+                 description="Name of the child to be added"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="addLifecycleListener"
+               description="Add a lifecycle listener to this Engine"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="type"
+                 description="Type(classname) of the new lifecycle listener to be added"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="addValve"
+               description="Add a valve to this Engine"
+               impact="ACTION"
+               returnType="java.lang.String">
+      <parameter name="valveType"
+                 description="Type(classname) of the new valve to be added"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="destroy" 
+               description="Destroy" 
+               impact="ACTION" 
+               returnType="void" />
+               
+    <operation name="init" 
+               description="Init" 
+               impact="ACTION" 
+               returnType="void" />
+    
+    <operation name="removeChild"
+               description="Remove a child(Host) from this Engine"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="name"
+                 description="Name of the existing child Container to be removed"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="removeLifecycleListeners"
+               description="Removes lifecycle listeners of given class type from this Engine"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="listener"
+                 description="Type(classname) of the lifecycle listeners to be removed"
+                 type="java.lang.String"/>
+    </operation>    
+    
+    <operation name="removeValve"
+               description="Remove a valve from this Engine"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="valveName"
+                 description="Objectname of the valve to be removed"
+                 type="java.lang.String"/>
+    </operation>  
+    
+    <operation name="start" 
+               description="Start" 
+               impact="ACTION" 
+               returnType="void" /> 
+    
+    <operation name="stop" 
+               description="Stop" 
+               impact="ACTION" 
+               returnType="void" />
+      
+  </mbean>
+
+  
+  <mbean name="StandardEngineValve"
+         description="Valve that implements the default basic behavior for the
+         StandardEngine container implementation"
+         domain="Catalina"
+         group="Valve"
+         type="org.apache.catalina.core.StandardEngineValve">
+    
+    <attribute name="asyncSupported"
+               description="Does this valve support async reporting?"
+               is="true"
+               type="boolean"/>
+    
+    <attribute name="className"
+               description="Fully qualified class name of the managed object"
+               type="java.lang.String"
+               writeable="false"/>
+
+    <attribute name="stateName"
+               description="The name of the LifecycleState that this component is currently in"
+               type="java.lang.String"
+               writeable="false"/>
+  </mbean>
+  
+  <mbean name="StandardHost"
+         description="Standard Host Component"
+         domain="Catalina"
+         group="Host"
+         type="org.apache.catalina.core.StandardHost"
+         className="org.apache.catalina.mbeans.ContainerMBean">
+    
+    <attribute name="aliases"
+               description="Host aliases"
+               type="[Ljava.lang.String;"/>
+    
+    <attribute name="appBase"
+               description="The application root for this Host"
+               type="java.lang.String"/>
+      
+    <attribute name="autoDeploy"
+               description="The auto deploy flag for this Host"
+               type="boolean"/>
+               
+    <attribute name="backgroundProcessorDelay"
+               description="The processor delay for this component."
+               type="int"/>
+               
+    <attribute name="children"
+               description="Object names of all children"
+               type="[Ljavax.management.ObjectName;"/> 
+                 
+    <attribute name="configClass"
+               description="The configuration class for contexts"
+               type="java.lang.String"/>
+               
+    <attribute name="contextClass"
+               description="The Java class name of the default Context implementation class for deployed web applications."
+               type="java.lang.String"
+               writeable="false" />
+          
+    <attribute name="copyXML"
+               description="Should XML files be copied to $CATALINA_BASE/conf/{engine}/{host} by default when a web application is deployed?"
+               is="true"
+               type="boolean"/>
+               
+    <attribute name="createDirs"
+               description="Should we create directories upon startup for appBase and xmlBase? "
+               type="boolean"/>
+      
+    <attribute name="deployIgnore"
+               description="Paths within appBase ignored for automatic deployment"
+               type="java.lang.String"/>
+
+    <attribute name="deployOnStartup"
+               description="The deploy on startup flag for this Host"
+               type="boolean"/>
+
+    <attribute name="deployXML"
+               description="deploy Context XML config files property"
+               is="true"
+               type="boolean"/>
+               
+    <attribute name="errorReportValveClass"
+               description="The Java class name of the default error reporter implementation class for deployed web applications."
+               type="java.lang.String"
+               writeable="false" />
+      
+    <attribute name="managedResource"
+               description="The managed resource this MBean is associated with"
+               type="java.lang.Object"/>
+      
+    <attribute name="name"
+               description="Unique name of this Host"
+               type="java.lang.String"/>
+               
+    <attribute name="realm"
+               description="Associated realm."
+               type="org.apache.catalina.Realm" />
+               
+    <attribute name="startChildren"
+               description="Will children be started automatically when they are added?"
+               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="unpackWARs"
+               description="Unpack WARs property"
+               is="true"
+               type="boolean"/>
+               
+    <attribute name="valveNames"
+               description="Return the MBean Names of the Valves associated with this Host"
+               type="[Ljava.lang.String;"/>
+               
+    <attribute name="workDir"
+               description="Work Directory base for applications"
+               type="java.lang.String"/>
+               
+    <attribute name="xmlBase"
+               description="The XML root for this Host."
+               type="java.lang.String"/>   
+               
+    <operation name="addAlias"
+               description="Add an alias name that should be mapped to this Host"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="alias"
+                 description="The alias to be added"
+                 type="java.lang.String"/>
+    </operation>
+               
+    <operation name="addChild"
+               description="Add a child(Context) to this Host"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="type"
+                 description="Type(classname) of the new child to be added"
+                 type="java.lang.String"/>
+      <parameter name="name"
+                 description="Name of the child to be added"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="addLifecycleListener"
+               description="Add a lifecycle listener to this Host"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="type"
+                 description="Type(classname) of the new lifecycle listener to be added"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="addValve"
+               description="Add a valve to this Host"
+               impact="ACTION"
+               returnType="java.lang.String">
+      <parameter name="valveType"
+                 description="Type(classname) of the new valve to be added"
+                 type="java.lang.String"/>
+    </operation>
+                   
+    <operation name="destroy" 
+               description="Destroy" 
+               impact="ACTION" 
+               returnType="void" />
+    
+    <operation name="findAliases"
+               description="Return the set of alias names for this Host"
+               impact="INFO"
+               returnType="[Ljava.lang.String;"/>
+               
+    <operation name="findReloadedContextMemoryLeaks"
+               description="Provide a list of contexts that have leaked memory on reload. This will attempt to force a full garbage collection. Use with extreme caution on production systems."
+               impact="ACTION"
+               returnType="[Ljava.lang.String;" />
+               
+    <operation name="init" 
+               description="Init" 
+               impact="ACTION" 
+               returnType="void" />
+               
+    <operation name="removeAlias"
+               description="Remove the specified alias name from the aliases for this  Host"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="alias"
+                 description="Alias name to be removed"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="removeChild"
+               description="Remove a child(Context) from this Host"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="name"
+                 description="Name of the existing child Container to be removed"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="removeLifecycleListeners"
+               description="Removes lifecycle listeners of given class type from this Host"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="listener"
+                 description="Type(classname) of the lifecycle listeners to be removed"
+                 type="java.lang.String"/>
+    </operation>  
+    
+    <operation name="removeValve"
+               description="Remove a valve from this Host"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="valveName"
+                 description="Objectname of the valve to be removed"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="start" 
+               description="Start" 
+               impact="ACTION" 
+               returnType="void" />
+    
+    <operation name="stop" 
+               description="Stop" 
+               impact="ACTION" 
+               returnType="void" />
+
+  </mbean>
+  
+  <mbean name="StandardHostValve"
+         description="Valve that implements the default basic behavior for the
+         StandardHost container implementation"
+         domain="Catalina"
+         group="Valve"
+         type="org.apache.catalina.core.StandardHostValve">
+         
+    <attribute name="asyncSupported"
+               description="Does this valve support async reporting?"
+               is="true"
+               type="boolean"/>
+    
+    <attribute name="className"
+               description="Fully qualified class name of the managed object"
+               type="java.lang.String"
+               writeable="false"/>
+
+    <attribute name="stateName"
+               description="The name of the LifecycleState that this component is currently in"
+               type="java.lang.String"
+               writeable="false"/>
+  </mbean>
+
+  <mbean name="StandardServer"
+         description="Standard Server Component"
+         domain="Catalina"
+         group="Server"
+         type="org.apache.catalina.core.StandardServer">
+    
+    <attribute name="address"
+               description="The address on which we wait for shutdown commands."
+               type="java.lang.String"/>
+    
+    <attribute name="managedResource"
+               description="The managed resource this MBean is associated with"
+               type="java.lang.Object"/>
+      
+    <attribute name="port"
+               description="TCP port for shutdown messages"
+               type="int"/>
+               
+    <attribute name="serverInfo"
+               description="Tomcat server release identifier"
+               type="java.lang.String"
+               writeable="false"/>
+               
+    <attribute name="serviceNames"
+               description="Object names of all services we know about"
+               type="[Ljavax.management.ObjectName;"
+               writeable="false" />
+      
+    <attribute name="shutdown"
+               description="Shutdown password"
+               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"/>
+   
+    <operation name="await"
+               description="Wait for the shutdown message"
+               impact="ACTION"
+               returnType="void" />
+
+    <operation name="storeConfig"
+               description="Save current state to server.xml file"
+               impact="ACTION"
+               returnType="void">
+
+    </operation>
+    
+  </mbean>
+
+  
+  <mbean name="StandardService"
+         description="Standard Service Component"
+         domain="Catalina"
+         group="Service"
+         type="org.apache.catalina.core.StandardService"
+         className="org.apache.catalina.mbeans.ServiceMBean">
+    
+    <attribute name="connectorNames"
+               description="ObjectNames of the connectors"
+               type="[Ljavax.management.ObjectName;"
+               writeable="false" />
+
+    <attribute name="managedResource"
+               description="The managed resource this MBean is associated with"
+               type="java.lang.Object"/>
+      
+    <attribute name="name"
+               description="Unique name of this Service"
+               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"/>
+
+    <operation name="addConnector"
+               description="Add a new connector"
+               impact="ACTION"
+               returnType="void">
+      <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"/>
+      <parameter name="isAjp"
+                 description="Create a AJP/1.3 Connector"
+                 type="boolean"/>
+      <parameter name="isSSL"
+                 description="Create a secure Connector"
+                 type="boolean"/>
+    </operation>
+    
+    <operation name="addExecutor"
+               description="Adds a named executor to the service"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="type"
+                 description="Classname of the Executor to be added"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="findConnectors"
+               description="Find and return the set of Connectors associated with this Service"
+               impact="ACTION"
+               returnType="[Ljava.lang.String;">
+    </operation>
+
+    <operation name="findExecutors"
+               description="Retrieves all executors"
+               impact="ACTION"
+               returnType="[Ljava.lang.String;">
+    </operation>
+    
+    <operation name="getExecutor"
+               description="Retrieves executor by name"
+               impact="ACTION"
+               returnType="java.lang.String">
+      <parameter name="name"
+                 description="Name of the executor to be retrieved"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="start" 
+               description="Start" 
+               impact="ACTION" 
+               returnType="void" />
+               
+    <operation name="stop" 
+               description="Stop" 
+               impact="ACTION" 
+               returnType="void" />
+  </mbean>
+  
+  <mbean name="StandardThreadExecutor"
+         description="Standard implementation of a thread pool"
+         domain="Catalina"
+         group="Executor"
+         type="org.apache.catalina.core.StandardThreadExecutor">
+    
+    <attribute name="activeCount"
+               description="Number of threads currently processing a task"
+               type="int"
+               writeable="false" /> 
+               
+    <attribute name="completedTaskCount"
+               description="Number of tasks completed by the executor"
+               type="int"
+               writeable="false" />
+
+    <attribute name="corePoolSize"
+               description="Core size of the thread pool"
+               type="int"
+               writeable="false" /> 
+          
+    <attribute name="daemon"
+               description="Run threads in daemon or non-daemon state?"
+               type="boolean"/>
+
+    <attribute name="largestPoolSize"
+               description="Peak number of threads"
+               type="int"
+               writeable="false" />            
+               
+    <attribute name="maxIdleTime"
+               description="Max number of milliseconds a thread can be idle before it can be shutdown"
+               type="int"/>
+               
+    <attribute name="maxQueueSize"
+               description="Maximum number of tasks for the pending task queue"
+               type="int"/>
+               
+    <attribute name="maxThreads"
+               description="Maximum number of allocated threads"
+               type="int"/>
+               
+    <attribute name="minSpareThreads"
+               description="Minimum number of allocated threads"
+               type="int"/> 
+      
+    <attribute name="name"
+               description="Unique name of this Executor"
+               type="java.lang.String"/>
+      
+    <attribute name="namePrefix"
+               description="Name prefix for thread names created by this executor"
+               type="java.lang.String"/>            
+
+    <attribute name="poolSize"
+               description="Number of threads in the pool"
+               type="int"
+               writeable="false" />
+          
+    <attribute name="prestartminSpareThreads"
+               description="Prestart threads?"
+               type="boolean"/>
+
+    <attribute name="queueSize"
+               description="Number of tasks waiting to be processed"
+               type="int"
+          writeable="false" />
+
+    <attribute name="stateName"
+               description="The name of the LifecycleState that this component is currently in"
+               type="java.lang.String"
+               writeable="false"/>
+               
+    <attribute name="threadPriority"
+               description="The thread priority for threads in this thread pool"
+               type="int"/>
+
+    <attribute name="threadRenewalDelay"
+               description="After a context is stopped, threads in the pool are renewed. To avoid renewing all threads at the same time, this delay is observed between 2 threads being renewed. Value is in ms, default value is 1000ms. If negative, threads are not renewed."
+               type="long"/>
+               
+  </mbean>
+
+  <mbean name="StandardWrapper"
+         description="Wrapper that represents an individual servlet definition"
+         domain="Catalina"
+         group="Wrapper"
+         type="org.apache.catalina.core.StandardWrapper"
+         className="org.apache.catalina.mbeans.ContainerMBean">
+         
+    <attribute name="asyncSupported"
+               description="Async support"
+               is="true"
+               type="boolean"/>  
+               
+    <attribute name="available"
+               description="The date and time at which this servlet will become available (in milliseconds since the epoch), or zero if the servlet is available. If this value equals Long.MAX_VALUE, the unavailability of this servlet is considered permanent."
+               type="long"/>  
+               
+    <attribute name="backgroundProcessorDelay"
+               description="The processor delay for this component."
+               type="int" />      
+    
+    <attribute name="classLoadTime"
+               description="Time taken to load the Servlet class"
+               type="int"
+               writeable="false" />  
+               
+    <attribute name="countAllocated"
+               description="The count of allocations that are currently active (even if they  are for the same instance, as will be true on a non-STM servlet)."
+               type="int"
+               writeable="false" />             
+               
+    <attribute name="errorCount"
+               description="Error count"
+               type="int"
+               writeable="false" />
+               
+    <attribute name="eventProvider"
+               description="Event provider support for this managed object"
+               is="true"
+               type="boolean"
+               writeable="false"/>
+          
+    <attribute name="loadOnStartup"
+               description="The load-on-startup order value (negative value means load on first call) for this servlet."
+               type="int"/>
+          
+    <attribute name="loadTime"
+               description="Time taken to load and initialise the Servlet"
+               type="long"
+               writeable="false" />
+
+    <attribute name="maxTime"
+               description="Maximum processing time of a request"
+               type="long"
+               writeable="false" />
+               
+    <attribute name="maxInstances"
+               description="Maximum number of STM instances."
+               type="int" />               
+
+    <attribute name="minTime"
+               description="Minimum processing time of a request"
+               type="long"
+               writeable="false" />
+
+    <attribute name="objectName"
+               description="Name of the object"
+               type="java.lang.String"/>
+
+    <attribute name="processingTime"
+               description="Total execution time of the servlet's service method"
+               type="long"
+               writeable="false" />
+
+    <attribute name="requestCount"
+               description="Number of requests processed by this wrapper"
+               type="int"
+               writeable="false" />
+               
+    <attribute name="runAs"
+               description="The run-as identity for this servlet."
+               type="java.lang.String"/>
+               
+    <attribute name="servletClass"
+               description="The run-as identity for this servlet."
+               type="java.lang.String"
+               writeable="false" />
+          
+    <attribute name="singleThreadModel"
+               description="Does this servlet implement the SingleThreadModel interface?"
+               type="boolean"
+               is="true"
+               writeable="false" />
+
+    <attribute name="stateManageable"
+               description="State management support for this managed object"
+               is="true"
+               type="boolean"
+               writeable="false"/>
+
+    <attribute name="stateName"
+               description="The name of the LifecycleState that this component is currently in"
+               type="java.lang.String"
+               writeable="false"/>
+
+    <attribute name="statisticsProvider"
+               description="Performance statistics support for this managed object"
+               is="true"
+               type="boolean"
+               writeable="false"/>
+               
+    <operation name="addInitParameter"
+               description="Add a valve to this Wrapper"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="name"
+                 description="Name of this initialization parameter to add"
+                 type="java.lang.String"/>
+      <parameter name="value"
+                 description="Value of this initialization parameter to add"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="addLifecycleListener"
+               description="Add a lifecycle listener to this Wrapper"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="type"
+                 description="Type(classname) of the new lifecycle listener to be added"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="addMapping"
+               description="Add a mapping associated with the Wrapper."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="mapping"
+                 description="The new wrapper mapping"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="addSecurityReference"
+               description="Add a new security role reference record to the set of records for this servlet."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="name"
+                 description="Role name used within this servlet"
+                 type="java.lang.String"/>
+      <parameter name="link"
+                 description="Role name used within the web application"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="addValve"
+               description="Add a valve to this Wrapper"
+               impact="ACTION"
+               returnType="java.lang.String">
+      <parameter name="valveType"
+                 description="Type(classname) of the new valve to be added"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="findInitParameter"
+               description="Add a mapping associated with the Wrapper."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="mapping"
+                 description="The new wrapper mapping"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="findInitParameters"
+               description="Return the names of all defined initialization parameters for this servlet."
+               impact="INFO"
+               returnType="[Ljava.lang.String;">
+    </operation>
+
+    <operation name="findMappings"
+               description="Return the mappings associated with this wrapper"
+               impact="INFO"
+               returnType="[Ljava.lang.String;">
+    </operation>
+               
+    <operation name="findMappingObject"
+               description="Return an object which may be utilized for mapping to this component"
+               impact="INFO"
+               returnType="org.apache.catalina.Wrapper">
+    </operation>
+    
+    <operation name="findSecurityReference"
+               description="Return the security role link for the specified security role reference name."
+               impact="ACTION"
+               returnType="java.lang.String">
+      <parameter name="name"
+                 description="Security role reference used within this servle"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="findSecurityReferences"
+               description="Return the set of security role reference names associated with this servlet"
+               impact="INFO"
+               returnType="[Ljava.lang.String;">
+    </operation>
+    
+    <operation name="removeInitParameter"
+               description="Remove the specified initialization parameter from this servlet."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="name"
+                 description="Name of the initialization parameter to remove"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="removeLifecycleListeners"
+               description="Removes lifecycle listeners of given class type from this Wrapper"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="listener"
+                 description="Type(classname) of the lifecycle listeners to be removed"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="removeMapping"
+               description="Remove a mapping associated with the wrapper."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="mapping"
+                 description="The pattern to remove"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="removeSecurityReference"
+               description="Remove any security role reference for the specified role name."
+               impact="ACTION"
+               returnType="void">
+      <parameter name="name"
+                 description="Security role used within this servlet to be removeds"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="removeValve"
+               description="Remove a valve from this Wrapper"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="valveName"
+                 description="Objectname of the valve to be removed"
+                 type="java.lang.String"/>
+    </operation>
+   
+  </mbean>
+  
+  <mbean name="StandardWrapperValve"
+         description="Valve that implements the default basic behavior for the StandardWrapper container implementation"
+         domain="Catalina"
+         group="Valve"
+         type="org.apache.catalina.core.StandardWrapperValve">
+     
+     <attribute name="asyncSupported"
+                description="Does this valve support async reporting?"
+                type="boolean"/>
+         
+     <attribute name="className"
+                description="Fully qualified class name of the managed object"
+                type="java.lang.String"
+                writeable="false"/>  
+
+    <attribute name="stateName"
+               description="The name of the LifecycleState that this component is currently in"
+               type="java.lang.String"
+               writeable="false"/>
+  </mbean>
+
+</mbeans-descriptors>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ApplicationParameter.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ApplicationParameter.java
new file mode 100644
index 0000000..8090447
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ApplicationParameter.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.catalina.deploy;
+
+import java.io.Serializable;
+
+
+/**
+ * Representation of a context initialization parameter that is configured
+ * in the server configuration file, rather than the application deployment
+ * descriptor.  This is convenient for establishing default values (which
+ * may be configured to allow application overrides or not) without having
+ * to modify the application deployment descriptor itself.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: ApplicationParameter.java,v 1.1 2011/06/28 21:08:22 rherrmann Exp $
+ */
+
+public class ApplicationParameter implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * The description of this environment entry.
+     */
+    private String description = null;
+
+    public String getDescription() {
+        return (this.description);
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+
+    /**
+     * The name of this application parameter.
+     */
+    private String name = null;
+
+    public String getName() {
+        return (this.name);
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+
+    /**
+     * Does this application parameter allow overrides by the application
+     * deployment descriptor?
+     */
+    private boolean override = true;
+
+    public boolean getOverride() {
+        return (this.override);
+    }
+
+    public void setOverride(boolean override) {
+        this.override = override;
+    }
+
+
+    /**
+     * The value of this application parameter.
+     */
+    private String value = null;
+
+    public String getValue() {
+        return (this.value);
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return a String representation of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("ApplicationParameter[");
+        sb.append("name=");
+        sb.append(name);
+        if (description != null) {
+            sb.append(", description=");
+            sb.append(description);
+        }
+        sb.append(", value=");
+        sb.append(value);
+        sb.append(", override=");
+        sb.append(override);
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/Constants.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/Constants.java
new file mode 100644
index 0000000..9ac67ae
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/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.deploy;
+
+
+public class Constants {
+
+    public static final String Package = "org.apache.catalina.deploy";
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextEjb.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextEjb.java
new file mode 100644
index 0000000..a3c5e0d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextEjb.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.deploy;
+
+
+/**
+ * Representation of an EJB resource reference for a web application, as
+ * represented in a <code>&lt;ejb-ref&gt;</code> element in the
+ * deployment descriptor.
+ *
+ * @author Craig R. McClanahan
+ * @author Peter Rossbach (pero@apache.org)
+ * @version $Id: ContextEjb.java,v 1.1 2011/06/28 21:08:22 rherrmann Exp $
+ */
+
+public class ContextEjb extends ResourceBase {
+
+    private static final long serialVersionUID = 1L;
+
+    // ------------------------------------------------------------- Properties
+
+
+
+    /**
+     * The name of the EJB home implementation class.
+     */
+    private String home = null;
+
+    public String getHome() {
+        return (this.home);
+    }
+
+    public void setHome(String home) {
+        this.home = home;
+    }
+
+
+    /**
+     * The link to a J2EE EJB definition.
+     */
+    private String link = null;
+
+    public String getLink() {
+        return (this.link);
+    }
+
+    public void setLink(String link) {
+        this.link = link;
+    }
+
+    /**
+     * The name of the EJB remote implementation class.
+     */
+    private String remote = null;
+
+    public String getRemote() {
+        return (this.remote);
+    }
+
+    public void setRemote(String remote) {
+        this.remote = remote;
+    }
+
+    
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return a String representation of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("ContextEjb[");
+        sb.append("name=");
+        sb.append(getName());
+        if (getDescription() != null) {
+            sb.append(", description=");
+            sb.append(getDescription());
+        }
+        if (getType() != null) {
+            sb.append(", type=");
+            sb.append(getType());
+        }
+        if (home != null) {
+            sb.append(", home=");
+            sb.append(home);
+        }
+        if (remote != null) {
+            sb.append(", remote=");
+            sb.append(remote);
+        }
+        if (link != null) {
+            sb.append(", link=");
+            sb.append(link);
+        }
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextEnvironment.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextEnvironment.java
new file mode 100644
index 0000000..f8752a6
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextEnvironment.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.catalina.deploy;
+
+
+/**
+ * Representation of an application environment entry, as represented in
+ * an <code>&lt;env-entry&gt;</code> element in the deployment descriptor.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: ContextEnvironment.java,v 1.1 2011/06/28 21:08:22 rherrmann Exp $
+ */
+
+public class ContextEnvironment extends ResourceBase {
+
+    private static final long serialVersionUID = 1L;
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Does this environment entry allow overrides by the application
+     * deployment descriptor?
+     */
+    private boolean override = true;
+
+    public boolean getOverride() {
+        return (this.override);
+    }
+
+    public void setOverride(boolean override) {
+        this.override = override;
+    }
+
+
+    /**
+     * The value of this environment entry.
+     */
+    private String value = null;
+
+    public String getValue() {
+        return (this.value);
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return a String representation of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("ContextEnvironment[");
+        sb.append("name=");
+        sb.append(getName());
+        if (getDescription() != null) {
+            sb.append(", description=");
+            sb.append(getDescription());
+        }
+        if (getType() != null) {
+            sb.append(", type=");
+            sb.append(getType());
+        }
+        if (value != null) {
+            sb.append(", value=");
+            sb.append(value);
+        }
+        sb.append(", override=");
+        sb.append(override);
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextHandler.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextHandler.java
new file mode 100644
index 0000000..ddf9be5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextHandler.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.deploy;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+
+/**
+ * Representation of a handler reference for a web service, as
+ * represented in a <code>&lt;handler&gt;</code> element in the
+ * deployment descriptor.
+ *
+ * @author Fabien Carrion
+ */
+
+public class ContextHandler extends ResourceBase {
+
+    private static final long serialVersionUID = 1L;
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * The Handler reference class.
+     */
+    private String handlerclass = null;
+
+    public String getHandlerclass() {
+        return (this.handlerclass);
+    }
+
+    public void setHandlerclass(String handlerclass) {
+        this.handlerclass = handlerclass;
+    }
+
+    /**
+     * A list of QName specifying the SOAP Headers the handler will work on. 
+     * -namespace and locapart values must be found inside the WSDL.
+     *
+     * A service-qname is composed by a namespaceURI and a localpart.
+     *
+     * soapHeader[0] : namespaceURI
+     * soapHeader[1] : localpart
+     */
+    private HashMap<String, String> soapHeaders = new HashMap<String, String>();
+
+    public Iterator<String> getLocalparts() {
+        return soapHeaders.keySet().iterator();
+    }
+
+    public String getNamespaceuri(String localpart) {
+        return soapHeaders.get(localpart);
+    }
+
+    public void addSoapHeaders(String localpart, String namespaceuri) {
+        soapHeaders.put(localpart, namespaceuri);
+    }
+
+    /**
+     * Set a configured property.
+     */
+    public void setProperty(String name, String value) {
+        this.setProperty(name, (Object) value);
+    }
+
+    /**
+     * The soapRole.
+     */
+    private ArrayList<String> soapRoles = new ArrayList<String>();
+
+    public String getSoapRole(int i) {
+        return this.soapRoles.get(i);
+    }
+
+    public int getSoapRolesSize() {
+        return this.soapRoles.size();
+    }
+
+    public void addSoapRole(String soapRole) {
+        this.soapRoles.add(soapRole);
+    }
+
+    /**
+     * The portName.
+     */
+    private ArrayList<String> portNames = new ArrayList<String>();
+
+    public String getPortName(int i) {
+        return this.portNames.get(i);
+    }
+
+    public int getPortNamesSize() {
+        return this.portNames.size();
+    }
+
+    public void addPortName(String portName) {
+        this.portNames.add(portName);
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return a String representation of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("ContextHandler[");
+        sb.append("name=");
+        sb.append(getName());
+        if (handlerclass != null) {
+            sb.append(", class=");
+            sb.append(handlerclass);
+        }
+        if (this.soapHeaders != null) {
+            sb.append(", soap-headers=");
+            sb.append(this.soapHeaders);
+        }
+        if (this.getSoapRolesSize() > 0) {
+            sb.append(", soap-roles=");
+            sb.append(soapRoles);
+        }
+        if (this.getPortNamesSize() > 0) {
+            sb.append(", port-name=");
+            sb.append(portNames);
+        }
+        if (this.listProperties() != null) {
+            sb.append(", init-param=");
+            sb.append(this.listProperties());
+        }
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextLocalEjb.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextLocalEjb.java
new file mode 100644
index 0000000..b95628e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextLocalEjb.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.deploy;
+
+
+/**
+ * Representation of a local EJB resource reference for a web application, as
+ * represented in a <code>&lt;ejb-local-ref&gt;</code> element in the
+ * deployment descriptor.
+ *
+ * @author Craig R. McClanahan
+ * @author Peter Rossbach (pero@apache.org)
+ * @version $Id: ContextLocalEjb.java,v 1.1 2011/06/28 21:08:22 rherrmann Exp $
+ */
+
+public class ContextLocalEjb extends ResourceBase {
+
+    private static final long serialVersionUID = 1L;
+
+    // ------------------------------------------------------------- Properties
+
+    /**
+     * The name of the EJB home implementation class.
+     */
+    private String home = null;
+
+    public String getHome() {
+        return (this.home);
+    }
+
+    public void setHome(String home) {
+        this.home = home;
+    }
+
+
+    /**
+     * The link to a J2EE EJB definition.
+     */
+    private String link = null;
+
+    public String getLink() {
+        return (this.link);
+    }
+
+    public void setLink(String link) {
+        this.link = link;
+    }
+
+
+    /**
+     * The name of the EJB local implementation class.
+     */
+    private String local = null;
+
+    public String getLocal() {
+        return (this.local);
+    }
+
+    public void setLocal(String local) {
+        this.local = local;
+    }
+
+    
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return a String representation of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("ContextLocalEjb[");
+        sb.append("name=");
+        sb.append(getName());
+        if (getDescription() != null) {
+            sb.append(", description=");
+            sb.append(getDescription());
+        }
+        if (getType() != null) {
+            sb.append(", type=");
+            sb.append(getType());
+        }
+        if (home != null) {
+            sb.append(", home=");
+            sb.append(home);
+        }
+        if (link != null) {
+            sb.append(", link=");
+            sb.append(link);
+        }
+        if (local != null) {
+            sb.append(", local=");
+            sb.append(local);
+        }
+        sb.append("]");
+        return (sb.toString());
+
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextResource.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextResource.java
new file mode 100644
index 0000000..af84b74
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextResource.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.deploy;
+
+
+/**
+ * Representation of a resource reference for a web application, as
+ * represented in a <code>&lt;resource-ref&gt;</code> element in the
+ * deployment descriptor.
+ *
+ * @author Craig R. McClanahan
+ * @author Peter Rossbach (pero@apache.org)
+ * @version $Id: ContextResource.java,v 1.1 2011/06/28 21:08:22 rherrmann Exp $
+ */
+
+public class ContextResource extends ResourceBase {
+
+    private static final long serialVersionUID = 1L;
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * The authorization requirement for this resource
+     * (<code>Application</code> or <code>Container</code>).
+     */
+    private String auth = null;
+
+    public String getAuth() {
+        return (this.auth);
+    }
+
+    public void setAuth(String auth) {
+        this.auth = auth;
+    }
+
+    /**
+     * The sharing scope of this resource factory (<code>Shareable</code>
+     * or <code>Unshareable</code>).
+     */
+    private String scope = "Shareable";
+
+    public String getScope() {
+        return (this.scope);
+    }
+
+    public void setScope(String scope) {
+        this.scope = scope;
+    }
+
+
+    /**
+     * Is this resource known to be a singleton resource. The default value is
+     * true since this is what users expect although the JavaEE spec implies
+     * that the default should be false.
+     */
+    private boolean singleton = true;
+    
+    public boolean getSingleton() {
+        return singleton;
+    }
+    
+    public void setSingleton(boolean singleton) {
+        this.singleton = singleton;
+    }
+
+
+    /**
+     * The name of the zero argument method to be called when the resource is
+     * no longer required to clean-up resources. This method must only speed up
+     * the clean-up of resources that would otherwise happen via garbage
+     * collection.
+     */
+    private String closeMethod = null;
+    
+    public String getCloseMethod() {
+        return closeMethod;
+    }
+
+    public void setCloseMethod(String closeMethod) {
+        this.closeMethod = closeMethod;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return a String representation of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("ContextResource[");
+        sb.append("name=");
+        sb.append(getName());
+        if (getDescription() != null) {
+            sb.append(", description=");
+            sb.append(getDescription());
+        }
+        if (getType() != null) {
+            sb.append(", type=");
+            sb.append(getType());
+        }
+        if (auth != null) {
+            sb.append(", auth=");
+            sb.append(auth);
+        }
+        if (scope != null) {
+            sb.append(", scope=");
+            sb.append(scope);
+        }
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextResourceEnvRef.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextResourceEnvRef.java
new file mode 100644
index 0000000..2484e80
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextResourceEnvRef.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.deploy;
+
+
+/**
+ * Representation of an application resource reference, as represented in
+ * an <code>&lt;res-env-refy&gt;</code> element in the deployment descriptor.
+ *
+ * @author Craig R. McClanahan
+ * @author Peter Rossbach (pero@apache.org)
+ * @version $Id: ContextResourceEnvRef.java,v 1.1 2011/06/28 21:08:22 rherrmann Exp $
+ */
+
+public class ContextResourceEnvRef extends ResourceBase {
+
+    private static final long serialVersionUID = 1L;
+
+    // ------------------------------------------------------------- Properties
+
+    /**
+     * Does this environment entry allow overrides by the application
+     * deployment descriptor?
+     */
+    private boolean override = true;
+
+    public boolean getOverride() {
+        return (this.override);
+    }
+
+    public void setOverride(boolean override) {
+        this.override = override;
+    }
+    
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return a String representation of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("ContextResourceEnvRef[");
+        sb.append("name=");
+        sb.append(getName());
+        if (getType() != null) {
+            sb.append(", type=");
+            sb.append(getType());
+        }
+        sb.append(", override=");
+        sb.append(override);
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextResourceLink.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextResourceLink.java
new file mode 100644
index 0000000..55f7b13
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextResourceLink.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.deploy;
+
+
+/**
+ * Representation of a resource link for a web application, as
+ * represented in a <code>&lt;ResourceLink&gt;</code> element in the
+ * server configuration file.
+ *
+ * @author Remy Maucherat
+ * @author Peter Rossbach (Peter Rossbach (pero@apache.org))
+ * @version $Id: ContextResourceLink.java,v 1.1 2011/06/28 21:08:22 rherrmann Exp $
+ */
+
+public class ContextResourceLink extends ResourceBase {
+
+    private static final long serialVersionUID = 1L;
+
+    // ------------------------------------------------------------- Properties
+
+   /**
+     * The global name of this resource.
+     */
+    private String global = null;
+    /**
+     * The factory to be used for creating the object
+     */
+    private String factory = null;
+
+    public String getGlobal() {
+        return (this.global);
+    }
+
+    public void setGlobal(String global) {
+        this.global = global;
+    }
+
+    public String getFactory() {
+        return factory;
+    }
+
+    public void setFactory(String factory) {
+        this.factory = factory;
+    }
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return a String representation of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("ContextResourceLink[");
+        sb.append("name=");
+        sb.append(getName());
+        if (getType() != null) {
+            sb.append(", type=");
+            sb.append(getType());
+        }
+        if (getGlobal() != null) {
+            sb.append(", global=");
+            sb.append(getGlobal());
+        }
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextService.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextService.java
new file mode 100644
index 0000000..35278dd
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextService.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.catalina.deploy;
+
+import java.util.HashMap;
+import java.util.Iterator;
+
+/**
+ * Representation of a web service reference for a web application, as
+ * represented in a <code>&lt;service-ref&gt;</code> element in the
+ * deployment descriptor.
+ *
+ * @author Fabien Carrion
+ * @version $Id: ContextService.java,v 1.1 2011/06/28 21:08:22 rherrmann Exp $
+ */
+
+public class ContextService extends ResourceBase {
+
+    private static final long serialVersionUID = 1L;
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * The WebService reference name.
+     */
+    private String displayname = null;
+
+    public String getDisplayname() {
+        return (this.displayname);
+    }
+
+    public void setDisplayname(String displayname) {
+        this.displayname = displayname;
+    }
+
+    /**
+     * A large icon for this WebService.
+     */
+    private String largeIcon = null;
+
+    public String getLargeIcon() {
+        return (this.largeIcon);
+    }
+
+    public void setLargeIcon(String largeIcon) {
+        this.largeIcon = largeIcon;
+    }
+
+    /**
+     * A small icon for this WebService.
+     */
+    private String smallIcon = null;
+
+    public String getSmallIcon() {
+        return (this.smallIcon);
+    }
+
+    public void setSmallIcon(String smallIcon) {
+        this.smallIcon = smallIcon;
+    }
+
+    /**
+     * The fully qualified class name of the JAX-WS Service interface that the
+     * client depends on.
+     */
+    private String serviceInterface = null;
+    
+    public String getInterface() {
+        return serviceInterface;
+    }
+    
+    public void setInterface(String serviceInterface) {
+        this.serviceInterface = serviceInterface;
+    }
+
+    /**
+     * Contains the location (relative to the root of
+     * the module) of the web service WSDL description.
+     */
+    private String wsdlfile = null;
+
+    public String getWsdlfile() {
+        return (this.wsdlfile);
+    }
+
+    public void setWsdlfile(String wsdlfile) {
+        this.wsdlfile = wsdlfile;
+    }
+
+    /**
+     * A file specifying the correlation of the WSDL definition
+     * to the interfaces (Service Endpoint Interface, Service Interface). 
+     */
+    private String jaxrpcmappingfile = null;
+
+    public String getJaxrpcmappingfile() {
+        return (this.jaxrpcmappingfile);
+    }
+
+    public void setJaxrpcmappingfile(String jaxrpcmappingfile) {
+        this.jaxrpcmappingfile = jaxrpcmappingfile;
+    }
+
+    /**
+     * Declares the specific WSDL service element that is being referred to.
+     * It is not specified if no wsdl-file is declared or if WSDL contains only
+     * 1 service element.
+     *
+     * A service-qname is composed by a namespaceURI and a localpart.
+     * It must be defined if more than 1 service is declared in the WSDL.
+     *
+     * serviceqname[0] : namespaceURI
+     * serviceqname[1] : localpart
+     */
+    private String[] serviceqname = new String[2];
+
+    public String[] getServiceqname() {
+        return (this.serviceqname);
+    }
+
+    public String getServiceqname(int i) {
+        return this.serviceqname[i];
+    }
+
+    public String getServiceqnameNamespaceURI() {
+        return this.serviceqname[0];
+    }
+
+    public String getServiceqnameLocalpart() {
+        return this.serviceqname[1];
+    }
+
+    public void setServiceqname(String[] serviceqname) {
+        this.serviceqname = serviceqname;
+    }
+
+    public void setServiceqname(String serviceqname, int i) {
+        this.serviceqname[i] = serviceqname;
+    }
+
+    public void setServiceqnameNamespaceURI(String namespaceuri) {
+        this.serviceqname[0] = namespaceuri;
+    }
+
+    public void setServiceqnameLocalpart(String localpart) {
+        this.serviceqname[1] = localpart;
+    }
+
+    /**
+     * Declares a client dependency on the container to resolving a Service Endpoint Interface
+     * to a WSDL port. It optionally associates the Service Endpoint Interface with a
+     * particular port-component.
+     *
+     */
+    public Iterator<String> getServiceendpoints() {
+        return this.listProperties();
+    }
+
+    public String getPortlink(String serviceendpoint) {
+        return (String) this.getProperty(serviceendpoint);
+    }
+
+    public void addPortcomponent(String serviceendpoint, String portlink) {
+        if (portlink == null)
+            portlink = "";
+        this.setProperty(serviceendpoint, portlink);
+    }
+
+    /**
+     * A list of Handlers to use for this service-ref.
+     *
+     * The instantiation of the handler have to be done.
+     */
+    private HashMap<String, ContextHandler> handlers =
+        new HashMap<String, ContextHandler>();
+
+    public Iterator<String> getHandlers() {
+        return handlers.keySet().iterator();
+    }
+
+    public ContextHandler getHandler(String handlername) {
+        return handlers.get(handlername);
+    }
+
+    public void addHandler(ContextHandler handler) {
+        handlers.put(handler.getName(), handler);
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return a String representation of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("ContextService[");
+        sb.append("name=");
+        sb.append(getName());
+        if (getDescription() != null) {
+            sb.append(", description=");
+            sb.append(getDescription());
+        }
+        if (getType() != null) {
+            sb.append(", type=");
+            sb.append(getType());
+        }
+        if (displayname != null) {
+            sb.append(", displayname=");
+            sb.append(displayname);
+        }
+        if (largeIcon != null) {
+            sb.append(", largeIcon=");
+            sb.append(largeIcon);
+        }
+        if (smallIcon != null) {
+            sb.append(", smallIcon=");
+            sb.append(smallIcon);
+        }
+        if (wsdlfile != null) {
+            sb.append(", wsdl-file=");
+            sb.append(wsdlfile);
+        }
+        if (jaxrpcmappingfile != null) {
+            sb.append(", jaxrpc-mapping-file=");
+            sb.append(jaxrpcmappingfile);
+        }
+        if (serviceqname[0] != null) {
+            sb.append(", service-qname/namespaceURI=");
+            sb.append(serviceqname[0]);
+        }
+        if (serviceqname[1] != null) {
+            sb.append(", service-qname/localpart=");
+            sb.append(serviceqname[1]);
+        }
+        if (this.getServiceendpoints() != null) {
+            sb.append(", port-component/service-endpoint-interface=");
+            sb.append(this.getServiceendpoints());
+        }
+        if (handlers != null) {
+            sb.append(", handler=");
+            sb.append(handlers);
+        }
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextTransaction.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextTransaction.java
new file mode 100644
index 0000000..69b39be
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ContextTransaction.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.deploy;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Iterator;
+
+
+/**
+ * Representation of an application resource reference, as represented in
+ * an <code>&lt;res-env-refy&gt;</code> element in the deployment descriptor.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: ContextTransaction.java,v 1.1 2011/06/28 21:08:22 rherrmann Exp $
+ */
+
+public class ContextTransaction implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Holder for our configured properties.
+     */
+    private HashMap<String, Object> properties = new HashMap<String, Object>();
+
+    /**
+     * Return a configured property.
+     */
+    public Object getProperty(String name) {
+        return properties.get(name);
+    }
+
+    /**
+     * Set a configured property.
+     */
+    public void setProperty(String name, Object value) {
+        properties.put(name, value);
+    }
+
+    /** 
+     * remove a configured property.
+     */
+    public void removeProperty(String name) {
+        properties.remove(name);
+    }
+
+    /**
+     * List properties.
+     */
+    public Iterator<String> listProperties() {
+        return properties.keySet().iterator();
+    }
+    
+    
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return a String representation of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("Transaction[");
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+    // -------------------------------------------------------- Package Methods
+
+
+    /**
+     * The NamingResources with which we are associated (if any).
+     */
+    protected NamingResources resources = null;
+
+    public NamingResources getNamingResources() {
+        return (this.resources);
+    }
+
+    void setNamingResources(NamingResources resources) {
+        this.resources = resources;
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ErrorPage.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ErrorPage.java
new file mode 100644
index 0000000..933e8e2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ErrorPage.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.deploy;
+
+
+import java.io.Serializable;
+
+import org.apache.catalina.util.RequestUtil;
+
+
+/**
+ * Representation of an error page element for a web application,
+ * as represented in a <code>&lt;error-page&gt;</code> element in the
+ * deployment descriptor.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: ErrorPage.java,v 1.1 2011/06/28 21:08:22 rherrmann Exp $
+ */
+
+public class ErrorPage implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The error (status) code for which this error page is active.
+     */
+    private int errorCode = 0;
+
+
+    /**
+     * The exception type for which this error page is active.
+     */
+    private String exceptionType = null;
+
+
+    /**
+     * The context-relative location to handle this error or exception.
+     */
+    private String location = null;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the error code.
+     */
+    public int getErrorCode() {
+
+        return (this.errorCode);
+
+    }
+
+
+    /**
+     * Set the error code.
+     *
+     * @param errorCode The new error code
+     */
+    public void setErrorCode(int errorCode) {
+
+        this.errorCode = errorCode;
+
+    }
+
+
+    /**
+     * Set the error code (hack for default XmlMapper data type).
+     *
+     * @param errorCode The new error code
+     */
+    public void setErrorCode(String errorCode) {
+
+        try {
+            this.errorCode = Integer.parseInt(errorCode);
+        } catch (NumberFormatException nfe) {
+            this.errorCode = 0;
+        }
+
+    }
+
+
+    /**
+     * Return the exception type.
+     */
+    public String getExceptionType() {
+
+        return (this.exceptionType);
+
+    }
+
+
+    /**
+     * Set the exception type.
+     *
+     * @param exceptionType The new exception type
+     */
+    public void setExceptionType(String exceptionType) {
+
+        this.exceptionType = exceptionType;
+
+    }
+
+
+    /**
+     * Return the location.
+     */
+    public String getLocation() {
+
+        return (this.location);
+
+    }
+
+
+    /**
+     * Set the location.
+     *
+     * @param location The new location
+     */
+    public void setLocation(String location) {
+
+        //        if ((location == null) || !location.startsWith("/"))
+        //            throw new IllegalArgumentException
+        //                ("Error Page Location must start with a '/'");
+        this.location = RequestUtil.URLDecode(location);
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Render a String representation of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("ErrorPage[");
+        if (exceptionType == null) {
+            sb.append("errorCode=");
+            sb.append(errorCode);
+        } else {
+            sb.append("exceptionType=");
+            sb.append(exceptionType);
+        }
+        sb.append(", location=");
+        sb.append(location);
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+    public String getName() {
+        if (exceptionType == null) {
+            return Integer.toString(errorCode);
+        } else {
+            return exceptionType;
+        }
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/FilterDef.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/FilterDef.java
new file mode 100644
index 0000000..313ab33
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/FilterDef.java
@@ -0,0 +1,205 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.deploy;
+
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.Filter;
+
+
+/**
+ * Representation of a filter definition for a web application, as represented
+ * in a <code>&lt;filter&gt;</code> element in the deployment descriptor.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: FilterDef.java,v 1.1 2011/06/28 21:08:22 rherrmann Exp $
+ */
+
+public class FilterDef implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * The description of this filter.
+     */
+    private String description = null;
+
+    public String getDescription() {
+        return (this.description);
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+
+    /**
+     * The display name of this filter.
+     */
+    private String displayName = null;
+
+    public String getDisplayName() {
+        return (this.displayName);
+    }
+
+    public void setDisplayName(String displayName) {
+        this.displayName = displayName;
+    }
+
+    
+    /**
+     * The filter instance associated with this definition
+     */
+    private transient Filter filter = null;
+    
+    public Filter getFilter() {
+        return filter;
+    }
+
+    public void setFilter(Filter filter) {
+        this.filter = filter;
+    }
+    
+    
+    /**
+     * The fully qualified name of the Java class that implements this filter.
+     */
+    private String filterClass = null;
+
+    public String getFilterClass() {
+        return (this.filterClass);
+    }
+
+    public void setFilterClass(String filterClass) {
+        this.filterClass = filterClass;
+    }
+
+
+    /**
+     * The name of this filter, which must be unique among the filters
+     * defined for a particular web application.
+     */
+    private String filterName = null;
+
+    public String getFilterName() {
+        return (this.filterName);
+    }
+
+    public void setFilterName(String filterName) {
+        this.filterName = filterName;
+    }
+
+
+    /**
+     * The large icon associated with this filter.
+     */
+    private String largeIcon = null;
+
+    public String getLargeIcon() {
+        return (this.largeIcon);
+    }
+
+    public void setLargeIcon(String largeIcon) {
+        this.largeIcon = largeIcon;
+    }
+
+
+    /**
+     * The set of initialization parameters for this filter, keyed by
+     * parameter name.
+     */
+    private Map<String, String> parameters = new HashMap<String, String>();
+
+    public Map<String, String> getParameterMap() {
+
+        return (this.parameters);
+
+    }
+
+
+    /**
+     * The small icon associated with this filter.
+     */
+    private String smallIcon = null;
+
+    public String getSmallIcon() {
+        return (this.smallIcon);
+    }
+
+    public void setSmallIcon(String smallIcon) {
+        this.smallIcon = smallIcon;
+    }
+    
+    private String asyncSupported = null;
+    
+    public String getAsyncSupported() {
+        return asyncSupported;
+    }
+
+    public void setAsyncSupported(String asyncSupported) {
+        this.asyncSupported = asyncSupported;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add an initialization parameter to the set of parameters associated
+     * with this filter.
+     *
+     * @param name The initialization parameter name
+     * @param value The initialization parameter value
+     */
+    public void addInitParameter(String name, String value) {
+
+        if (parameters.containsKey(name)) {
+            // The spec does not define this but the TCK expects the first
+            // definition to take precedence
+            return;
+        }
+        parameters.put(name, value);
+
+    }
+
+
+    /**
+     * Render a String representation of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("FilterDef[");
+        sb.append("filterName=");
+        sb.append(this.filterName);
+        sb.append(", filterClass=");
+        sb.append(this.filterClass);
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/FilterMap.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/FilterMap.java
new file mode 100644
index 0000000..b192cde
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/FilterMap.java
@@ -0,0 +1,226 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.deploy;
+
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Locale;
+
+import javax.servlet.DispatcherType;
+
+import org.apache.catalina.util.RequestUtil;
+
+
+/**
+ * Representation of a filter mapping for a web application, as represented
+ * in a <code>&lt;filter-mapping&gt;</code> element in the deployment
+ * descriptor.  Each filter mapping must contain a filter name plus either
+ * a URL pattern or a servlet name.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: FilterMap.java,v 1.1 2011/06/28 21:08:22 rherrmann Exp $
+ */
+
+public class FilterMap implements Serializable {
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    private static final long serialVersionUID = 1L;
+    
+    /**
+     * The name of this filter to be executed when this mapping matches
+     * a particular request.
+     */
+    
+    public static final int ERROR = 1;
+    public static final int FORWARD = 2;
+    public static final int INCLUDE = 4;
+    public static final int REQUEST = 8;
+    public static final int ASYNC = 16;
+    
+    // represents nothing having been set. This will be seen 
+    // as equal to a REQUEST
+    private static final int NOT_SET = 0;
+    
+    private int dispatcherMapping = NOT_SET;
+    
+    private String filterName = null;    
+
+    public String getFilterName() {
+        return (this.filterName);
+    }
+
+    public void setFilterName(String filterName) {
+        this.filterName = filterName;
+    }
+
+
+    /**
+     * The servlet name this mapping matches.
+     */
+    private String[] servletNames = new String[0];
+
+    public String[] getServletNames() {
+        if (matchAllServletNames) {
+            return new String[] {};
+        } else {
+            return (this.servletNames);
+        }
+    }
+
+    public void addServletName(String servletName) {
+        if ("*".equals(servletName)) {
+            this.matchAllServletNames = true;
+        } else {
+            String[] results = new String[servletNames.length + 1];
+            System.arraycopy(servletNames, 0, results, 0, servletNames.length);
+            results[servletNames.length] = servletName;
+            servletNames = results;
+        }
+    }
+
+    
+    /**
+     * The flag that indicates this mapping will match all url-patterns
+     */
+    private boolean matchAllUrlPatterns = false;
+    
+    public boolean getMatchAllUrlPatterns() {
+        return matchAllUrlPatterns;
+    }
+    
+
+    /**
+     * The flag that indicates this mapping will match all servlet-names
+     */
+    private boolean matchAllServletNames = false;
+    
+    public boolean getMatchAllServletNames() {
+        return matchAllServletNames;
+    }
+
+    
+    /**
+     * The URL pattern this mapping matches.
+     */
+    private String[] urlPatterns = new String[0];
+
+    public String[] getURLPatterns() {
+        if (matchAllUrlPatterns) {
+            return new String[] {};
+        } else {
+            return (this.urlPatterns);
+        }
+    }
+
+    public void addURLPattern(String urlPattern) {
+        if ("*".equals(urlPattern)) {
+            this.matchAllUrlPatterns = true;
+        } else {
+            String[] results = new String[urlPatterns.length + 1];
+            System.arraycopy(urlPatterns, 0, results, 0, urlPatterns.length);
+            results[urlPatterns.length] = RequestUtil.URLDecode(urlPattern);
+            urlPatterns = results;
+        }
+    }
+    
+    /**
+     *
+     * This method will be used to set the current state of the FilterMap
+     * representing the state of when filters should be applied.
+     */
+    public void setDispatcher(String dispatcherString) {
+        String dispatcher = dispatcherString.toUpperCase(Locale.ENGLISH);
+        
+        if (dispatcher.equals(DispatcherType.FORWARD.name())) {
+            // apply FORWARD to the global dispatcherMapping.
+            dispatcherMapping |= FORWARD;
+        } else if (dispatcher.equals(DispatcherType.INCLUDE.name())) {
+            // apply INCLUDE to the global dispatcherMapping.
+            dispatcherMapping |= INCLUDE;
+        } else if (dispatcher.equals(DispatcherType.REQUEST.name())) {
+            // apply REQUEST to the global dispatcherMapping.
+            dispatcherMapping |= REQUEST;
+        }  else if (dispatcher.equals(DispatcherType.ERROR.name())) {
+            // apply ERROR to the global dispatcherMapping.
+            dispatcherMapping |= ERROR;
+        }  else if (dispatcher.equals(DispatcherType.ASYNC.name())) {
+            // apply ERROR to the global dispatcherMapping.
+            dispatcherMapping |= ASYNC;
+        }
+    }
+    
+    public int getDispatcherMapping() {
+        // per the SRV.6.2.5 absence of any dispatcher elements is
+        // equivalent to a REQUEST value
+        if (dispatcherMapping == NOT_SET) return REQUEST;
+        
+        return dispatcherMapping; 
+    }
+
+    public String[] getDispatcherNames() {
+        ArrayList<String> result = new ArrayList<String>();
+        if ((dispatcherMapping & FORWARD) > 0) {
+            result.add(DispatcherType.FORWARD.name());
+        }
+        if ((dispatcherMapping & INCLUDE) > 0) {
+            result.add(DispatcherType.INCLUDE.name());
+        }
+        if ((dispatcherMapping & REQUEST) > 0) {
+            result.add(DispatcherType.REQUEST.name());
+        }
+        if ((dispatcherMapping & ERROR) > 0) {
+            result.add(DispatcherType.ERROR.name());
+        }
+        if ((dispatcherMapping & ASYNC) > 0) {
+            result.add(DispatcherType.ASYNC.name());
+        }
+        return result.toArray(new String[result.size()]);
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Render a String representation of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("FilterMap[");
+        sb.append("filterName=");
+        sb.append(this.filterName);
+        for (int i = 0; i < servletNames.length; i++) {
+            sb.append(", servletName=");
+            sb.append(servletNames[i]);
+        }
+        for (int i = 0; i < urlPatterns.length; i++) {
+            sb.append(", urlPattern=");
+            sb.append(urlPatterns[i]);
+        }
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/Injectable.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/Injectable.java
new file mode 100644
index 0000000..32fa5e1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/Injectable.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.catalina.deploy;
+
+import java.util.List;
+
+public interface Injectable {
+    public String getName();
+    public void addInjectionTarget(String injectionTargetName, String jndiName);
+    public List<InjectionTarget> getInjectionTargets();
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/InjectionTarget.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/InjectionTarget.java
new file mode 100644
index 0000000..520120f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/InjectionTarget.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.catalina.deploy;
+
+/**
+ * @version $Id: InjectionTarget.java,v 1.1 2011/06/28 21:08:22 rherrmann Exp $
+ */
+public class InjectionTarget {
+    private String targetClass;
+    private String targetName;
+
+
+    public InjectionTarget() {
+        // NOOP
+    }
+
+    public InjectionTarget(String targetClass, String targetName) {
+        this.targetClass = targetClass;
+        this.targetName = targetName;
+    }
+
+    public String getTargetClass() {
+        return targetClass;
+    }
+
+    public void setTargetClass(String targetClass) {
+        this.targetClass = targetClass;
+    }
+
+    public String getTargetName() {
+        return targetName;
+    }
+
+    public void setTargetName(String targetName) {
+        this.targetName = targetName;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/JspPropertyGroup.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/JspPropertyGroup.java
new file mode 100644
index 0000000..5b00550
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/JspPropertyGroup.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.catalina.deploy;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Representation of a jsp-property-group element in web.xml.
+ */
+public class JspPropertyGroup {
+    private Boolean deferredSyntax = null;
+    public void setDeferredSyntax(String deferredSyntax) {
+        this.deferredSyntax = Boolean.valueOf(deferredSyntax);
+    }
+    public Boolean getDeferredSyntax() { return deferredSyntax; }
+
+    private Boolean elIgnored = null;
+    public void setElIgnored(String elIgnored) {
+        this.elIgnored = Boolean.valueOf(elIgnored);
+    }
+    public Boolean getElIgnored() { return elIgnored; }
+
+    private Set<String> includeCodas = new LinkedHashSet<String>();
+    public void addIncludeCoda(String includeCoda) {
+        includeCodas.add(includeCoda);
+    }
+    public Set<String> getIncludeCodas() { return includeCodas; }
+
+    private Set<String> includePreludes = new LinkedHashSet<String>();
+    public void addIncludePrelude(String includePrelude) {
+        includePreludes.add(includePrelude);
+    }
+    public Set<String> getIncludePreludes() { return includePreludes; }
+
+    private Boolean isXml = null;
+    public void setIsXml(String isXml) {
+        this.isXml = Boolean.valueOf(isXml);
+    }
+    public Boolean getIsXml() { return isXml; }
+
+    private String pageEncoding = null;
+    public void setPageEncoding(String pageEncoding) {
+        this.pageEncoding = pageEncoding;
+    }
+    public String getPageEncoding() { return this.pageEncoding; }
+    
+    private Boolean scriptingInvalid = null;
+    public void setScriptingInvalid(String scriptingInvalid) {
+        this.scriptingInvalid = Boolean.valueOf(scriptingInvalid);
+    }
+    public Boolean getScriptingInvalid() { return scriptingInvalid; }
+
+    private Boolean trimWhitespace = null;
+    public void setTrimWhitespace(String trimWhitespace) {
+        this.trimWhitespace = Boolean.valueOf(trimWhitespace);
+    }
+    public Boolean getTrimWhitespace() { return trimWhitespace; }
+
+    private String urlPattern = null;
+    public void setUrlPattern(String urlPattern) {
+        this.urlPattern = urlPattern;
+    }
+    public String getUrlPattern() { return this.urlPattern; }
+    
+    private String defaultContentType = null;
+    public void setDefaultContentType(String defaultContentType) {
+        this.defaultContentType = defaultContentType;
+    }
+    public String getDefaultContentType() { return this.defaultContentType; }
+    
+    private Integer buffer = null;
+    public void setBuffer(String buffer) {
+        this.buffer = Integer.valueOf(buffer);
+    }
+    public Integer getBuffer() { return this.buffer; }
+    
+    private Boolean errorOnUndeclaredNamespace = null;
+    public void setErrorOnUndeclaredNamespace(
+            String errorOnUndeclaredNamespace) {
+        this.errorOnUndeclaredNamespace =
+            Boolean.valueOf(errorOnUndeclaredNamespace);
+    }
+    public Boolean getErrorOnUndeclaredNamespace() {
+        return this.errorOnUndeclaredNamespace;
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/LocalStrings.properties
new file mode 100644
index 0000000..c225757
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/LocalStrings.properties
@@ -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.
+
+webXml.duplicateEnvEntry=Duplicate env-entry name [{0}]
+webXml.duplicateFilter=Duplicate filter name [{0}]
+webXml.duplicateMessageDestination=Duplicate message-destination name [{0}]
+webXml.duplicateMessageDestinationRef=Duplicate message-destination-ref name [{0}]
+webXml.duplicateResourceEnvRef=Duplicate resource-env-ref name [{0}]
+webXml.duplicateResourceRef=Duplicate resource-ref name [{0}]
+webXml.duplicateTaglibUri=Duplicate tag library URI [{0}]
+webXml.reservedName=A web.xml file was detected using a reserved name [{0}]. The name element will be ignored for this fragment.
+webXml.mergeConflictDisplayName=The display name was defined in multiple fragments with different values including fragment with name [{0}] located at [{1}]
+webXml.mergeConflictErrorPage=The Error Page for [{0}] was defined inconsistently in multiple fragments including fragment with name [{1}] located at [{2}]
+webXml.mergeConflictFilter=The Filter [{0}] was defined inconsistently in multiple fragments including fragment with name [{1}] located at [{2}]
+webXml.mergeConflictLoginConfig=A LoginConfig was defined inconsistently in multiple fragments including fragment with name [{1}] located at [{2}]
+webXml.mergeConflictOrder=Fragment relative ordering contains circular references. Thsi can be resolved by using absolute ordering in web.xml.
+webXml.mergeConflictResource=The Resource [{0}] was defined inconsistently in multiple fragments including fragment with name [{1}] located at [{2}]
+webXml.mergeConflictServlet=The Servlet [{0}] was defined inconsistently in multiple fragments including fragment with name [{1}] located at [{2}]
+webXml.mergeConflictSessionCookieName=The session cookie name was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
+webXml.mergeConflictSessionCookieDomain=The session cookie domain was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
+webXml.mergeConflictSessionCookiePath=The session cookie path was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
+webXml.mergeConflictSessionCookieComment=The session cookie comment was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
+webXml.mergeConflictSessionCookieHttpOnly=The session cookie http-only flag was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
+webXml.mergeConflictSessionCookieSecure=The session cookie secure flag was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
+webXml.mergeConflictSessionCookieMaxAge=The session cookie max-age was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
+webXml.mergeConflictSessionTimeout=The session timeout was defined inconsistently in multiple fragments with different values including fragment with name [{0}] located at [{1}]
+webXml.mergeConflictSessionTrackingMode=The session tracking modes were defined inconsistently in multiple fragments including fragment with name [{0}] located at [{1}]
+webXml.mergeConflictString=The [{0}] with name [{1}] was defined inconsistently in multiple fragments including fragment with name [{2}] located at [{3}]
+webXml.multipleOther=Multiple others entries in ordering
+webxml.unrecognisedPublicId=The public ID [{0}] did not match any of the known public ID's for web.xml files so the version could not be identified
+webXml.version.nfe=Unable to parse [{0}] from the version string [{1}]. This component of the version string will be ignored. 
+webXml.wrongFragmentName=Used a wrong fragment name {0} at web.xml absolute-ordering tag!
+
+namingResources.cleanupCloseFailed=Failed to invoke method [{0}] for resource [{1}] in container [{2}] so no cleanup was performed for that resource
+namingResources.cleanupCloseSecurity=Unable to retrieve method [{0}] for resource [{1}] in container [{2}] so no cleanup was performed for that resource
+namingResources.cleanupNoClose=Resource [{0}] in container [{1}] does not have a [{2}] method so no cleanup was performed for that resource
+namingResources.cleanupNoContext=Failed to retrieve JNDI naming context for container [{0}] so no cleanup was performed for that container
+namingResources.cleanupNoResource=Failed to retrieve JNDI resource [{0}] for container [{1}] so no cleanup was performed for that resource
+namingResources.mbeanCreateFail=Failed to create MBean for naming resource [{0}]
+namingResources.mbeanDestroyFail=Failed to destroy MBean for naming resource [{0}]
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/LoginConfig.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/LoginConfig.java
new file mode 100644
index 0000000..ae3215b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/LoginConfig.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.catalina.deploy;
+
+
+import java.io.Serializable;
+
+import org.apache.catalina.util.RequestUtil;
+
+
+/**
+ * Representation of a login configuration element for a web application,
+ * as represented in a <code>&lt;login-config&gt;</code> element in the
+ * deployment descriptor.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: LoginConfig.java,v 1.1 2011/06/28 21:08:22 rherrmann Exp $
+ */
+
+public class LoginConfig implements Serializable {
+
+
+    private static final long serialVersionUID = 1L;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new LoginConfig with default properties.
+     */
+    public LoginConfig() {
+
+        super();
+
+    }
+
+
+    /**
+     * Construct a new LoginConfig with the specified properties.
+     *
+     * @param authMethod The authentication method
+     * @param realmName The realm name
+     * @param loginPage The login page URI
+     * @param errorPage The error page URI
+     */
+    public LoginConfig(String authMethod, String realmName,
+                       String loginPage, String errorPage) {
+
+        super();
+        setAuthMethod(authMethod);
+        setRealmName(realmName);
+        setLoginPage(loginPage);
+        setErrorPage(errorPage);
+
+    }
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * The authentication method to use for application login.  Must be
+     * BASIC, DIGEST, FORM, or CLIENT-CERT.
+     */
+    private String authMethod = null;
+
+    public String getAuthMethod() {
+        return (this.authMethod);
+    }
+
+    public void setAuthMethod(String authMethod) {
+        this.authMethod = authMethod;
+    }
+
+
+    /**
+     * The context-relative URI of the error page for form login.
+     */
+    private String errorPage = null;
+
+    public String getErrorPage() {
+        return (this.errorPage);
+    }
+
+    public void setErrorPage(String errorPage) {
+        //        if ((errorPage == null) || !errorPage.startsWith("/"))
+        //            throw new IllegalArgumentException
+        //                ("Error Page resource path must start with a '/'");
+        this.errorPage = RequestUtil.URLDecode(errorPage);
+    }
+
+
+    /**
+     * The context-relative URI of the login page for form login.
+     */
+    private String loginPage = null;
+
+    public String getLoginPage() {
+        return (this.loginPage);
+    }
+
+    public void setLoginPage(String loginPage) {
+        //        if ((loginPage == null) || !loginPage.startsWith("/"))
+        //            throw new IllegalArgumentException
+        //                ("Login Page resource path must start with a '/'");
+        this.loginPage = RequestUtil.URLDecode(loginPage);
+    }
+
+
+    /**
+     * The realm name used when challenging the user for authentication
+     * credentials.
+     */
+    private String realmName = null;
+
+    public String getRealmName() {
+        return (this.realmName);
+    }
+
+    public void setRealmName(String realmName) {
+        this.realmName = realmName;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return a String representation of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("LoginConfig[");
+        sb.append("authMethod=");
+        sb.append(authMethod);
+        if (realmName != null) {
+            sb.append(", realmName=");
+            sb.append(realmName);
+        }
+        if (loginPage != null) {
+            sb.append(", loginPage=");
+            sb.append(loginPage);
+        }
+        if (errorPage != null) {
+            sb.append(", errorPage=");
+            sb.append(errorPage);
+        }
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((authMethod == null) ? 0 : authMethod.hashCode());
+        result = prime * result
+                + ((errorPage == null) ? 0 : errorPage.hashCode());
+        result = prime * result
+                + ((loginPage == null) ? 0 : loginPage.hashCode());
+        result = prime * result
+                + ((realmName == null) ? 0 : realmName.hashCode());
+        return result;
+    }
+
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (!(obj instanceof LoginConfig))
+            return false;
+        LoginConfig other = (LoginConfig) obj;
+        if (authMethod == null) {
+            if (other.authMethod != null)
+                return false;
+        } else if (!authMethod.equals(other.authMethod))
+            return false;
+        if (errorPage == null) {
+            if (other.errorPage != null)
+                return false;
+        } else if (!errorPage.equals(other.errorPage))
+            return false;
+        if (loginPage == null) {
+            if (other.loginPage != null)
+                return false;
+        } else if (!loginPage.equals(other.loginPage))
+            return false;
+        if (realmName == null) {
+            if (other.realmName != null)
+                return false;
+        } else if (!realmName.equals(other.realmName))
+            return false;
+        return true;
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/MessageDestination.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/MessageDestination.java
new file mode 100644
index 0000000..7f82eea
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/MessageDestination.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 org.apache.catalina.deploy;
+
+
+/**
+ * <p>Representation of a message destination for a web application, as
+ * represented in a <code>&lt;message-destination&gt;</code> element
+ * in the deployment descriptor.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: MessageDestination.java,v 1.1 2011/06/28 21:08:22 rherrmann Exp $
+ * @since Tomcat 5.0
+ */
+
+public class MessageDestination extends ResourceBase {
+
+    private static final long serialVersionUID = 1L;
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * The display name of this destination.
+     */
+    private String displayName = null;
+
+    public String getDisplayName() {
+        return (this.displayName);
+    }
+
+    public void setDisplayName(String displayName) {
+        this.displayName = displayName;
+    }
+
+
+    /**
+     * The large icon of this destination.
+     */
+    private String largeIcon = null;
+
+    public String getLargeIcon() {
+        return (this.largeIcon);
+    }
+
+    public void setLargeIcon(String largeIcon) {
+        this.largeIcon = largeIcon;
+    }
+
+
+    /**
+     * The small icon of this destination.
+     */
+    private String smallIcon = null;
+
+    public String getSmallIcon() {
+        return (this.smallIcon);
+    }
+
+    public void setSmallIcon(String smallIcon) {
+        this.smallIcon = smallIcon;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return a String representation of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("MessageDestination[");
+        sb.append("name=");
+        sb.append(getName());
+        if (displayName != null) {
+            sb.append(", displayName=");
+            sb.append(displayName);
+        }
+        if (largeIcon != null) {
+            sb.append(", largeIcon=");
+            sb.append(largeIcon);
+        }
+        if (smallIcon != null) {
+            sb.append(", smallIcon=");
+            sb.append(smallIcon);
+        }
+        if (getDescription() != null) {
+            sb.append(", description=");
+            sb.append(getDescription());
+        }
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/MessageDestinationRef.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/MessageDestinationRef.java
new file mode 100644
index 0000000..9eedee1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/MessageDestinationRef.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.deploy;
+
+
+/**
+ * <p>Representation of a message destination reference for a web application,
+ * as represented in a <code>&lt;message-destination-ref&gt;</code> element
+ * in the deployment descriptor.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: MessageDestinationRef.java,v 1.1 2011/06/28 21:08:22 rherrmann Exp $
+ * @since Tomcat 5.0
+ */
+
+public class MessageDestinationRef extends ResourceBase {
+
+    private static final long serialVersionUID = 1L;
+    
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * The link of this destination ref.
+     */
+    private String link = null;
+
+    public String getLink() {
+        return (this.link);
+    }
+
+    public void setLink(String link) {
+        this.link = link;
+    }
+
+
+    /**
+     * The usage of this destination ref.
+     */
+    private String usage = null;
+
+    public String getUsage() {
+        return (this.usage);
+    }
+
+    public void setUsage(String usage) {
+        this.usage = usage;
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return a String representation of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("MessageDestination[");
+        sb.append("name=");
+        sb.append(getName());
+        if (link != null) {
+            sb.append(", link=");
+            sb.append(link);
+        }
+        if (getType() != null) {
+            sb.append(", type=");
+            sb.append(getType());
+        }
+        if (usage != null) {
+            sb.append(", usage=");
+            sb.append(usage);
+        }
+        if (getDescription() != null) {
+            sb.append(", description=");
+            sb.append(getDescription());
+        }
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/MultipartDef.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/MultipartDef.java
new file mode 100644
index 0000000..c3ed794
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/MultipartDef.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.deploy;
+
+import java.io.Serializable;
+
+
+/**
+ * Representation of a the multipart configuration for a servlet.
+ */
+public class MultipartDef implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    // ------------------------------------------------------------- Properties
+    private String location;
+   
+    public String getLocation() {
+        return location;
+    }
+
+    public void setLocation(String location) {
+        this.location = location;
+    }
+    
+    
+    private String maxFileSize;
+
+    public String getMaxFileSize() {
+        return maxFileSize;
+    }
+
+    public void setMaxFileSize(String maxFileSize) {
+        this.maxFileSize = maxFileSize;
+    }
+    
+    
+    private String maxRequestSize;
+
+    public String getMaxRequestSize() {
+        return maxRequestSize;
+    }
+
+    public void setMaxRequestSize(String maxRequestSize) {
+        this.maxRequestSize = maxRequestSize;
+    }
+
+    
+    private String fileSizeThreshold;
+    
+    public String getFileSizeThreshold() {
+        return fileSizeThreshold;
+    }
+
+    public void setFileSizeThreshold(String fileSizeThreshold) {
+        this.fileSizeThreshold = fileSizeThreshold;
+    }
+
+
+    // ---------------------------------------------------------- Object methods
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime
+                * result
+                + ((fileSizeThreshold == null) ? 0 : fileSizeThreshold
+                        .hashCode());
+        result = prime * result
+                + ((location == null) ? 0 : location.hashCode());
+        result = prime * result
+                + ((maxFileSize == null) ? 0 : maxFileSize.hashCode());
+        result = prime * result
+                + ((maxRequestSize == null) ? 0 : maxRequestSize.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof MultipartDef)) {
+            return false;
+        }
+        MultipartDef other = (MultipartDef) obj;
+        if (fileSizeThreshold == null) {
+            if (other.fileSizeThreshold != null) {
+                return false;
+            }
+        } else if (!fileSizeThreshold.equals(other.fileSizeThreshold)) {
+            return false;
+        }
+        if (location == null) {
+            if (other.location != null) {
+                return false;
+            }
+        } else if (!location.equals(other.location)) {
+            return false;
+        }
+        if (maxFileSize == null) {
+            if (other.maxFileSize != null) {
+                return false;
+            }
+        } else if (!maxFileSize.equals(other.maxFileSize)) {
+            return false;
+        }
+        if (maxRequestSize == null) {
+            if (other.maxRequestSize != null) {
+                return false;
+            }
+        } else if (!maxRequestSize.equals(other.maxRequestSize)) {
+            return false;
+        }
+        return true;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/NamingResources.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/NamingResources.java
new file mode 100644
index 0000000..e0372b4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/NamingResources.java
@@ -0,0 +1,1103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.deploy;
+
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Hashtable;
+
+import javax.naming.NamingException;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.Server;
+import org.apache.catalina.mbeans.MBeanUtils;
+import org.apache.catalina.util.LifecycleMBeanBase;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.naming.ContextBindings;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Holds and manages the naming resources defined in the J2EE Enterprise 
+ * Naming Context and their associated JNDI context.
+ *
+ * @author Remy Maucherat
+ * @version $Id: NamingResources.java,v 1.1 2011/06/28 21:08:22 rherrmann Exp $
+ */
+
+public class NamingResources extends LifecycleMBeanBase implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+    
+    private static final Log log = LogFactory.getLog(NamingResources.class);
+    
+    private static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    private volatile boolean resourceRequireExplicitRegistration = false;
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Create a new NamingResources instance.
+     */
+    public NamingResources() {
+        // NOOP
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Associated container object.
+     */
+    private Object container = null;
+
+
+    /**
+     * List of naming entries, keyed by name. The value is the entry type, as
+     * declared by the user.
+     */
+    private Hashtable<String, String> entries =
+        new Hashtable<String, String>();
+
+
+    /**
+     * The EJB resource references for this web application, keyed by name.
+     */
+    private HashMap<String, ContextEjb> ejbs =
+        new HashMap<String, ContextEjb>();
+
+
+    /**
+     * The environment entries for this web application, keyed by name.
+     */
+    private HashMap<String, ContextEnvironment> envs =
+        new HashMap<String, ContextEnvironment>();
+
+
+    /**
+     * The local  EJB resource references for this web application, keyed by
+     * name.
+     */
+    private HashMap<String, ContextLocalEjb> localEjbs =
+        new HashMap<String, ContextLocalEjb>();
+
+
+    /**
+     * The message destination referencess for this web application,
+     * keyed by name.
+     */
+    private HashMap<String, MessageDestinationRef> mdrs =
+        new HashMap<String, MessageDestinationRef>();
+
+
+    /**
+     * The resource environment references for this web application,
+     * keyed by name.
+     */
+    private HashMap<String, ContextResourceEnvRef> resourceEnvRefs =
+        new HashMap<String, ContextResourceEnvRef>();
+
+
+    /**
+     * The resource references for this web application, keyed by name.
+     */
+    private HashMap<String, ContextResource> resources =
+        new HashMap<String, ContextResource>();
+
+
+    /**
+     * The resource links for this web application, keyed by name.
+     */
+    private HashMap<String, ContextResourceLink> resourceLinks =
+        new HashMap<String, ContextResourceLink>();
+
+
+    /**
+     * The web service references for this web application, keyed by name.
+     */
+    private HashMap<String, ContextService> services =
+        new HashMap<String, ContextService>();
+
+
+    /**
+     * The transaction for this webapp.
+     */
+    private ContextTransaction transaction = null;
+
+
+    /**
+     * The property change support for this component.
+     */
+    protected PropertyChangeSupport support = new PropertyChangeSupport(this);
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Get the container with which the naming resources are associated.
+     */
+    public Object getContainer() {
+        return container;
+    }
+
+
+    /**
+     * Set the container with which the naming resources are associated.
+     */
+    public void setContainer(Object container) {
+        this.container = container;
+    }
+
+    
+    /**
+     * Set the transaction object.
+     */
+    public void setTransaction(ContextTransaction transaction) {
+        this.transaction = transaction;
+    }
+    
+
+    /**
+     * Get the transaction object.
+     */
+    public ContextTransaction getTransaction() {
+        return transaction;
+    }
+    
+
+    /**
+     * Add an EJB resource reference for this web application.
+     *
+     * @param ejb New EJB resource reference
+     */
+    public void addEjb(ContextEjb ejb) {
+
+        if (entries.containsKey(ejb.getName())) {
+            return;
+        } else {
+            entries.put(ejb.getName(), ejb.getType());
+        }
+
+        synchronized (ejbs) {
+            ejb.setNamingResources(this);
+            ejbs.put(ejb.getName(), ejb);
+        }
+        support.firePropertyChange("ejb", null, ejb);
+
+    }
+
+
+    /**
+     * Add an environment entry for this web application.
+     *
+     * @param environment New environment entry
+     */
+    public void addEnvironment(ContextEnvironment environment) {
+
+        if (entries.containsKey(environment.getName())) {
+            ContextEnvironment ce = findEnvironment(environment.getName());
+            ContextResourceLink rl = findResourceLink(environment.getName());
+            if (ce != null) {
+                if (ce.getOverride()) {
+                    removeEnvironment(environment.getName());
+                } else {
+                    return;
+                }
+            } else if (rl != null) {
+                // Link. Need to look at the global resources
+                NamingResources global = getServer().getGlobalNamingResources();
+                if (global.findEnvironment(rl.getGlobal()) != null) {
+                    if (global.findEnvironment(rl.getGlobal()).getOverride()) {
+                        removeResourceLink(environment.getName());
+                    } else {
+                        return;
+                    }
+                }
+            } else {
+                // It exists but it isn't an env or a res link...
+                return;
+            }
+        }
+        
+        entries.put(environment.getName(), environment.getType());
+
+        synchronized (envs) {
+            environment.setNamingResources(this);
+            envs.put(environment.getName(), environment);
+        }
+        support.firePropertyChange("environment", null, environment);
+
+        // Register with JMX
+        if (resourceRequireExplicitRegistration) {
+            try {
+                MBeanUtils.createMBean(environment);
+            } catch (Exception e) {
+                log.warn(sm.getString("namingResources.mbeanCreateFail",
+                        environment.getName()), e);
+            }
+        }
+    }
+
+    // Container should be an instance of Server or Context. If it is anything
+    // else, return null which will trigger a NPE.
+    private Server getServer() {
+        if (container instanceof Server) {
+            return (Server) container;
+        }
+        if (container instanceof Context) {
+            // Could do this in one go. Lots of casts so split out for clarity
+            Engine engine =
+                (Engine) ((Context) container).getParent().getParent();
+            return engine.getService().getServer();
+        }
+        return null;
+    }
+
+    /**
+     * Add a local EJB resource reference for this web application.
+     *
+     * @param ejb New EJB resource reference
+     */
+    public void addLocalEjb(ContextLocalEjb ejb) {
+
+        if (entries.containsKey(ejb.getName())) {
+            return;
+        } else {
+            entries.put(ejb.getName(), ejb.getType());
+        }
+
+        synchronized (localEjbs) {
+            ejb.setNamingResources(this);
+            localEjbs.put(ejb.getName(), ejb);
+        }
+        support.firePropertyChange("localEjb", null, ejb);
+
+    }
+
+
+    /**
+     * Add a message destination reference for this web application.
+     *
+     * @param mdr New message destination reference
+     */
+    public void addMessageDestinationRef(MessageDestinationRef mdr) {
+
+        if (entries.containsKey(mdr.getName())) {
+            return;
+        } else {
+            entries.put(mdr.getName(), mdr.getType());
+        }
+
+        synchronized (mdrs) {
+            mdr.setNamingResources(this);
+            mdrs.put(mdr.getName(), mdr);
+        }
+        support.firePropertyChange("messageDestinationRef", null, mdr);
+
+    }
+
+
+    /**
+     * Add a property change listener to this component.
+     *
+     * @param listener The listener to add
+     */
+    public void addPropertyChangeListener(PropertyChangeListener listener) {
+
+        support.addPropertyChangeListener(listener);
+
+    }
+
+
+    /**
+     * Add a resource reference for this web application.
+     *
+     * @param resource New resource reference
+     */
+    public void addResource(ContextResource resource) {
+
+        if (entries.containsKey(resource.getName())) {
+            return;
+        } else {
+            entries.put(resource.getName(), resource.getType());
+        }
+
+        synchronized (resources) {
+            resource.setNamingResources(this);
+            resources.put(resource.getName(), resource);
+        }
+        support.firePropertyChange("resource", null, resource);
+
+        // Register with JMX
+        if (resourceRequireExplicitRegistration) {
+            try {
+                MBeanUtils.createMBean(resource);
+            } catch (Exception e) {
+                log.warn(sm.getString("namingResources.mbeanCreateFail",
+                        resource.getName()), e);
+            }
+        }
+    }
+
+
+    /**
+     * Add a resource environment reference for this web application.
+     *
+     * @param resource The resource
+     */
+    public void addResourceEnvRef(ContextResourceEnvRef resource) {
+
+        if (entries.containsKey(resource.getName())) {
+            return;
+        } else {
+            entries.put(resource.getName(), resource.getType());
+        }
+
+        synchronized (resourceEnvRefs) {
+            resource.setNamingResources(this);
+            resourceEnvRefs.put(resource.getName(), resource);
+        }
+        support.firePropertyChange("resourceEnvRef", null, resource);
+
+    }
+
+
+    /**
+     * Add a resource link for this web application.
+     *
+     * @param resourceLink New resource link
+     */
+    public void addResourceLink(ContextResourceLink resourceLink) {
+
+        if (entries.containsKey(resourceLink.getName())) {
+            return;
+        } else {
+            String value = resourceLink.getType();
+            if (value == null) {
+                value = "";
+            }
+            entries.put(resourceLink.getName(), value);
+        }
+
+        synchronized (resourceLinks) {
+            resourceLink.setNamingResources(this);
+            resourceLinks.put(resourceLink.getName(), resourceLink);
+        }
+        support.firePropertyChange("resourceLink", null, resourceLink);
+
+        // Register with JMX
+        if (resourceRequireExplicitRegistration) {
+            try {
+                MBeanUtils.createMBean(resourceLink);
+            } catch (Exception e) {
+                log.warn(sm.getString("namingResources.mbeanCreateFail",
+                        resourceLink.getName()), e);
+            }
+        }
+    }
+
+
+    /**
+     * Add a web service reference for this web application.
+     *
+     * @param service New web service reference
+     */
+    public void addService(ContextService service) {
+
+        if (entries.containsKey(service.getName())) {
+            return;
+        } else {
+            String value = service.getType();
+            if (value == null) {
+                value = "";
+            }
+            entries.put(service.getName(), value);
+        }
+        
+        synchronized (services) {
+            service.setNamingResources(this);
+            services.put(service.getName(), service);
+        }
+        support.firePropertyChange("service", null, service);
+        
+    }
+
+
+    /**
+     * Return the EJB resource reference with the specified name, if any;
+     * otherwise, return <code>null</code>.
+     *
+     * @param name Name of the desired EJB resource reference
+     */
+    public ContextEjb findEjb(String name) {
+
+        synchronized (ejbs) {
+            return ejbs.get(name);
+        }
+
+    }
+
+
+    /**
+     * Return the defined EJB resource references for this application.
+     * If there are none, a zero-length array is returned.
+     */
+    public ContextEjb[] findEjbs() {
+
+        synchronized (ejbs) {
+            ContextEjb results[] = new ContextEjb[ejbs.size()];
+            return ejbs.values().toArray(results);
+        }
+
+    }
+
+
+    /**
+     * Return the environment entry with the specified name, if any;
+     * otherwise, return <code>null</code>.
+     *
+     * @param name Name of the desired environment entry
+     */
+    public ContextEnvironment findEnvironment(String name) {
+
+        synchronized (envs) {
+            return envs.get(name);
+        }
+
+    }
+
+
+    /**
+     * Return the set of defined environment entries for this web
+     * application.  If none have been defined, a zero-length array
+     * is returned.
+     */
+    public ContextEnvironment[] findEnvironments() {
+
+        synchronized (envs) {
+            ContextEnvironment results[] = new ContextEnvironment[envs.size()];
+            return envs.values().toArray(results);
+        }
+
+    }
+
+
+    /**
+     * Return the local EJB resource reference with the specified name, if any;
+     * otherwise, return <code>null</code>.
+     *
+     * @param name Name of the desired EJB resource reference
+     */
+    public ContextLocalEjb findLocalEjb(String name) {
+
+        synchronized (localEjbs) {
+            return localEjbs.get(name);
+        }
+
+    }
+
+
+    /**
+     * Return the defined local EJB resource references for this application.
+     * If there are none, a zero-length array is returned.
+     */
+    public ContextLocalEjb[] findLocalEjbs() {
+
+        synchronized (localEjbs) {
+            ContextLocalEjb results[] = new ContextLocalEjb[localEjbs.size()];
+            return localEjbs.values().toArray(results);
+        }
+
+    }
+
+
+    /**
+     * Return the message destination reference with the specified name,
+     * if any; otherwise, return <code>null</code>.
+     *
+     * @param name Name of the desired message destination reference
+     */
+    public MessageDestinationRef findMessageDestinationRef(String name) {
+
+        synchronized (mdrs) {
+            return mdrs.get(name);
+        }
+
+    }
+
+
+    /**
+     * Return the defined message destination references for this application.
+     * If there are none, a zero-length array is returned.
+     */
+    public MessageDestinationRef[] findMessageDestinationRefs() {
+
+        synchronized (mdrs) {
+            MessageDestinationRef results[] =
+                new MessageDestinationRef[mdrs.size()];
+            return mdrs.values().toArray(results);
+        }
+
+    }
+
+
+    /**
+     * Return the resource reference with the specified name, if any;
+     * otherwise return <code>null</code>.
+     *
+     * @param name Name of the desired resource reference
+     */
+    public ContextResource findResource(String name) {
+
+        synchronized (resources) {
+            return resources.get(name);
+        }
+
+    }
+
+
+    /**
+     * Return the resource link with the specified name, if any;
+     * otherwise return <code>null</code>.
+     *
+     * @param name Name of the desired resource link
+     */
+    public ContextResourceLink findResourceLink(String name) {
+
+        synchronized (resourceLinks) {
+            return resourceLinks.get(name);
+        }
+
+    }
+
+
+    /**
+     * Return the defined resource links for this application.  If
+     * none have been defined, a zero-length array is returned.
+     */
+    public ContextResourceLink[] findResourceLinks() {
+
+        synchronized (resourceLinks) {
+            ContextResourceLink results[] = 
+                new ContextResourceLink[resourceLinks.size()];
+            return resourceLinks.values().toArray(results);
+        }
+
+    }
+
+
+    /**
+     * Return the defined resource references for this application.  If
+     * none have been defined, a zero-length array is returned.
+     */
+    public ContextResource[] findResources() {
+
+        synchronized (resources) {
+            ContextResource results[] = new ContextResource[resources.size()];
+            return resources.values().toArray(results);
+        }
+
+    }
+
+
+    /**
+     * Return the resource environment reference type for the specified
+     * name, if any; otherwise return <code>null</code>.
+     *
+     * @param name Name of the desired resource environment reference
+     */
+    public ContextResourceEnvRef findResourceEnvRef(String name) {
+
+        synchronized (resourceEnvRefs) {
+            return resourceEnvRefs.get(name);
+        }
+
+    }
+
+
+    /**
+     * Return the set of resource environment reference names for this
+     * web application.  If none have been specified, a zero-length
+     * array is returned.
+     */
+    public ContextResourceEnvRef[] findResourceEnvRefs() {
+
+        synchronized (resourceEnvRefs) {
+            ContextResourceEnvRef results[] = new ContextResourceEnvRef[resourceEnvRefs.size()];
+            return resourceEnvRefs.values().toArray(results);
+        }
+
+    }
+
+
+    /**
+     * Return the web service reference for the specified
+     * name, if any; otherwise return <code>null</code>.
+     *
+     * @param name Name of the desired web service
+     */
+    public ContextService findService(String name) {
+
+        synchronized (services) {
+            return services.get(name);
+        }
+
+    }
+
+
+    /**
+     * Return the defined web service references for this application.  If
+     * none have been defined, a zero-length array is returned.
+     */
+    public ContextService[] findServices() {
+        
+        synchronized (services) {
+            ContextService results[] = new ContextService[services.size()];
+            return services.values().toArray(results);
+        }
+        
+    }
+
+
+    /**
+     * Return true if the name specified already exists.
+     */
+    public boolean exists(String name) {
+
+        return (entries.containsKey(name));
+
+    }
+
+
+    /**
+     * Remove any EJB resource reference with the specified name.
+     *
+     * @param name Name of the EJB resource reference to remove
+     */
+    public void removeEjb(String name) {
+
+        entries.remove(name);
+
+        ContextEjb ejb = null;
+        synchronized (ejbs) {
+            ejb = ejbs.remove(name);
+        }
+        if (ejb != null) {
+            support.firePropertyChange("ejb", ejb, null);
+            ejb.setNamingResources(null);
+        }
+
+    }
+
+
+    /**
+     * Remove any environment entry with the specified name.
+     *
+     * @param name Name of the environment entry to remove
+     */
+    public void removeEnvironment(String name) {
+
+        entries.remove(name);
+
+        ContextEnvironment environment = null;
+        synchronized (envs) {
+            environment = envs.remove(name);
+        }
+        if (environment != null) {
+            support.firePropertyChange("environment", environment, null);
+            // De-register with JMX
+            if (resourceRequireExplicitRegistration) {
+                try {
+                    MBeanUtils.destroyMBean(environment);
+                } catch (Exception e) {
+                    log.warn(sm.getString("namingResources.mbeanDestroyFail",
+                            environment.getName()), e);
+                }
+            }
+            environment.setNamingResources(null);
+        }
+    }
+
+
+    /**
+     * Remove any local EJB resource reference with the specified name.
+     *
+     * @param name Name of the EJB resource reference to remove
+     */
+    public void removeLocalEjb(String name) {
+
+        entries.remove(name);
+
+        ContextLocalEjb localEjb = null;
+        synchronized (localEjbs) {
+            localEjb = localEjbs.remove(name);
+        }
+        if (localEjb != null) {
+            support.firePropertyChange("localEjb", localEjb, null);
+            localEjb.setNamingResources(null);
+        }
+
+    }
+
+
+    /**
+     * Remove any message destination reference with the specified name.
+     *
+     * @param name Name of the message destination resource reference to remove
+     */
+    public void removeMessageDestinationRef(String name) {
+
+        entries.remove(name);
+
+        MessageDestinationRef mdr = null;
+        synchronized (mdrs) {
+            mdr = mdrs.remove(name);
+        }
+        if (mdr != null) {
+            support.firePropertyChange("messageDestinationRef",
+                                       mdr, null);
+            mdr.setNamingResources(null);
+        }
+
+    }
+
+
+    /**
+     * Remove a property change listener from this component.
+     *
+     * @param listener The listener to remove
+     */
+    public void removePropertyChangeListener(PropertyChangeListener listener) {
+
+        support.removePropertyChangeListener(listener);
+
+    }
+
+
+    /**
+     * Remove any resource reference with the specified name.
+     *
+     * @param name Name of the resource reference to remove
+     */
+    public void removeResource(String name) {
+
+        entries.remove(name);
+
+        ContextResource resource = null;
+        synchronized (resources) {
+            resource = resources.remove(name);
+        }
+        if (resource != null) {
+            support.firePropertyChange("resource", resource, null);
+            // De-register with JMX
+            if (resourceRequireExplicitRegistration) {
+                try {
+                    MBeanUtils.destroyMBean(resource);
+                } catch (Exception e) {
+                    log.warn(sm.getString("namingResources.mbeanDestroyFail",
+                            resource.getName()), e);
+                }
+            }
+            resource.setNamingResources(null);
+        }
+    }
+
+
+    /**
+     * Remove any resource environment reference with the specified name.
+     *
+     * @param name Name of the resource environment reference to remove
+     */
+    public void removeResourceEnvRef(String name) {
+
+        entries.remove(name);
+
+        ContextResourceEnvRef resourceEnvRef = null;
+        synchronized (resourceEnvRefs) {
+            resourceEnvRef =
+                resourceEnvRefs.remove(name);
+        }
+        if (resourceEnvRef != null) {
+            support.firePropertyChange("resourceEnvRef", resourceEnvRef, null);
+            resourceEnvRef.setNamingResources(null);
+        }
+
+    }
+
+
+    /**
+     * Remove any resource link with the specified name.
+     *
+     * @param name Name of the resource link to remove
+     */
+    public void removeResourceLink(String name) {
+
+        entries.remove(name);
+
+        ContextResourceLink resourceLink = null;
+        synchronized (resourceLinks) {
+            resourceLink = resourceLinks.remove(name);
+        }
+        if (resourceLink != null) {
+            support.firePropertyChange("resourceLink", resourceLink, null);
+            // De-register with JMX
+            if (resourceRequireExplicitRegistration) {
+                try {
+                    MBeanUtils.destroyMBean(resourceLink);
+                } catch (Exception e) {
+                    log.warn(sm.getString("namingResources.mbeanDestroyFail",
+                            resourceLink.getName()), e);
+                }
+            }
+            resourceLink.setNamingResources(null);
+        }
+    }
+
+
+    /**
+     * Remove any web service reference with the specified name.
+     *
+     * @param name Name of the web service reference to remove
+     */
+    public void removeService(String name) {
+        
+        entries.remove(name);
+        
+        ContextService service = null;
+        synchronized (services) {
+            service = services.remove(name);
+        }
+        if (service != null) {
+            support.firePropertyChange("service", service, null);
+            service.setNamingResources(null);
+        }
+        
+    }
+
+
+    // ------------------------------------------------------- Lifecycle methods
+    
+    @Override
+    protected void initInternal() throws LifecycleException {
+        super.initInternal();
+        
+        // Set this before we register currently known naming resources to avoid
+        // timing issues. Duplication registration is not an issue.
+        resourceRequireExplicitRegistration = true;
+        
+        for (ContextResource cr : resources.values()) {
+            try {
+                MBeanUtils.createMBean(cr);
+            } catch (Exception e) {
+                log.warn(sm.getString(
+                        "namingResources.mbeanCreateFail", cr.getName()), e);
+            }
+        }
+        
+        for (ContextEnvironment ce : envs.values()) {
+            try {
+                MBeanUtils.createMBean(ce);
+            } catch (Exception e) {
+                log.warn(sm.getString(
+                        "namingResources.mbeanCreateFail", ce.getName()), e);
+            }
+        }
+        
+        for (ContextResourceLink crl : resourceLinks.values()) {
+            try {
+                MBeanUtils.createMBean(crl);
+            } catch (Exception e) {
+                log.warn(sm.getString(
+                        "namingResources.mbeanCreateFail", crl.getName()), e);
+            }
+        }
+    }
+
+
+    @Override
+    protected void startInternal() throws LifecycleException {
+        fireLifecycleEvent(CONFIGURE_START_EVENT, null);
+        setState(LifecycleState.STARTING);
+    }
+
+
+    @Override
+    protected void stopInternal() throws LifecycleException {
+        cleanUp();
+        setState(LifecycleState.STOPPING);
+        fireLifecycleEvent(CONFIGURE_STOP_EVENT, null);
+    }
+
+    /**
+     * Close those resources that an explicit close may help clean-up faster.
+     */
+    private void cleanUp() {
+        if (resources.size() == 0) {
+            return;
+        }
+        javax.naming.Context ctxt;
+        try {
+            if (container instanceof Server) {
+                ctxt = ((Server) container).getGlobalNamingContext();
+            } else {
+                ctxt = ContextBindings.getClassLoader();
+                ctxt = (javax.naming.Context) ctxt.lookup("comp/env");
+            }
+        } catch (NamingException e) {
+            log.warn(sm.getString("namingResources.cleanupNoContext",
+                    container), e);
+            return;
+        }
+        for (ContextResource cr: resources.values()) {
+            if (cr.getSingleton()) {
+                String closeMethod = cr.getCloseMethod(); 
+                if (closeMethod != null && closeMethod.length() > 0) {
+                    String name = cr.getName();
+                    Object resource;
+                    try {
+                         resource = ctxt.lookup(name);
+                    } catch (NamingException e) {
+                        log.warn(sm.getString(
+                                "namingResources.cleanupNoResource",
+                                cr.getName(), container), e);
+                        continue;
+                    }
+                    cleanUp(resource, name, closeMethod);
+                }
+            }
+        }
+    }
+
+    
+    /**
+     * Clean up a resource by calling the defined close method. For example,
+     * closing a database connection pool will close it's open connections. This
+     * will happen on GC but that leaves db connections open that may cause
+     * issues.
+     * 
+     * @param resource  The resource to close.
+     */
+    private void cleanUp(Object resource, String name, String closeMethod) {
+        // Look for a zero-arg close() method
+        Method m = null;
+        try {
+            m = resource.getClass().getMethod(closeMethod, (Class<?>[]) null);
+        } catch (SecurityException e) {
+            log.debug(sm.getString("namingResources.cleanupCloseSecurity",
+                    closeMethod, name, container));
+            return;
+        } catch (NoSuchMethodException e) {
+            log.debug(sm.getString("namingResources.cleanupNoClose",
+                    name, container, closeMethod));
+            return;
+        }
+        if (m != null) {
+            try {
+                m.invoke(resource, (Object[]) null);
+            } catch (IllegalArgumentException e) {
+                log.warn(sm.getString("namingResources.cleanupCloseFailed",
+                        closeMethod, name, container), e);
+            } catch (IllegalAccessException e) {
+                log.warn(sm.getString("namingResources.cleanupCloseFailed",
+                        closeMethod, name, container), e);
+            } catch (InvocationTargetException e) {
+                log.warn(sm.getString("namingResources.cleanupCloseFailed",
+                        closeMethod, name, container), e);
+            }
+        }
+    }
+
+    @Override
+    protected void destroyInternal() throws LifecycleException {
+
+        // Set this before we de-register currently known naming resources to
+        // avoid timing issues. Duplication de-registration is not an issue.
+        resourceRequireExplicitRegistration = false;
+
+        // Destroy in reverse order to create, although it should not matter
+        for (ContextResourceLink crl : resourceLinks.values()) {
+            try {
+                MBeanUtils.destroyMBean(crl);
+            } catch (Exception e) {
+                log.warn(sm.getString(
+                        "namingResources.mbeanDestroyFail", crl.getName()), e);
+            }
+        }
+        
+        for (ContextEnvironment ce : envs.values()) {
+            try {
+                MBeanUtils.destroyMBean(ce);
+            } catch (Exception e) {
+                log.warn(sm.getString(
+                        "namingResources.mbeanDestroyFail", ce.getName()), e);
+            }
+        }
+        
+        for (ContextResource cr : resources.values()) {
+            try {
+                MBeanUtils.destroyMBean(cr);
+            } catch (Exception e) {
+                log.warn(sm.getString(
+                        "namingResources.mbeanDestroyFail", cr.getName()), e);
+            }
+        }
+        
+        super.destroyInternal();
+    }
+
+
+    @Override
+    protected String getDomainInternal() {
+        // Use the same domain as our associated container if we have one
+        Object c = getContainer();
+        
+        if (c instanceof LifecycleMBeanBase) {
+            return ((LifecycleMBeanBase) c).getDomain();
+        }
+
+        return null;
+    }
+
+
+    @Override
+    protected String getObjectNameKeyProperties() {
+        Object c = getContainer();
+        if (c instanceof Container) {
+            return "type=NamingResources" +
+                    MBeanUtils.getContainerKeyProperties((Container) c);
+        }
+        // Server or just unknown
+        return "type=NamingResources";
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ResourceBase.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ResourceBase.java
new file mode 100644
index 0000000..3dacc5b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ResourceBase.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.deploy;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+
+/**
+ * Representation of an Context element
+ *
+ * @author Peter Rossbach (pero@apache.org)
+ * @version $Id: ResourceBase.java,v 1.1 2011/06/28 21:08:22 rherrmann Exp $
+ */
+
+public class ResourceBase implements Serializable, Injectable {
+
+    private static final long serialVersionUID = 1L;
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * The description of this resource.
+     */
+    private String description = null;
+
+    public String getDescription() {
+        return (this.description);
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+
+
+    /**
+     * The name of this resource.
+     */
+    private String name = null;
+
+    @Override
+    public String getName() {
+        return (this.name);
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+
+    /**
+     * The name of the resource implementation class.
+     */
+    private String type = null;
+
+    public String getType() {
+        return (this.type);
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+
+    /**
+     * Holder for our configured properties.
+     */
+    private HashMap<String, Object> properties = new HashMap<String, Object>();
+
+    /**
+     * Return a configured property.
+     */
+    public Object getProperty(String name) {
+        return properties.get(name);
+    }
+
+    /**
+     * Set a configured property.
+     */
+    public void setProperty(String name, Object value) {
+        properties.put(name, value);
+    }
+
+    /** 
+     * Remove a configured property.
+     */
+    public void removeProperty(String name) {
+        properties.remove(name);
+    }
+
+    /**
+     * List properties.
+     */
+    public Iterator<String> listProperties() {
+        return properties.keySet().iterator();
+    }
+
+    private List<InjectionTarget> injectionTargets = new ArrayList<InjectionTarget>();
+
+    @Override
+    public void addInjectionTarget(String injectionTargetName, String jndiName) {
+        InjectionTarget target = new InjectionTarget(injectionTargetName, jndiName);
+        injectionTargets.add(target);
+    }
+
+    @Override
+    public List<InjectionTarget> getInjectionTargets() {
+        return injectionTargets;
+    }
+
+    // -------------------------------------------------------- Package Methods
+
+
+    /**
+     * The NamingResources with which we are associated (if any).
+     */
+    protected NamingResources resources = null;
+
+    public NamingResources getNamingResources() {
+        return (this.resources);
+    }
+
+    void setNamingResources(NamingResources resources) {
+        this.resources = resources;
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/SecurityCollection.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/SecurityCollection.java
new file mode 100644
index 0000000..4c187d7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/SecurityCollection.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.catalina.deploy;
+
+
+import java.io.Serializable;
+
+import org.apache.catalina.util.RequestUtil;
+
+
+/**
+ * Representation of a web resource collection for a web application's security
+ * constraint, as represented in a <code>&lt;web-resource-collection&gt;</code>
+ * element in the deployment descriptor.
+ * <p>
+ * <b>WARNING</b>:  It is assumed that instances of this class will be created
+ * and modified only within the context of a single thread, before the instance
+ * is made visible to the remainder of the application.  After that, only read
+ * access is expected.  Therefore, none of the read and write access within
+ * this class is synchronized.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: SecurityCollection.java,v 1.1 2011/06/28 21:08:22 rherrmann Exp $
+ */
+
+public class SecurityCollection implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new security collection instance with default values.
+     */
+    public SecurityCollection() {
+
+        this(null, null);
+
+    }
+
+
+    /**
+     * Construct a new security collection instance with specified values.
+     *
+     * @param name Name of this security collection
+     */
+    public SecurityCollection(String name) {
+
+        this(name, null);
+
+    }
+
+
+    /**
+     * Construct a new security collection instance with specified values.
+     *
+     * @param name Name of this security collection
+     * @param description Description of this security collection
+     */
+    public SecurityCollection(String name, String description) {
+
+        super();
+        setName(name);
+        setDescription(description);
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Description of this web resource collection.
+     */
+    private String description = null;
+
+
+    /**
+     * The HTTP methods explicitly covered by this web resource collection.
+     */
+    private String methods[] = new String[0];
+
+
+    /**
+     * The HTTP methods explicitly excluded from this web resource collection.
+     */
+    private String omittedMethods[] = new String[0];
+
+    /**
+     * The name of this web resource collection.
+     */
+    private String name = null;
+
+
+    /**
+     * The URL patterns protected by this security collection.
+     */
+    private String patterns[] = new String[0];
+
+
+    /**
+     * This security collection was established by a deployment descriptor.
+     * Defaults to <code>true</code>.
+     */
+    private boolean isFromDescriptor = true;
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the description of this web resource collection.
+     */
+    public String getDescription() {
+
+        return (this.description);
+
+    }
+
+
+    /**
+     * Set the description of this web resource collection.
+     *
+     * @param description The new description
+     */
+    public void setDescription(String description) {
+
+        this.description = description;
+
+    }
+
+
+    /**
+     * Return the name of this web resource collection.
+     */
+    public String getName() {
+
+        return (this.name);
+
+    }
+
+
+    /**
+     * Set the name of this web resource collection
+     *
+     * @param name The new name
+     */
+    public void setName(String name) {
+
+        this.name = name;
+
+    }
+
+
+    /**
+     * Return if this constraint was defined in a deployment descriptor.
+     */
+    public boolean isFromDescriptor() {
+        return isFromDescriptor;
+    }
+
+
+    /**
+     * Set if this constraint was defined in a deployment descriptor.
+     */
+    public void setFromDescriptor(boolean isFromDescriptor) {
+        this.isFromDescriptor = isFromDescriptor;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add an HTTP request method to be explicitly part of this web resource
+     * collection.
+     */
+    public void addMethod(String method) {
+
+        if (method == null)
+            return;
+        String results[] = new String[methods.length + 1];
+        for (int i = 0; i < methods.length; i++)
+            results[i] = methods[i];
+        results[methods.length] = method;
+        methods = results;
+
+    }
+
+
+    /**
+     * Add an HTTP request method to the methods explicitly excluded from this
+     * web resource collection.
+     */
+    public void addOmittedMethod(String method) {
+        if (method == null)
+            return;
+        String results[] = new String[omittedMethods.length + 1];
+        for (int i = 0; i < omittedMethods.length; i++)
+            results[i] = omittedMethods[i];
+        results[omittedMethods.length] = method;
+        omittedMethods = results;
+    }
+
+    /**
+     * Add a URL pattern to be part of this web resource collection.
+     */
+    public void addPattern(String pattern) {
+
+        if (pattern == null)
+            return;
+
+        String decodedPattern = RequestUtil.URLDecode(pattern);
+        String results[] = new String[patterns.length + 1];
+        for (int i = 0; i < patterns.length; i++) {
+            results[i] = patterns[i];
+        }
+        results[patterns.length] = decodedPattern;
+        patterns = results;
+
+    }
+
+
+    /**
+     * Return <code>true</code> if the specified HTTP request method is
+     * part of this web resource collection.
+     *
+     * @param method Request method to check
+     */
+    public boolean findMethod(String method) {
+
+        if (methods.length == 0 && omittedMethods.length == 0)
+            return (true);
+        if (methods.length > 0) {
+            for (int i = 0; i < methods.length; i++) {
+                if (methods[i].equals(method))
+                    return true;
+            }
+            return false;
+        }
+        if (omittedMethods.length > 0) {
+            for (int i = 0; i < omittedMethods.length; i++) {
+                if (omittedMethods[i].equals(method))
+                    return false;
+            }
+        }
+        return true;
+    }
+
+
+    /**
+     * Return the set of HTTP request methods that are part of this web
+     * resource collection, or a zero-length array if no methods have been
+     * explicitly included.
+     */
+    public String[] findMethods() {
+
+        return (methods);
+
+    }
+
+
+    /**
+     * Return the set of HTTP request methods that are explicitly excluded from
+     * this web resource collection, or a zero-length array if no request
+     * methods are excluded.
+     */
+    public String[] findOmittedMethods() {
+
+        return (omittedMethods);
+
+    }
+
+
+    /**
+     * Is the specified pattern part of this web resource collection?
+     *
+     * @param pattern Pattern to be compared
+     */
+    public boolean findPattern(String pattern) {
+
+        for (int i = 0; i < patterns.length; i++) {
+            if (patterns[i].equals(pattern))
+                return (true);
+        }
+        return (false);
+
+    }
+
+
+    /**
+     * Return the set of URL patterns that are part of this web resource
+     * collection.  If none have been specified, a zero-length array is
+     * returned.
+     */
+    public String[] findPatterns() {
+
+        return (patterns);
+
+    }
+
+
+    /**
+     * Remove the specified HTTP request method from those that are part
+     * of this web resource collection.
+     *
+     * @param method Request method to be removed
+     */
+    public void removeMethod(String method) {
+
+        if (method == null)
+            return;
+        int n = -1;
+        for (int i = 0; i < methods.length; i++) {
+            if (methods[i].equals(method)) {
+                n = i;
+                break;
+            }
+        }
+        if (n >= 0) {
+            int j = 0;
+            String results[] = new String[methods.length - 1];
+            for (int i = 0; i < methods.length; i++) {
+                if (i != n)
+                    results[j++] = methods[i];
+            }
+            methods = results;
+        }
+
+    }
+
+
+    /**
+     * Remove the specified HTTP request method from those that are explicitly
+     * excluded from this web resource collection.
+     *
+     * @param method Request method to be removed
+     */
+    public void removeOmittedMethod(String method) {
+
+        if (method == null)
+            return;
+        int n = -1;
+        for (int i = 0; i < omittedMethods.length; i++) {
+            if (omittedMethods[i].equals(method)) {
+                n = i;
+                break;
+            }
+        }
+        if (n >= 0) {
+            int j = 0;
+            String results[] = new String[omittedMethods.length - 1];
+            for (int i = 0; i < omittedMethods.length; i++) {
+                if (i != n)
+                    results[j++] = omittedMethods[i];
+            }
+            omittedMethods = results;
+        }
+
+    }
+
+
+    /**
+     * Remove the specified URL pattern from those that are part of this
+     * web resource collection.
+     *
+     * @param pattern Pattern to be removed
+     */
+    public void removePattern(String pattern) {
+
+        if (pattern == null)
+            return;
+        int n = -1;
+        for (int i = 0; i < patterns.length; i++) {
+            if (patterns[i].equals(pattern)) {
+                n = i;
+                break;
+            }
+        }
+        if (n >= 0) {
+            int j = 0;
+            String results[] = new String[patterns.length - 1];
+            for (int i = 0; i < patterns.length; i++) {
+                if (i != n)
+                    results[j++] = patterns[i];
+            }
+            patterns = results;
+        }
+
+    }
+
+
+    /**
+     * Return a String representation of this security collection.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("SecurityCollection[");
+        sb.append(name);
+        if (description != null) {
+            sb.append(", ");
+            sb.append(description);
+        }
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/SecurityConstraint.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/SecurityConstraint.java
new file mode 100644
index 0000000..2cb7725
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/SecurityConstraint.java
@@ -0,0 +1,549 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.deploy;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.servlet.HttpConstraintElement;
+import javax.servlet.HttpMethodConstraintElement;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.annotation.ServletSecurity;
+import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
+
+
+/**
+ * Representation of a security constraint element for a web application,
+ * as represented in a <code>&lt;security-constraint&gt;</code> element in the
+ * deployment descriptor.
+ * <p>
+ * <b>WARNING</b>:  It is assumed that instances of this class will be created
+ * and modified only within the context of a single thread, before the instance
+ * is made visible to the remainder of the application.  After that, only read
+ * access is expected.  Therefore, none of the read and write access within
+ * this class is synchronized.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: SecurityConstraint.java,v 1.1 2011/06/28 21:08:22 rherrmann Exp $
+ */
+
+public class SecurityConstraint implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new security constraint instance with default values.
+     */
+    public SecurityConstraint() {
+
+        super();
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Was the "all roles" wildcard included in the authorization constraints
+     * for this security constraint?
+     */
+    private boolean allRoles = false;
+
+
+    /**
+     * Was an authorization constraint included in this security constraint?
+     * This is necessary to distinguish the case where an auth-constraint with
+     * no roles (signifying no direct access at all) was requested, versus
+     * a lack of auth-constraint which implies no access control checking.
+     */
+    private boolean authConstraint = false;
+
+
+    /**
+     * The set of roles permitted to access resources protected by this
+     * security constraint.
+     */
+    private String authRoles[] = new String[0];
+
+
+    /**
+     * The set of web resource collections protected by this security
+     * constraint.
+     */
+    private SecurityCollection collections[] = new SecurityCollection[0];
+
+
+    /**
+     * The display name of this security constraint.
+     */
+    private String displayName = null;
+
+
+    /**
+     * The user data constraint for this security constraint.  Must be NONE,
+     * INTEGRAL, or CONFIDENTIAL.
+     */
+    private String userConstraint = "NONE";
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Was the "all roles" wildcard included in this authentication
+     * constraint?
+     */
+    public boolean getAllRoles() {
+
+        return (this.allRoles);
+
+    }
+
+
+    /**
+     * Return the authorization constraint present flag for this security
+     * constraint.
+     */
+    public boolean getAuthConstraint() {
+
+        return (this.authConstraint);
+
+    }
+
+
+    /**
+     * Set the authorization constraint present flag for this security
+     * constraint.
+     */
+    public void setAuthConstraint(boolean authConstraint) {
+
+        this.authConstraint = authConstraint;
+
+    }
+
+
+    /**
+     * Return the display name of this security constraint.
+     */
+    public String getDisplayName() {
+
+        return (this.displayName);
+
+    }
+
+
+    /**
+     * Set the display name of this security constraint.
+     */
+    public void setDisplayName(String displayName) {
+
+        this.displayName = displayName;
+
+    }
+
+
+    /**
+     * Return the user data constraint for this security constraint.
+     */
+    public String getUserConstraint() {
+
+        return (userConstraint);
+
+    }
+
+
+    /**
+     * Set the user data constraint for this security constraint.
+     *
+     * @param userConstraint The new user data constraint
+     */
+    public void setUserConstraint(String userConstraint) {
+
+        if (userConstraint != null)
+            this.userConstraint = userConstraint;
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add an authorization role, which is a role name that will be
+     * permitted access to the resources protected by this security constraint.
+     *
+     * @param authRole Role name to be added
+     */
+    public void addAuthRole(String authRole) {
+
+        if (authRole == null)
+            return;
+        if ("*".equals(authRole)) {
+            allRoles = true;
+            return;
+        }
+        String results[] = new String[authRoles.length + 1];
+        for (int i = 0; i < authRoles.length; i++)
+            results[i] = authRoles[i];
+        results[authRoles.length] = authRole;
+        authRoles = results;
+        authConstraint = true;
+
+    }
+
+
+    /**
+     * Add a new web resource collection to those protected by this
+     * security constraint.
+     *
+     * @param collection The new web resource collection
+     */
+    public void addCollection(SecurityCollection collection) {
+
+        if (collection == null)
+            return;
+        SecurityCollection results[] =
+            new SecurityCollection[collections.length + 1];
+        for (int i = 0; i < collections.length; i++)
+            results[i] = collections[i];
+        results[collections.length] = collection;
+        collections = results;
+
+    }
+
+
+    /**
+     * Return <code>true</code> if the specified role is permitted access to
+     * the resources protected by this security constraint.
+     *
+     * @param role Role name to be checked
+     */
+    public boolean findAuthRole(String role) {
+
+        if (role == null)
+            return (false);
+        for (int i = 0; i < authRoles.length; i++) {
+            if (role.equals(authRoles[i]))
+                return (true);
+        }
+        return (false);
+
+    }
+
+
+    /**
+     * Return the set of roles that are permitted access to the resources
+     * protected by this security constraint.  If none have been defined,
+     * a zero-length array is returned (which implies that all authenticated
+     * users are permitted access).
+     */
+    public String[] findAuthRoles() {
+
+        return (authRoles);
+
+    }
+
+
+    /**
+     * Return the web resource collection for the specified name, if any;
+     * otherwise, return <code>null</code>.
+     *
+     * @param name Web resource collection name to return
+     */
+    public SecurityCollection findCollection(String name) {
+
+        if (name == null)
+            return (null);
+        for (int i = 0; i < collections.length; i++) {
+            if (name.equals(collections[i].getName()))
+                return (collections[i]);
+        }
+        return (null);
+
+    }
+
+
+    /**
+     * Return all of the web resource collections protected by this
+     * security constraint.  If there are none, a zero-length array is
+     * returned.
+     */
+    public SecurityCollection[] findCollections() {
+
+        return (collections);
+
+    }
+
+
+    /**
+     * Return <code>true</code> if the specified context-relative URI (and
+     * associated HTTP method) are protected by this security constraint.
+     *
+     * @param uri Context-relative URI to check
+     * @param method Request method being used
+     */
+    public boolean included(String uri, String method) {
+
+        // We cannot match without a valid request method
+        if (method == null)
+            return (false);
+
+        // Check all of the collections included in this constraint
+        for (int i = 0; i < collections.length; i++) {
+            if (!collections[i].findMethod(method))
+                continue;
+            String patterns[] = collections[i].findPatterns();
+            for (int j = 0; j < patterns.length; j++) {
+                if (matchPattern(uri, patterns[j]))
+                    return (true);
+            }
+        }
+
+        // No collection included in this constraint matches this request
+        return (false);
+
+    }
+
+
+    /**
+     * Remove the specified role from the set of roles permitted to access
+     * the resources protected by this security constraint.
+     *
+     * @param authRole Role name to be removed
+     */
+    public void removeAuthRole(String authRole) {
+
+        if (authRole == null)
+            return;
+        int n = -1;
+        for (int i = 0; i < authRoles.length; i++) {
+            if (authRoles[i].equals(authRole)) {
+                n = i;
+                break;
+            }
+        }
+        if (n >= 0) {
+            int j = 0;
+            String results[] = new String[authRoles.length - 1];
+            for (int i = 0; i < authRoles.length; i++) {
+                if (i != n)
+                    results[j++] = authRoles[i];
+            }
+            authRoles = results;
+        }
+
+    }
+
+
+    /**
+     * Remove the specified web resource collection from those protected by
+     * this security constraint.
+     *
+     * @param collection Web resource collection to be removed
+     */
+    public void removeCollection(SecurityCollection collection) {
+
+        if (collection == null)
+            return;
+        int n = -1;
+        for (int i = 0; i < collections.length; i++) {
+            if (collections[i].equals(collection)) {
+                n = i;
+                break;
+            }
+        }
+        if (n >= 0) {
+            int j = 0;
+            SecurityCollection results[] =
+                new SecurityCollection[collections.length - 1];
+            for (int i = 0; i < collections.length; i++) {
+                if (i != n)
+                    results[j++] = collections[i];
+            }
+            collections = results;
+        }
+
+    }
+
+
+    /**
+     * Return a String representation of this security constraint.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("SecurityConstraint[");
+        for (int i = 0; i < collections.length; i++) {
+            if (i > 0)
+                sb.append(", ");
+            sb.append(collections[i].getName());
+        }
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Does the specified request path match the specified URL pattern?
+     * This method follows the same rules (in the same order) as those used
+     * for mapping requests to servlets.
+     *
+     * @param path Context-relative request path to be checked
+     *  (must start with '/')
+     * @param pattern URL pattern to be compared against
+     */
+    private boolean matchPattern(String path, String pattern) {
+
+        // Normalize the argument strings
+        if ((path == null) || (path.length() == 0))
+            path = "/";
+        if ((pattern == null) || (pattern.length() == 0))
+            pattern = "/";
+
+        // Check for exact match
+        if (path.equals(pattern))
+            return (true);
+
+        // Check for path prefix matching
+        if (pattern.startsWith("/") && pattern.endsWith("/*")) {
+            pattern = pattern.substring(0, pattern.length() - 2);
+            if (pattern.length() == 0)
+                return (true);  // "/*" is the same as "/"
+            if (path.endsWith("/"))
+                path = path.substring(0, path.length() - 1);
+            while (true) {
+                if (pattern.equals(path))
+                    return (true);
+                int slash = path.lastIndexOf('/');
+                if (slash <= 0)
+                    break;
+                path = path.substring(0, slash);
+            }
+            return (false);
+        }
+
+        // Check for suffix matching
+        if (pattern.startsWith("*.")) {
+            int slash = path.lastIndexOf('/');
+            int period = path.lastIndexOf('.');
+            if ((slash >= 0) && (period > slash) &&
+                path.endsWith(pattern.substring(1))) {
+                return (true);
+            }
+            return (false);
+        }
+
+        // Check for universal mapping
+        if (pattern.equals("/"))
+            return (true);
+
+        return (false);
+
+    }
+
+
+    /**
+     * Convert a {@link ServletSecurityElement} to an array of
+     * {@link SecurityConstraint}(s).
+     * 
+     * @param element       The element to be converted
+     * @param urlPattern    The url pattern that the element should be applied
+     *                      to
+     * @return              The (possibly zero length) array of constraints that
+     *                      are the equivalent to the input
+     */
+    public static SecurityConstraint[] createConstraints(
+            ServletSecurityElement element, String urlPattern) {
+        Set<SecurityConstraint> result = new HashSet<SecurityConstraint>();
+        
+        // Add the per method constraints
+        Collection<HttpMethodConstraintElement> methods =
+            element.getHttpMethodConstraints();
+        Iterator<HttpMethodConstraintElement> methodIter = methods.iterator();
+        while (methodIter.hasNext()) {
+            HttpMethodConstraintElement methodElement = methodIter.next();
+            SecurityConstraint constraint =
+                createConstraint(methodElement, urlPattern, true);
+            // There will always be a single collection
+            SecurityCollection collection = constraint.findCollections()[0];
+            collection.addMethod(methodElement.getMethodName());
+            result.add(constraint);
+        }
+        
+        // Add the constraint for all the other methods
+        SecurityConstraint constraint = createConstraint(element, urlPattern, false);
+        if (constraint != null) {
+            // There will always be a single collection
+            SecurityCollection collection = constraint.findCollections()[0];
+            Iterator<String> ommittedMethod = element.getMethodNames().iterator();
+            while (ommittedMethod.hasNext()) {
+                collection.addOmittedMethod(ommittedMethod.next());
+            }
+            
+            result.add(constraint);
+            
+        }
+        
+        return result.toArray(new SecurityConstraint[result.size()]);
+    }
+    
+    private static SecurityConstraint createConstraint(
+            HttpConstraintElement element, String urlPattern, boolean alwaysCreate) {
+
+        SecurityConstraint constraint = new SecurityConstraint();
+        SecurityCollection collection = new SecurityCollection();
+        boolean create = alwaysCreate;
+        
+        if (element.getTransportGuarantee() !=
+                ServletSecurity.TransportGuarantee.NONE) {
+            constraint.setUserConstraint(element.getTransportGuarantee().name());
+            create = true;
+        }
+        if (element.getRolesAllowed().length > 0) {
+            String[] roles = element.getRolesAllowed();
+            for (String role : roles) {
+                constraint.addAuthRole(role);
+            }
+            create = true;
+        }
+        if (element.getEmptyRoleSemantic() != EmptyRoleSemantic.PERMIT) {
+            constraint.setAuthConstraint(true);
+            create = true;
+        }
+        
+        if (create) {
+            collection.addPattern(urlPattern);
+            constraint.addCollection(collection);
+            return constraint;
+        }
+        
+        return null;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/SecurityRoleRef.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/SecurityRoleRef.java
new file mode 100644
index 0000000..26081d7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/SecurityRoleRef.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.catalina.deploy;
+
+
+/**
+ * <p>Representation of a security role reference for a web application, as
+ * represented in a <code>&lt;security-role-ref&gt;</code> element
+ * in the deployment descriptor.</p>
+ *
+ * @author Mark Thomas
+ * @version $Id: SecurityRoleRef.java,v 1.1 2011/06/28 21:08:22 rherrmann Exp $
+ * @since Tomcat 5.5
+ */
+
+public class SecurityRoleRef {
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * The (required) role name.
+     */
+    private String name = null;
+
+    public String getName() {
+        return (this.name);
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+
+    /**
+     * The optional role link.
+     */
+    private String link = null;
+
+    public String getLink() {
+        return (this.link);
+    }
+
+    public void setLink(String link) {
+        this.link = link;
+    }
+
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return a String representation of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("SecurityRoleRef[");
+        sb.append("name=");
+        sb.append(name);
+        if (link != null) {
+            sb.append(", link=");
+            sb.append(link);
+        }
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ServletDef.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ServletDef.java
new file mode 100644
index 0000000..7b410b7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/ServletDef.java
@@ -0,0 +1,267 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.deploy;
+
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * Representation of a servlet definition for a web application, as represented
+ * in a <code>&lt;servlet&gt;</code> element in the deployment descriptor.
+ */
+
+public class ServletDef implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * The description of this servlet.
+     */
+    private String description = null;
+
+    public String getDescription() {
+        return (this.description);
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+
+    /**
+     * The display name of this servlet.
+     */
+    private String displayName = null;
+
+    public String getDisplayName() {
+        return (this.displayName);
+    }
+
+    public void setDisplayName(String displayName) {
+        this.displayName = displayName;
+    }
+
+
+    /**
+     * The small icon associated with this servlet.
+     */
+    private String smallIcon = null;
+
+    public String getSmallIcon() {
+        return (this.smallIcon);
+    }
+
+    public void setSmallIcon(String smallIcon) {
+        this.smallIcon = smallIcon;
+    }
+    
+    /**
+     * The large icon associated with this servlet.
+     */
+    private String largeIcon = null;
+
+    public String getLargeIcon() {
+        return (this.largeIcon);
+    }
+
+    public void setLargeIcon(String largeIcon) {
+        this.largeIcon = largeIcon;
+    }
+
+
+    /**
+     * The name of this servlet, which must be unique among the servlets
+     * defined for a particular web application.
+     */
+    private String servletName = null;
+
+    public String getServletName() {
+        return (this.servletName);
+    }
+
+    public void setServletName(String servletName) {
+        this.servletName = servletName;
+    }
+
+
+    /**
+     * The fully qualified name of the Java class that implements this servlet.
+     */
+    private String servletClass = null;
+
+    public String getServletClass() {
+        return (this.servletClass);
+    }
+
+    public void setServletClass(String servletClass) {
+        this.servletClass = servletClass;
+    }
+
+
+    /**
+     * The name of the JSP file to which this servlet definition applies
+     */
+    private String jspFile = null;
+
+    public String getJspFile() {
+        return (this.jspFile);
+    }
+
+    public void setJspFile(String jspFile) {
+        this.jspFile = jspFile;
+    }
+
+
+    /**
+     * The set of initialization parameters for this servlet, keyed by
+     * parameter name.
+     */
+    private Map<String, String> parameters = new HashMap<String, String>();
+
+    public Map<String, String> getParameterMap() {
+
+        return (this.parameters);
+
+    }
+
+    /**
+     * Add an initialization parameter to the set of parameters associated
+     * with this servlet.
+     *
+     * @param name The initialisation parameter name
+     * @param value The initialisation parameter value
+     */
+    public void addInitParameter(String name, String value) {
+
+        if (parameters.containsKey(name)) {
+            // The spec does not define this but the TCK expects the first
+            // definition to take precedence
+            return;
+        }
+        parameters.put(name, value);
+
+    }
+
+    /**
+     * The load-on-startup order for this servlet
+     */
+    private Integer loadOnStartup = null;
+
+    public Integer getLoadOnStartup() {
+        return (this.loadOnStartup);
+    }
+
+    public void setLoadOnStartup(String loadOnStartup) {
+        this.loadOnStartup = Integer.valueOf(loadOnStartup);
+    }
+
+
+    /**
+     * The run-as configuration for this servlet
+     */
+    private String runAs = null;
+
+    public String getRunAs() {
+        return (this.runAs);
+    }
+
+    public void setRunAs(String runAs) {
+        this.runAs = runAs;
+    }
+
+
+    /**
+     * The set of security role references for this servlet
+     */
+    private Set<SecurityRoleRef> securityRoleRefs =
+        new HashSet<SecurityRoleRef>();
+
+    public Set<SecurityRoleRef> getSecurityRoleRefs() {
+        return (this.securityRoleRefs);
+    }
+
+    /**
+     * Add a security-role-ref to the set of security-role-refs associated
+     * with this servlet.
+     */
+    public void addSecurityRoleRef(SecurityRoleRef securityRoleRef) {
+        securityRoleRefs.add(securityRoleRef);
+    }
+
+    /**
+     * Add a security-role-ref to the set of security-role-refs associated
+     * with this servlet.
+     */
+    public void addSecurityRoleRef(String roleName, String roleLink) {
+        SecurityRoleRef srr = new SecurityRoleRef();
+        srr.setName(roleName);
+        srr.setLink(roleLink);
+        securityRoleRefs.add(srr);
+    }
+
+    
+    /**
+     * The multipart configuration, if any, for this servlet
+     */
+    private MultipartDef multipartDef = null;
+    
+    public MultipartDef getMultipartDef() {
+        return this.multipartDef;
+    }
+    
+    public void setMultipartDef(MultipartDef multipartDef) {
+        this.multipartDef = multipartDef;
+    }
+    
+    
+    /**
+     * Does this servlet support async.
+     */
+    private Boolean asyncSupported = null;
+    
+    public Boolean getAsyncSupported() {
+        return this.asyncSupported;
+    }
+    
+    public void setAsyncSupported(String asyncSupported) {
+        this.asyncSupported = Boolean.valueOf(asyncSupported);
+    }
+
+    
+    /**
+     * Is this servlet enabled.
+     */
+    private Boolean enabled = null;
+    
+    public Boolean getEnabled() {
+        return this.enabled;
+    }
+    
+    public void setEnabled(String enabled) {
+        this.enabled = Boolean.valueOf(enabled);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/SessionConfig.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/SessionConfig.java
new file mode 100644
index 0000000..a227119
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/SessionConfig.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.catalina.deploy;
+
+import java.util.EnumSet;
+
+import javax.servlet.SessionTrackingMode;
+
+/**
+ * Representation of a session configuration element for a web application,
+ * as represented in a <code>&lt;session-config&gt;</code> element in the
+ * deployment descriptor.
+ */
+public class SessionConfig {
+    private Integer sessionTimeout;
+    private String cookieName;
+    private String cookieDomain;
+    private String cookiePath;
+    private String cookieComment;
+    private Boolean cookieHttpOnly;
+    private Boolean cookieSecure;
+    private Integer cookieMaxAge;
+    private EnumSet<SessionTrackingMode> sessionTrackingModes =
+        EnumSet.noneOf(SessionTrackingMode.class);
+    
+    public Integer getSessionTimeout() {
+        return sessionTimeout;
+    }
+    public void setSessionTimeout(String sessionTimeout) {
+        this.sessionTimeout = Integer.valueOf(sessionTimeout);
+    }
+    
+    public String getCookieName() {
+        return cookieName;
+    }
+    public void setCookieName(String cookieName) {
+        this.cookieName = cookieName;
+    }
+    
+    public String getCookieDomain() {
+        return cookieDomain;
+    }
+    public void setCookieDomain(String cookieDomain) {
+        this.cookieDomain = cookieDomain;
+    }
+    
+    public String getCookiePath() {
+        return cookiePath;
+    }
+    public void setCookiePath(String cookiePath) {
+        this.cookiePath = cookiePath;
+    }
+    
+    public String getCookieComment() {
+        return cookieComment;
+    }
+    public void setCookieComment(String cookieComment) {
+        this.cookieComment = cookieComment;
+    }
+    
+    public Boolean getCookieHttpOnly() {
+        return cookieHttpOnly;
+    }
+    public void setCookieHttpOnly(String cookieHttpOnly) {
+        this.cookieHttpOnly = Boolean.valueOf(cookieHttpOnly);
+    }
+    
+    public Boolean getCookieSecure() {
+        return cookieSecure;
+    }
+    public void setCookieSecure(String cookieSecure) {
+        this.cookieSecure = Boolean.valueOf(cookieSecure);
+    }
+    
+    public Integer getCookieMaxAge() {
+        return cookieMaxAge;
+    }
+    public void setCookieMaxAge(String cookieMaxAge) {
+        this.cookieMaxAge = Integer.valueOf(cookieMaxAge);
+    }
+    
+    public EnumSet<SessionTrackingMode> getSessionTrackingModes() {
+        return sessionTrackingModes;
+    }
+    public void addSessionTrackingMode(String sessionTrackingMode) {
+        sessionTrackingModes.add(
+                SessionTrackingMode.valueOf(sessionTrackingMode));
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/WebXml.java b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/WebXml.java
new file mode 100644
index 0000000..230e9fc
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/WebXml.java
@@ -0,0 +1,2182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.deploy;
+
+import java.net.URL;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.descriptor.JspPropertyGroupDescriptor;
+import javax.servlet.descriptor.TaglibDescriptor;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.core.ApplicationJspPropertyGroupDescriptor;
+import org.apache.catalina.core.ApplicationTaglibDescriptor;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Representation of common elements of web.xml and web-fragment.xml. Provides
+ * a repository for parsed data before the elements are merged.
+ * Validation is spread between multiple classes:
+ * The digester checks for structural correctness (eg single login-config)
+ * This class checks for invalid duplicates (eg filter/servlet names)
+ * StandardContext will check validity of values (eg URL formats etc)
+ */
+public class WebXml {
+    
+    protected static final String ORDER_OTHERS =
+        "org.apache.catalina.order.others";
+    
+    private static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog(WebXml.class);
+    
+    // web.xml only elements
+    // Absolute Ordering
+    private Set<String> absoluteOrdering = null;
+    public void addAbsoluteOrdering(String fragmentName) {
+        if (absoluteOrdering == null) {
+            absoluteOrdering = new LinkedHashSet<String>();
+        }
+        absoluteOrdering.add(fragmentName);
+    }
+    public void addAbsoluteOrderingOthers() {
+        if (absoluteOrdering == null) {
+            absoluteOrdering = new LinkedHashSet<String>();
+        }
+        absoluteOrdering.add(ORDER_OTHERS);
+    }
+    public Set<String> getAbsoluteOrdering() {
+        return absoluteOrdering;
+    }
+
+    // web-fragment.xml only elements
+    // Relative ordering
+    private Set<String> after = new LinkedHashSet<String>();
+    public void addAfterOrdering(String fragmentName) {
+        after.add(fragmentName);
+    }
+    public void addAfterOrderingOthers() {
+        if (before.contains(ORDER_OTHERS)) {
+            throw new IllegalArgumentException(sm.getString(
+                    "webXml.multipleOther"));
+        }
+        after.add(ORDER_OTHERS);
+    }
+    public Set<String> getAfterOrdering() { return after; }
+    
+    private Set<String> before = new LinkedHashSet<String>();
+    public void addBeforeOrdering(String fragmentName) {
+        before.add(fragmentName);
+    }
+    public void addBeforeOrderingOthers() {
+        if (after.contains(ORDER_OTHERS)) {
+            throw new IllegalArgumentException(sm.getString(
+                    "webXml.multipleOther"));
+        }
+        before.add(ORDER_OTHERS);
+    }
+    public Set<String> getBeforeOrdering() { return before; }
+
+    // Common elements and attributes
+    
+    // Required attribute of web-app element
+    public String getVersion() {
+        StringBuilder sb = new StringBuilder(3);
+        sb.append(majorVersion);
+        sb.append('.');
+        sb.append(minorVersion);
+        return sb.toString();
+    }
+    /**
+     * Set the version for this web.xml file
+     * @param version   Values of <code>null</code> will be ignored
+     */
+    public void setVersion(String version) {
+        if (version == null) return;
+        
+        // Update major and minor version
+        // Expected format is n.n - allow for any number of digits just in case
+        String major = null;
+        String minor = null;
+        int split = version.indexOf('.');
+        if (split < 0) {
+            // Major only
+            major = version;
+        } else {
+            major = version.substring(0, split);
+            minor = version.substring(split + 1);
+        }
+        if (major == null || major.length() == 0) {
+            majorVersion = 0;
+        } else {
+            try {
+                majorVersion = Integer.parseInt(major);
+            } catch (NumberFormatException nfe) {
+                log.warn(sm.getString("webXml.version.nfe", major, version),
+                        nfe);
+                majorVersion = 0;
+            }
+        }
+        
+        if (minor == null || minor.length() == 0) {
+            minorVersion = 0;
+        } else {
+            try {
+                minorVersion = Integer.parseInt(minor);
+            } catch (NumberFormatException nfe) {
+                log.warn(sm.getString("webXml.version.nfe", minor, version),
+                        nfe);
+                minorVersion = 0;
+            }
+        }
+    }
+
+
+    // Optional publicId attribute
+    private String publicId = null;
+    public String getPublicId() { return publicId; }
+    public void setPublicId(String publicId) {
+        // Update major and minor version
+        if (publicId == null) {
+            // skip
+        } else if (org.apache.catalina.startup.Constants.WebSchemaPublicId_30.
+                equalsIgnoreCase(publicId) ||
+                org.apache.catalina.startup.Constants.WebFragmentSchemaPublicId_30.
+                equalsIgnoreCase(publicId)) {
+            majorVersion = 3;
+            minorVersion = 0;
+            this.publicId = publicId;
+        } else if (org.apache.catalina.startup.Constants.WebSchemaPublicId_25.
+                equalsIgnoreCase(publicId)) {
+            majorVersion = 2;
+            minorVersion = 5;
+            this.publicId = publicId;
+        } else if (org.apache.catalina.startup.Constants.WebSchemaPublicId_24.
+                equalsIgnoreCase(publicId)) {
+            majorVersion = 2;
+            minorVersion = 4;
+            this.publicId = publicId;
+        } else if (org.apache.catalina.startup.Constants.WebDtdPublicId_23.
+                equalsIgnoreCase(publicId)) {
+            majorVersion = 2;
+            minorVersion = 3;
+            this.publicId = publicId;
+        } else if (org.apache.catalina.startup.Constants.WebDtdPublicId_22.
+                equalsIgnoreCase(publicId)) {
+            majorVersion = 2;
+            minorVersion = 2;
+            this.publicId = publicId;
+        } else if ("datatypes".equals(publicId)) {
+            // Will occur when validation is enabled and dependencies are
+            // traced back. Ignore it.
+        } else {
+            // Unrecognised publicId
+            log.warn(sm.getString("webxml.unrecognisedPublicId", publicId));
+        }
+    }
+    
+    // Optional metadata-complete attribute
+    private boolean metadataComplete = false;
+    public boolean isMetadataComplete() { return metadataComplete; }
+    public void setMetadataComplete(boolean metadataComplete) {
+        this.metadataComplete = metadataComplete; }
+    
+    // Optional name element
+    private String name = null;
+    public String getName() { return name; }
+    public void setName(String name) {
+        if (ORDER_OTHERS.equalsIgnoreCase(name)) {
+            // This is unusual. This name will be ignored. Log the fact.
+            log.warn(sm.getString("webXml.reservedName", name));
+        } else {
+            this.name = name;
+        }
+    }
+
+    // Derived major and minor version attributes
+    // Default to 3.0 until we know otherwise
+    private int majorVersion = 3;
+    private int minorVersion = 0;
+    public int getMajorVersion() { return majorVersion; }
+    public int getMinorVersion() { return minorVersion; }
+    
+    // web-app elements
+    // TODO: Ignored elements:
+    // - description
+    // - icon
+
+    // display-name - TODO should support multiple with language
+    private String displayName = null;
+    public String getDisplayName() { return displayName; }
+    public void setDisplayName(String displayName) {
+        this.displayName = displayName;
+    }
+    
+    // distributable
+    private boolean distributable = false;
+    public boolean isDistributable() { return distributable; }
+    public void setDistributable(boolean distributable) {
+        this.distributable = distributable;
+    }
+    
+    // context-param
+    // TODO: description (multiple with language) is ignored
+    private Map<String,String> contextParams = new HashMap<String,String>();
+    public void addContextParam(String param, String value) {
+        contextParams.put(param, value);
+    }
+    public Map<String,String> getContextParams() { return contextParams; }
+    
+    // filter
+    // TODO: Should support multiple description elements with language
+    // TODO: Should support multiple display-name elements with language
+    // TODO: Should support multiple icon elements
+    // TODO: Description for init-param is ignored
+    private Map<String,FilterDef> filters =
+        new LinkedHashMap<String,FilterDef>();
+    public void addFilter(FilterDef filter) {
+        if (filters.containsKey(filter.getFilterName())) {
+            // Filter names must be unique within a web(-fragment).xml
+            throw new IllegalArgumentException(
+                    sm.getString("webXml.duplicateFilter",
+                            filter.getFilterName()));
+        }
+        filters.put(filter.getFilterName(), filter);
+    }
+    public Map<String,FilterDef> getFilters() { return filters; }
+    
+    // filter-mapping
+    private Set<FilterMap> filterMaps = new LinkedHashSet<FilterMap>();
+    private Set<String> filterMappingNames = new HashSet<String>();
+    public void addFilterMapping(FilterMap filterMap) {
+        filterMaps.add(filterMap);
+        filterMappingNames.add(filterMap.getFilterName());
+    }
+    public Set<FilterMap> getFilterMappings() { return filterMaps; }
+    
+    // listener
+    // TODO: description (multiple with language) is ignored
+    // TODO: display-name (multiple with language) is ignored
+    // TODO: icon (multiple) is ignored
+    private Set<String> listeners = new LinkedHashSet<String>();
+    public void addListener(String className) {
+        listeners.add(className);
+    }
+    public Set<String> getListeners() { return listeners; }
+    
+    // servlet
+    // TODO: description (multiple with language) is ignored
+    // TODO: display-name (multiple with language) is ignored
+    // TODO: icon (multiple) is ignored
+    // TODO: init-param/description (multiple with language) is ignored
+    // TODO: security-role-ref/description (multiple with language) is ignored
+    private Map<String,ServletDef> servlets = new HashMap<String,ServletDef>();
+    public void addServlet(ServletDef servletDef) {
+        servlets.put(servletDef.getServletName(), servletDef);
+    }
+    public Map<String,ServletDef> getServlets() { return servlets; }
+    
+    // servlet-mapping
+    private Map<String,String> servletMappings = new HashMap<String,String>();
+    private Set<String> servletMappingNames = new HashSet<String>();
+    public void addServletMapping(String urlPattern, String servletName) {
+        servletMappings.put(urlPattern, servletName);
+        servletMappingNames.add(servletName);
+    }
+    public Map<String,String> getServletMappings() { return servletMappings; }
+    
+    // session-config
+    // Digester will check there is only one of these
+    private SessionConfig sessionConfig = new SessionConfig();
+    public void setSessionConfig(SessionConfig sessionConfig) {
+        this.sessionConfig = sessionConfig;
+    }
+    public SessionConfig getSessionConfig() { return sessionConfig; }
+    
+    // mime-mapping
+    private Map<String,String> mimeMappings = new HashMap<String,String>();
+    public void addMimeMapping(String extension, String mimeType) {
+        mimeMappings.put(extension, mimeType);
+    }
+    public Map<String,String> getMimeMappings() { return mimeMappings; }
+    
+    // welcome-file-list
+    // When merging web.xml files it may be necessary for any new welcome files
+    // to completely replace the current set
+    private boolean replaceWelcomeFiles = false;
+    public void setReplaceWelcomeFiles(boolean replaceWelcomeFiles) {
+        this.replaceWelcomeFiles = replaceWelcomeFiles;
+    }
+    private Set<String> welcomeFiles = new LinkedHashSet<String>();
+    public void addWelcomeFile(String welcomeFile) {
+        if (replaceWelcomeFiles) {
+            welcomeFiles.clear();
+            replaceWelcomeFiles = false;
+        }
+        welcomeFiles.add(welcomeFile);
+    }
+    public Set<String> getWelcomeFiles() { return welcomeFiles; }
+    
+    // error-page
+    private Map<String,ErrorPage> errorPages = new HashMap<String,ErrorPage>();
+    public void addErrorPage(ErrorPage errorPage) {
+        errorPages.put(errorPage.getName(), errorPage);
+    }
+    public Map<String,ErrorPage> getErrorPages() { return errorPages; }
+    
+    // Digester will check there is only one jsp-config
+    // jsp-config/taglib or taglib (2.3 and earlier)
+    private Map<String,String> taglibs = new HashMap<String,String>();
+    public void addTaglib(String uri, String location) {
+        if (taglibs.containsKey(uri)) {
+            // Taglib URIs must be unique within a web(-fragment).xml
+            throw new IllegalArgumentException(
+                    sm.getString("webXml.duplicateTaglibUri", uri));
+        }
+        taglibs.put(uri, location);
+    }
+    public Map<String,String> getTaglibs() { return taglibs; }
+    
+    // jsp-config/jsp-property-group
+    private Set<JspPropertyGroup> jspPropertyGroups =
+        new HashSet<JspPropertyGroup>();
+    public void addJspPropertyGroup(JspPropertyGroup propertyGroup) {
+        jspPropertyGroups.add(propertyGroup);
+    }
+    public Set<JspPropertyGroup> getJspPropertyGroups() {
+        return jspPropertyGroups;
+    }
+
+    // security-constraint
+    // TODO: Should support multiple display-name elements with language
+    // TODO: Should support multiple description elements with language
+    private Set<SecurityConstraint> securityConstraints =
+        new HashSet<SecurityConstraint>();
+    public void addSecurityConstraint(SecurityConstraint securityConstraint) {
+        securityConstraints.add(securityConstraint);
+    }
+    public Set<SecurityConstraint> getSecurityConstraints() {
+        return securityConstraints;
+    }
+    
+    // login-config
+    // Digester will check there is only one of these
+    private LoginConfig loginConfig = null;
+    public void setLoginConfig(LoginConfig loginConfig) {
+        this.loginConfig = loginConfig;
+    }
+    public LoginConfig getLoginConfig() { return loginConfig; }
+    
+    // security-role
+    // TODO: description (multiple with language) is ignored
+    private Set<String> securityRoles = new HashSet<String>();
+    public void addSecurityRole(String securityRole) {
+        securityRoles.add(securityRole);
+    }
+    public Set<String> getSecurityRoles() { return securityRoles; }
+    
+    // env-entry
+    // TODO: Should support multiple description elements with language
+    private Map<String,ContextEnvironment> envEntries =
+        new HashMap<String,ContextEnvironment>();
+    public void addEnvEntry(ContextEnvironment envEntry) {
+        if (envEntries.containsKey(envEntry.getName())) {
+            // env-entry names must be unique within a web(-fragment).xml
+            throw new IllegalArgumentException(
+                    sm.getString("webXml.duplicateEnvEntry",
+                            envEntry.getName()));
+        }
+        envEntries.put(envEntry.getName(),envEntry);
+    }
+    public Map<String,ContextEnvironment> getEnvEntries() { return envEntries; }
+    
+    // ejb-ref
+    // TODO: Should support multiple description elements with language
+    private Map<String,ContextEjb> ejbRefs = new HashMap<String,ContextEjb>();
+    public void addEjbRef(ContextEjb ejbRef) {
+        ejbRefs.put(ejbRef.getName(),ejbRef);
+    }
+    public Map<String,ContextEjb> getEjbRefs() { return ejbRefs; }
+    
+    // ejb-local-ref
+    // TODO: Should support multiple description elements with language
+    private Map<String,ContextLocalEjb> ejbLocalRefs =
+        new HashMap<String,ContextLocalEjb>();
+    public void addEjbLocalRef(ContextLocalEjb ejbLocalRef) {
+        ejbLocalRefs.put(ejbLocalRef.getName(),ejbLocalRef);
+    }
+    public Map<String,ContextLocalEjb> getEjbLocalRefs() {
+        return ejbLocalRefs;
+    }
+    
+    // service-ref
+    // TODO: Should support multiple description elements with language
+    // TODO: Should support multiple display-names elements with language
+    // TODO: Should support multiple icon elements ???
+    private Map<String,ContextService> serviceRefs =
+        new HashMap<String,ContextService>();
+    public void addServiceRef(ContextService serviceRef) {
+        serviceRefs.put(serviceRef.getName(), serviceRef);
+    }
+    public Map<String,ContextService> getServiceRefs() { return serviceRefs; }
+    
+    // resource-ref
+    // TODO: Should support multiple description elements with language
+    private Map<String,ContextResource> resourceRefs =
+        new HashMap<String,ContextResource>();
+    public void addResourceRef(ContextResource resourceRef) {
+        if (resourceRefs.containsKey(resourceRef.getName())) {
+            // resource-ref names must be unique within a web(-fragment).xml
+            throw new IllegalArgumentException(
+                    sm.getString("webXml.duplicateResourceRef",
+                            resourceRef.getName()));
+        }
+        resourceRefs.put(resourceRef.getName(), resourceRef);
+    }
+    public Map<String,ContextResource> getResourceRefs() {
+        return resourceRefs;
+    }
+    
+    // resource-env-ref
+    // TODO: Should support multiple description elements with language
+    private Map<String,ContextResourceEnvRef> resourceEnvRefs =
+        new HashMap<String,ContextResourceEnvRef>();
+    public void addResourceEnvRef(ContextResourceEnvRef resourceEnvRef) {
+        if (resourceEnvRefs.containsKey(resourceEnvRef.getName())) {
+            // resource-env-ref names must be unique within a web(-fragment).xml
+            throw new IllegalArgumentException(
+                    sm.getString("webXml.duplicateResourceEnvRef",
+                            resourceEnvRef.getName()));
+        }
+        resourceEnvRefs.put(resourceEnvRef.getName(), resourceEnvRef);
+    }
+    public Map<String,ContextResourceEnvRef> getResourceEnvRefs() {
+        return resourceEnvRefs;
+    }
+    
+    // message-destination-ref
+    // TODO: Should support multiple description elements with language
+    private Map<String,MessageDestinationRef> messageDestinationRefs =
+        new HashMap<String,MessageDestinationRef>();
+    public void addMessageDestinationRef(
+            MessageDestinationRef messageDestinationRef) {
+        if (messageDestinationRefs.containsKey(
+                messageDestinationRef.getName())) {
+            // message-destination-ref names must be unique within a
+            // web(-fragment).xml
+            throw new IllegalArgumentException(sm.getString(
+                    "webXml.duplicateMessageDestinationRef",
+                    messageDestinationRef.getName()));
+        }
+        messageDestinationRefs.put(messageDestinationRef.getName(),
+                messageDestinationRef);
+    }
+    public Map<String,MessageDestinationRef> getMessageDestinationRefs() {
+        return messageDestinationRefs;
+    }
+    
+    // message-destination
+    // TODO: Should support multiple description elements with language
+    // TODO: Should support multiple display-names elements with language
+    // TODO: Should support multiple icon elements ???
+    private Map<String,MessageDestination> messageDestinations =
+        new HashMap<String,MessageDestination>();
+    public void addMessageDestination(
+            MessageDestination messageDestination) {
+        if (messageDestinations.containsKey(
+                messageDestination.getName())) {
+            // message-destination names must be unique within a
+            // web(-fragment).xml
+            throw new IllegalArgumentException(
+                    sm.getString("webXml.duplicateMessageDestination",
+                            messageDestination.getName()));
+        }
+        messageDestinations.put(messageDestination.getName(),
+                messageDestination);
+    }
+    public Map<String,MessageDestination> getMessageDestinations() {
+        return messageDestinations;
+    }
+    
+    // locale-encoging-mapping-list
+    private Map<String,String> localeEncodingMappings =
+        new HashMap<String,String>();
+    public void addLocaleEncodingMapping(String locale, String encoding) {
+        localeEncodingMappings.put(locale, encoding);
+    }
+    public Map<String,String> getLocalEncodingMappings() {
+        return localeEncodingMappings;
+    }
+    
+
+    // Attributes not defined in web.xml or web-fragment.xml
+    
+    // URL of JAR / exploded JAR for this web-fragment
+    private URL uRL = null;
+    public void setURL(URL url) { this.uRL = url; }
+    public URL getURL() { return uRL; }
+
+
+    @Override
+    public String toString() {
+        StringBuilder buf = new StringBuilder(32);
+        buf.append("Name: ");
+        buf.append(getName());
+        buf.append(", URL: ");
+        buf.append(getURL());
+        return buf.toString();
+    }
+    
+    private static final String INDENT2 = "  ";
+    private static final String INDENT4 = "    ";
+    private static final String INDENT6 = "      ";
+    
+    /**
+     * Generate a web.xml in String form that matches the representation stored
+     * in this object.
+     * 
+     * @return The complete contents of web.xml as a String
+     */
+    public String toXml() {
+        StringBuilder sb = new StringBuilder(2048);
+        
+        // TODO - Various, icon, description etc elements are skipped - mainly
+        //        because they are ignored when web.xml is parsed - see above
+
+        // Declaration
+        sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+        
+        // Root element
+        sb.append("<web-app xmlns=\"http://java.sun.com/xml/ns/javaee\"\n");
+        sb.append("         xmlns:xsi=");
+        sb.append("\"http://www.w3.org/2001/XMLSchema-instance\"\n");
+        sb.append("         xsi:schemaLocation=");
+        sb.append("\"http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd\"\n");
+        sb.append("         version=\"");
+        sb.append(getVersion());
+        sb.append("\"\n");
+        sb.append("         metadata-complete=\"true\">\n\n");
+
+        appendElement(sb, INDENT2, "display-name", displayName);
+        
+        if (isDistributable()) {
+            sb.append("  <distributable/>\n\n");
+        }
+        
+        for (Map.Entry<String, String> entry : contextParams.entrySet()) {
+            sb.append("  <context-param>\n");
+            appendElement(sb, INDENT4, "param-name", entry.getKey());
+            appendElement(sb, INDENT4, "param-valuee", entry.getValue());
+            sb.append("  </context-param>\n");
+        }
+        sb.append('\n');
+        
+        for (Map.Entry<String, FilterDef> entry : filters.entrySet()) {
+            FilterDef filterDef = entry.getValue();
+            sb.append("  <filter>\n");
+            appendElement(sb, INDENT4, "description",
+                    filterDef.getDescription());
+            appendElement(sb, INDENT4, "display-name",
+                    filterDef.getDisplayName());
+            appendElement(sb, INDENT4, "filter-name",
+                    filterDef.getFilterName());
+            appendElement(sb, INDENT4, "filter-class",
+                    filterDef.getFilterClass());
+            appendElement(sb, INDENT4, "async-supported",
+                    filterDef.getAsyncSupported());
+            for (Map.Entry<String, String> param :
+                    filterDef.getParameterMap().entrySet()) {
+                sb.append("    <init-param>\n");
+                appendElement(sb, INDENT6, "param-name", param.getKey());
+                appendElement(sb, INDENT6, "param-value", param.getValue());
+                sb.append("    </init-param>\n");
+            }
+            sb.append("  </filter>\n");
+        }
+        sb.append('\n');
+
+        for (FilterMap filterMap : filterMaps) {
+            sb.append("  <filter-mapping>\n");
+            appendElement(sb, INDENT4, "filter-name",
+                    filterMap.getFilterName());
+            if (filterMap.getMatchAllServletNames()) {
+                sb.append("    <servlet-name>*</servlet-name>\n");
+            } else {
+                for (String servletName : filterMap.getServletNames()) {
+                    appendElement(sb, INDENT4, "servlet-name", servletName);
+                }
+            }
+            if (filterMap.getMatchAllUrlPatterns()) {
+                sb.append("    <url-pattern>*</url-pattern>\n");
+            } else {
+                for (String urlPattern : filterMap.getURLPatterns()) {
+                    appendElement(sb, INDENT4, "url-pattern", urlPattern);
+                }
+            }
+            for (String dispatcher : filterMap.getDispatcherNames()) {
+                appendElement(sb, INDENT4, "dispatcher", dispatcher);
+            }
+            sb.append("  </filter-mapping>\n");
+        }
+        sb.append('\n');
+
+        for (String listener : listeners) {
+            sb.append("  <listener>\n");
+            appendElement(sb, INDENT4, "listener-class", listener);
+            sb.append("  </listener>\n");
+        }
+        sb.append('\n');
+
+        for (Map.Entry<String, ServletDef> entry : servlets.entrySet()) {
+            ServletDef servletDef = entry.getValue();
+            sb.append("  <servlet>\n");
+            appendElement(sb, INDENT4, "description",
+                    servletDef.getDescription());
+            appendElement(sb, INDENT4, "display-name",
+                    servletDef.getDisplayName());
+            appendElement(sb, INDENT4, "servlet-name", entry.getKey());
+            appendElement(sb, INDENT4, "servlet-class",
+                    servletDef.getServletClass());
+            appendElement(sb, INDENT4, "jsp-file", servletDef.getJspFile());
+            for (Map.Entry<String, String> param :
+                    servletDef.getParameterMap().entrySet()) {
+                sb.append("    <init-param>\n");
+                appendElement(sb, INDENT6, "param-name", param.getKey());
+                appendElement(sb, INDENT6, "param-value", param.getValue());
+                sb.append("    </init-param>\n");
+            }
+            appendElement(sb, INDENT4, "load-on-startup",
+                    servletDef.getLoadOnStartup());
+            appendElement(sb, INDENT4, "enabled", servletDef.getEnabled());
+            appendElement(sb, INDENT4, "async-supported",
+                    servletDef.getAsyncSupported());
+            if (servletDef.getRunAs() != null) {
+                sb.append("    <run-as>\n");
+                appendElement(sb, INDENT6, "role-name", servletDef.getRunAs());
+                sb.append("    </run-as>\n");
+            }
+            for (SecurityRoleRef roleRef : servletDef.getSecurityRoleRefs()) {
+                sb.append("    <security-role-ref>\n");
+                appendElement(sb, INDENT6, "role-name", roleRef.getName());
+                appendElement(sb, INDENT6, "role-link", roleRef.getLink());
+                sb.append("    </security-role-ref>\n");
+            }
+            MultipartDef multipartDef = servletDef.getMultipartDef();
+            if (multipartDef != null) {
+                sb.append("    <multipart-config>\n");
+                appendElement(sb, INDENT6, "location",
+                        multipartDef.getLocation());
+                appendElement(sb, INDENT6, "max-file-size",
+                        multipartDef.getMaxFileSize());
+                appendElement(sb, INDENT6, "max-request-size",
+                        multipartDef.getMaxRequestSize());
+                appendElement(sb, INDENT6, "file-size-threshold",
+                        multipartDef.getFileSizeThreshold());
+                sb.append("    </multipart-config>\n");
+            }
+            sb.append("  </servlet>\n");
+        }
+        sb.append('\n');
+
+        for (Map.Entry<String, String> entry : servletMappings.entrySet()) {
+            sb.append("  <servlet-mapping>\n");
+            appendElement(sb, INDENT4, "servlet-name", entry.getValue());
+            appendElement(sb, INDENT4, "url-pattern", entry.getKey());
+            sb.append("  </servlet-mapping>\n");
+        }
+        sb.append('\n');
+        
+        if (sessionConfig != null) {
+            sb.append("  <session-config>\n");
+            appendElement(sb, INDENT4, "session-timeout",
+                    sessionConfig.getSessionTimeout());
+            sb.append("    <cookie-config>\n");
+            appendElement(sb, INDENT6, "name", sessionConfig.getCookieName());
+            appendElement(sb, INDENT6, "domain",
+                    sessionConfig.getCookieDomain());
+            appendElement(sb, INDENT6, "path", sessionConfig.getCookiePath());
+            appendElement(sb, INDENT6, "comment",
+                    sessionConfig.getCookieComment());
+            appendElement(sb, INDENT6, "http-only",
+                    sessionConfig.getCookieHttpOnly());
+            appendElement(sb, INDENT6, "secure",
+                    sessionConfig.getCookieSecure());
+            appendElement(sb, INDENT6, "max-age",
+                    sessionConfig.getCookieMaxAge());
+            sb.append("    </cookie-config>\n");
+            for (SessionTrackingMode stm :
+                    sessionConfig.getSessionTrackingModes()) {
+                appendElement(sb, INDENT4, "tracking-mode", stm.name());
+            }
+            sb.append("  </session-config>\n\n");
+        }
+        
+        for (Map.Entry<String, String> entry : mimeMappings.entrySet()) {
+            sb.append("  <mime-mapping>\n");
+            appendElement(sb, INDENT4, "extension", entry.getKey());
+            appendElement(sb, INDENT4, "mime-type", entry.getValue());
+            sb.append("  </mime-mapping>\n");
+        }
+        sb.append('\n');
+        
+        if (welcomeFiles.size() > 0) {
+            sb.append("  <welcome-file-list>\n");
+            for (String welcomeFile : welcomeFiles) {
+                appendElement(sb, INDENT4, "welcome-file", welcomeFile);
+            }
+            sb.append("  </welcome-file-list>\n\n");
+        }
+        
+        for (ErrorPage errorPage : errorPages.values()) {
+            sb.append("  <error-page>\n");
+            if (errorPage.getExceptionType() == null) {
+                appendElement(sb, INDENT4, "error-code",
+                        Integer.toString(errorPage.getErrorCode()));
+            } else {
+                appendElement(sb, INDENT4, "exception-type",
+                        errorPage.getExceptionType());
+            }
+            appendElement(sb, INDENT4, "location", errorPage.getLocation());
+            sb.append("  </error-page>\n");
+        }
+        sb.append('\n');
+
+        if (taglibs.size() > 0 || jspPropertyGroups.size() > 0) {
+            sb.append("  <jsp-config>\n");
+            for (Map.Entry<String, String> entry : taglibs.entrySet()) {
+                sb.append("    <taglib>\n");
+                appendElement(sb, INDENT6, "taglib-uri", entry.getKey());
+                appendElement(sb, INDENT6, "taglib-location", entry.getValue());
+                sb.append("    </taglib>\n");
+            }
+            for (JspPropertyGroup jpg : jspPropertyGroups) {
+                sb.append("    <jsp-property-group>\n");
+                appendElement(sb, INDENT6, "url-pattern", jpg.getUrlPattern());
+                appendElement(sb, INDENT6, "el-ignored", jpg.getElIgnored());
+                appendElement(sb, INDENT6, "scripting-invalid",
+                        jpg.getScriptingInvalid());
+                appendElement(sb, INDENT6, "page-encoding",
+                        jpg.getPageEncoding());
+                for (String prelude : jpg.getIncludePreludes()) {
+                    appendElement(sb, INDENT6, "include-prelude", prelude);
+                }
+                for (String coda : jpg.getIncludeCodas()) {
+                    appendElement(sb, INDENT6, "include-coda", coda);
+                }
+                appendElement(sb, INDENT6, "is-xml", jpg.getIsXml());
+                appendElement(sb, INDENT6, "deferred-syntax-allowed-as-literal",
+                        jpg.getDeferredSyntax());
+                appendElement(sb, INDENT6, "trim-directive-whitespaces",
+                        jpg.getTrimWhitespace());
+                appendElement(sb, INDENT6, "default-content-type",
+                        jpg.getDefaultContentType());
+                appendElement(sb, INDENT6, "buffer", jpg.getBuffer());
+                appendElement(sb, INDENT6, "error-on-undeclared-namespace",
+                        jpg.getErrorOnUndeclaredNamespace());
+                sb.append("    </jsp-property-group>\n");
+            }
+            sb.append("  </jsp-config>\n\n");
+        }
+        
+        for (SecurityConstraint constraint : securityConstraints) {
+            sb.append("  <security-constraint>\n");
+            appendElement(sb, INDENT4, "display-name",
+                    constraint.getDisplayName());
+            for (SecurityCollection collection : constraint.findCollections()) {
+                sb.append("    <web-resource-collection>\n");
+                appendElement(sb, INDENT6, "web-resource-name",
+                        collection.getName());
+                appendElement(sb, INDENT6, "description",
+                        collection.getDescription());
+                for (String urlPattern : collection.findPatterns()) {
+                    appendElement(sb, INDENT6, "url-pattern", urlPattern);
+                }
+                for (String method : collection.findMethods()) {
+                    appendElement(sb, INDENT6, "http-method", method);
+                }
+                for (String method : collection.findOmittedMethods()) {
+                    appendElement(sb, INDENT6, "http-method-omission", method);
+                }
+                sb.append("    </web-resource-collection>\n");
+            }
+            if (constraint.findAuthRoles().length > 0) {
+                sb.append("    <auth-constraint>\n");
+                for (String role : constraint.findAuthRoles()) {
+                    appendElement(sb, INDENT6, "role-name", role);
+                }
+                sb.append("    </auth-constraint>\n");
+            }
+            if (constraint.getUserConstraint() != null) {
+                sb.append("    <user-data-constraint>\n");
+                appendElement(sb, INDENT6, "transport-guarantee",
+                        constraint.getUserConstraint());
+                sb.append("    </user-data-constraint>\n");
+            }
+            sb.append("  </security-constraint>\n");
+        }
+        sb.append('\n');
+
+        if (loginConfig != null) {
+            sb.append("  <login-config>\n");
+            appendElement(sb, INDENT4, "auth-method",
+                    loginConfig.getAuthMethod());
+            appendElement(sb,INDENT4, "realm-name",
+                    loginConfig.getRealmName());
+            if (loginConfig.getErrorPage() != null ||
+                        loginConfig.getLoginPage() != null) {
+                sb.append("    <form-login-config>\n");
+                appendElement(sb, INDENT6, "form-login-page",
+                        loginConfig.getLoginPage());
+                appendElement(sb, INDENT6, "form-error-page",
+                        loginConfig.getErrorPage());
+                sb.append("    </form-login-config>\n");
+            }
+            sb.append("  </login-config>\n\n");
+        }
+        
+        for (String roleName : securityRoles) {
+            sb.append("  <security-role>\n");
+            appendElement(sb, INDENT4, "role-name", roleName);
+            sb.append("  </security-role>\n");
+        }
+        
+        for (ContextEnvironment envEntry : envEntries.values()) {
+            sb.append("  <env-entry>\n");
+            appendElement(sb, INDENT4, "description",
+                    envEntry.getDescription());
+            appendElement(sb, INDENT4, "env-entry-name", envEntry.getName());
+            appendElement(sb, INDENT4, "env-entry-type", envEntry.getType());
+            appendElement(sb, INDENT4, "env-entry-value", envEntry.getValue());
+            // TODO mapped-name
+            for (InjectionTarget target : envEntry.getInjectionTargets()) {
+                sb.append("    <injection-target>\n");
+                appendElement(sb, INDENT6, "injection-target-class",
+                        target.getTargetClass());
+                appendElement(sb, INDENT6, "injection-target-name",
+                        target.getTargetName());
+                sb.append("    </injection-target>\n");
+            }
+            // TODO lookup-name
+            sb.append("  </env-entry>\n");
+        }
+        sb.append('\n');
+
+        for (ContextEjb ejbRef : ejbRefs.values()) {
+            sb.append("  <ejb-ref>\n");
+            appendElement(sb, INDENT4, "description", ejbRef.getDescription());
+            appendElement(sb, INDENT4, "ejb-ref-name", ejbRef.getName());
+            appendElement(sb, INDENT4, "ejb-ref-type", ejbRef.getType());
+            appendElement(sb, INDENT4, "home", ejbRef.getHome());
+            appendElement(sb, INDENT4, "remote", ejbRef.getRemote());
+            appendElement(sb, INDENT4, "ejb-link", ejbRef.getLink());
+            // TODO mapped-name
+            for (InjectionTarget target : ejbRef.getInjectionTargets()) {
+                sb.append("    <injection-target>\n");
+                appendElement(sb, INDENT6, "injection-target-class",
+                        target.getTargetClass());
+                appendElement(sb, INDENT6, "injection-target-name",
+                        target.getTargetName());
+                sb.append("    </injection-target>\n");
+            }
+            // TODO lookup-name
+            sb.append("  </ejb-ref>\n");
+        }
+        sb.append('\n');
+
+        for (ContextLocalEjb ejbLocalRef : ejbLocalRefs.values()) {
+            sb.append("  <ejb-local-ref>\n");
+            appendElement(sb, INDENT4, "description",
+                    ejbLocalRef.getDescription());
+            appendElement(sb, INDENT4, "ejb-ref-name", ejbLocalRef.getName());
+            appendElement(sb, INDENT4, "ejb-ref-type", ejbLocalRef.getType());
+            appendElement(sb, INDENT4, "local-home", ejbLocalRef.getHome());
+            appendElement(sb, INDENT4, "local", ejbLocalRef.getLocal());
+            appendElement(sb, INDENT4, "ejb-link", ejbLocalRef.getLink());
+            // TODO mapped-name
+            for (InjectionTarget target : ejbLocalRef.getInjectionTargets()) {
+                sb.append("    <injection-target>\n");
+                appendElement(sb, INDENT6, "injection-target-class",
+                        target.getTargetClass());
+                appendElement(sb, INDENT6, "injection-target-name",
+                        target.getTargetName());
+                sb.append("    </injection-target>\n");
+            }
+            // TODO lookup-name
+            sb.append("  </ejb-local-ref>\n");
+        }
+        sb.append('\n');
+        
+        for (ContextService serviceRef : serviceRefs.values()) {
+            sb.append("  <service-ref>\n");
+            appendElement(sb, INDENT4, "description",
+                    serviceRef.getDescription());
+            appendElement(sb, INDENT4, "display-name",
+                    serviceRef.getDisplayname());
+            appendElement(sb, INDENT4, "service-ref-name",
+                    serviceRef.getName());
+            appendElement(sb, INDENT4, "service-interface",
+                    serviceRef.getInterface());
+            appendElement(sb, INDENT4, "service-ref-type",
+                    serviceRef.getType());
+            appendElement(sb, INDENT4, "wsdl-file", serviceRef.getWsdlfile());
+            appendElement(sb, INDENT4, "jaxrpc-mapping-file",
+                    serviceRef.getJaxrpcmappingfile());
+            String qname = serviceRef.getServiceqnameNamespaceURI();
+            if (qname != null) {
+                qname = qname + ":";
+            }
+            qname = qname + serviceRef.getServiceqnameLocalpart();
+            appendElement(sb, INDENT4, "service-qname", qname);
+            Iterator<String> endpointIter = serviceRef.getServiceendpoints();
+            while (endpointIter.hasNext()) {
+                String endpoint = endpointIter.next();
+                sb.append("    <port-component-ref>\n");
+                appendElement(sb, INDENT6, "service-endpoint-interface",
+                        endpoint);
+                appendElement(sb, INDENT6, "port-component-link",
+                        serviceRef.getProperty(endpoint));
+                sb.append("    </port-component-ref>\n");
+            }
+            Iterator<String> handlerIter = serviceRef.getHandlers();
+            while (handlerIter.hasNext()) {
+                String handler = handlerIter.next();
+                sb.append("    <handler>\n");
+                ContextHandler ch = serviceRef.getHandler(handler);
+                appendElement(sb, INDENT6, "handler-name", ch.getName());
+                appendElement(sb, INDENT6, "handler-class",
+                        ch.getHandlerclass());
+                sb.append("    </handler>\n");
+            }
+            // TODO handler-chains
+            // TODO mapped-name
+            for (InjectionTarget target : serviceRef.getInjectionTargets()) {
+                sb.append("    <injection-target>\n");
+                appendElement(sb, INDENT6, "injection-target-class",
+                        target.getTargetClass());
+                appendElement(sb, INDENT6, "injection-target-name",
+                        target.getTargetName());
+                sb.append("    </injection-target>\n");
+            }
+            // TODO lookup-name
+            sb.append("  </service-ref>\n");
+        }
+        sb.append('\n');
+        
+        for (ContextResource resourceRef : resourceRefs.values()) {
+            sb.append("  <resource-ref>\n");
+            appendElement(sb, INDENT4, "description",
+                    resourceRef.getDescription());
+            appendElement(sb, INDENT4, "res-ref-name", resourceRef.getName());
+            appendElement(sb, INDENT4, "res-type", resourceRef.getType());
+            appendElement(sb, INDENT4, "res-auth", resourceRef.getAuth());
+            appendElement(sb, INDENT4, "res-sharing-scope",
+                    resourceRef.getScope());
+            // TODO mapped-name
+            for (InjectionTarget target : resourceRef.getInjectionTargets()) {
+                sb.append("    <injection-target>\n");
+                appendElement(sb, INDENT6, "injection-target-class",
+                        target.getTargetClass());
+                appendElement(sb, INDENT6, "injection-target-name",
+                        target.getTargetName());
+                sb.append("    </injection-target>\n");
+            }
+            // TODO lookup-name
+            sb.append("  </resource-ref>\n");
+        }
+        sb.append('\n');
+
+        for (ContextResourceEnvRef resourceEnvRef : resourceEnvRefs.values()) {
+            sb.append("  <resource-env-ref>\n");
+            appendElement(sb, INDENT4, "description",
+                    resourceEnvRef.getDescription());
+            appendElement(sb, INDENT4, "resource-env-ref-name",
+                    resourceEnvRef.getName());
+            appendElement(sb, INDENT4, "resource-env-ref-type",
+                    resourceEnvRef.getType());
+            // TODO mapped-name
+            for (InjectionTarget target :
+                    resourceEnvRef.getInjectionTargets()) {
+                sb.append("    <injection-target>\n");
+                appendElement(sb, INDENT6, "injection-target-class",
+                        target.getTargetClass());
+                appendElement(sb, INDENT6, "injection-target-name",
+                        target.getTargetName());
+                sb.append("    </injection-target>\n");
+            }
+            // TODO lookup-name
+            sb.append("  </resource-env-ref>\n");
+        }
+        sb.append('\n');
+
+        for (MessageDestinationRef mdr : messageDestinationRefs.values()) {
+            sb.append("  <message-destination-ref>\n");
+            appendElement(sb, INDENT4, "description", mdr.getDescription());
+            appendElement(sb, INDENT4, "message-destination-ref-name",
+                    mdr.getName());
+            appendElement(sb, INDENT4, "message-destination-type",
+                    mdr.getType());
+            appendElement(sb, INDENT4, "message-destination-usage",
+                    mdr.getUsage());
+            appendElement(sb, INDENT4, "message-destination-link",
+                    mdr.getLink());
+            // TODO mapped-name
+            for (InjectionTarget target : mdr.getInjectionTargets()) {
+                sb.append("    <injection-target>\n");
+                appendElement(sb, INDENT6, "injection-target-class",
+                        target.getTargetClass());
+                appendElement(sb, INDENT6, "injection-target-name",
+                        target.getTargetName());
+                sb.append("    </injection-target>\n");
+            }
+            // TODO lookup-name
+            sb.append("  </message-destination-ref>\n");
+        }
+        sb.append('\n');
+
+        for (MessageDestination md : messageDestinations.values()) {
+            sb.append("  <message-destination>\n");
+            appendElement(sb, INDENT4, "description", md.getDescription());
+            appendElement(sb, INDENT4, "display-name", md.getDisplayName());
+            appendElement(sb, INDENT4, "message-destination-name",
+                    md.getName());
+            // TODO mapped-name
+            sb.append("  </message-destination>\n");
+        }
+        sb.append('\n');
+
+        if (localeEncodingMappings.size() > 0) {
+            sb.append("  <locale-encoding-mapping-list>\n");
+            for (Map.Entry<String, String> entry :
+                    localeEncodingMappings.entrySet()) {
+                sb.append("    <locale-encoding-mapping>\n");
+                appendElement(sb, INDENT6, "locale", entry.getKey());
+                appendElement(sb, INDENT6, "encoding", entry.getValue());
+                sb.append("    </locale-encoding-mapping>\n");
+            }
+            sb.append("  </locale-encoding-mapping-list>\n");
+        }
+        sb.append("</web-app>");
+        return sb.toString();
+    }
+
+    private static void appendElement(StringBuilder sb, String indent,
+            String elementName, String value) {
+        if (value == null || value.length() == 0) return;
+        sb.append(indent);
+        sb.append('<');
+        sb.append(elementName);
+        sb.append('>');
+        sb.append(escapeXml(value));
+        sb.append("</");
+        sb.append(elementName);
+        sb.append(">\n");
+    }
+
+    private static void appendElement(StringBuilder sb, String indent,
+            String elementName, Object value) {
+        if (value == null) return;
+        appendElement(sb, indent, elementName, value.toString());
+    }
+
+
+    /**
+     * Escape the 5 entities defined by XML.
+     */
+    private static String escapeXml(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("&apos;");
+            } else if (c == '&') {
+                sb.append("&amp;");
+            } else if (c == '"') {
+                sb.append("&quot;");
+            } else {
+                sb.append(c);
+            }
+        }
+        return sb.toString();
+    }
+
+
+    /**
+     * Configure a {@link Context} using the stored web.xml representation.
+     *  
+     * @param context   The context to be configured
+     */
+    public void configureContext(Context context) {
+        // As far as possible, process in alphabetical order so it is easy to
+        // check everything is present
+        // Some validation depends on correct public ID
+        context.setPublicId(publicId);
+
+        // Everything else in order
+        context.setEffectiveMajorVersion(getMajorVersion());
+        context.setEffectiveMinorVersion(getMinorVersion());
+        
+        for (Entry<String, String> entry : contextParams.entrySet()) {
+            context.addParameter(entry.getKey(), entry.getValue());
+        }
+        context.setDisplayName(displayName);
+        context.setDistributable(distributable);
+        for (ContextLocalEjb ejbLocalRef : ejbLocalRefs.values()) {
+            context.getNamingResources().addLocalEjb(ejbLocalRef);
+        }
+        for (ContextEjb ejbRef : ejbRefs.values()) {
+            context.getNamingResources().addEjb(ejbRef);
+        }
+        for (ContextEnvironment environment : envEntries.values()) {
+            context.getNamingResources().addEnvironment(environment);
+        }
+        for (ErrorPage errorPage : errorPages.values()) {
+            context.addErrorPage(errorPage);
+        }
+        for (FilterDef filter : filters.values()) {
+            if (filter.getAsyncSupported() == null) {
+                filter.setAsyncSupported("false");
+            }
+            context.addFilterDef(filter);
+        }
+        for (FilterMap filterMap : filterMaps) {
+            context.addFilterMap(filterMap);
+        }
+        for (JspPropertyGroup jspPropertyGroup : jspPropertyGroups) {
+            JspPropertyGroupDescriptor descriptor =
+                new ApplicationJspPropertyGroupDescriptor(jspPropertyGroup);
+            context.getJspConfigDescriptor().getJspPropertyGroups().add(
+                    descriptor);
+        }
+        for (String listener : listeners) {
+            context.addApplicationListener(listener);
+        }
+        for (Entry<String, String> entry : localeEncodingMappings.entrySet()) {
+            context.addLocaleEncodingMappingParameter(entry.getKey(),
+                    entry.getValue());
+        }
+        // Prevents IAE
+        if (loginConfig != null) {
+            context.setLoginConfig(loginConfig);
+        }
+        for (MessageDestinationRef mdr : messageDestinationRefs.values()) {
+            context.getNamingResources().addMessageDestinationRef(mdr);
+        }
+
+        // messageDestinations were ignored in Tomcat 6, so ignore here
+        
+        context.setIgnoreAnnotations(metadataComplete);
+        for (Entry<String, String> entry : mimeMappings.entrySet()) {
+            context.addMimeMapping(entry.getKey(), entry.getValue());
+        }
+        // Name is just used for ordering
+        for (ContextResourceEnvRef resource : resourceEnvRefs.values()) {
+            context.getNamingResources().addResourceEnvRef(resource);
+        }
+        for (ContextResource resource : resourceRefs.values()) {
+            context.getNamingResources().addResource(resource);
+        }
+        for (SecurityConstraint constraint : securityConstraints) {
+            context.addConstraint(constraint);
+        }
+        for (String role : securityRoles) {
+            context.addSecurityRole(role);
+        }
+        for (ContextService service : serviceRefs.values()) {
+            context.getNamingResources().addService(service);
+        }
+        for (ServletDef servlet : servlets.values()) {
+            Wrapper wrapper = context.createWrapper();
+            // Description is ignored
+            // Display name is ignored
+            // Icons are ignored
+            
+            // jsp-file gets passed to the JSP Servlet as an init-param
+
+            if (servlet.getLoadOnStartup() != null) {
+                wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
+            }
+            if (servlet.getEnabled() != null) {
+                wrapper.setEnabled(servlet.getEnabled().booleanValue());
+            }
+            wrapper.setName(servlet.getServletName());
+            Map<String,String> params = servlet.getParameterMap(); 
+            for (Entry<String, String> entry : params.entrySet()) {
+                wrapper.addInitParameter(entry.getKey(), entry.getValue());
+            }
+            wrapper.setRunAs(servlet.getRunAs());
+            Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();
+            for (SecurityRoleRef roleRef : roleRefs) {
+                wrapper.addSecurityReference(
+                        roleRef.getName(), roleRef.getLink());
+            }
+            wrapper.setServletClass(servlet.getServletClass());
+            MultipartDef multipartdef = servlet.getMultipartDef();
+            if (multipartdef != null) {
+                if (multipartdef.getMaxFileSize() != null &&
+                        multipartdef.getMaxRequestSize()!= null &&
+                        multipartdef.getFileSizeThreshold() != null) {
+                    wrapper.setMultipartConfigElement(new MultipartConfigElement(
+                            multipartdef.getLocation(),
+                            Long.parseLong(multipartdef.getMaxFileSize()),
+                            Long.parseLong(multipartdef.getMaxRequestSize()),
+                            Integer.parseInt(
+                                    multipartdef.getFileSizeThreshold())));
+                } else {
+                    wrapper.setMultipartConfigElement(new MultipartConfigElement(
+                            multipartdef.getLocation()));
+                }
+            }
+            if (servlet.getAsyncSupported() != null) {
+                wrapper.setAsyncSupported(
+                        servlet.getAsyncSupported().booleanValue());
+            }
+            context.addChild(wrapper);
+        }
+        for (Entry<String, String> entry : servletMappings.entrySet()) {
+            context.addServletMapping(entry.getKey(), entry.getValue());
+        }
+        if (sessionConfig != null) {
+            if (sessionConfig.getSessionTimeout() != null) {
+                context.setSessionTimeout(
+                        sessionConfig.getSessionTimeout().intValue());
+            }
+            SessionCookieConfig scc =
+                context.getServletContext().getSessionCookieConfig();
+            scc.setName(sessionConfig.getCookieName());
+            scc.setDomain(sessionConfig.getCookieDomain());
+            scc.setPath(sessionConfig.getCookiePath());
+            scc.setComment(sessionConfig.getCookieComment());
+            if (sessionConfig.getCookieHttpOnly() != null) {
+                scc.setHttpOnly(sessionConfig.getCookieHttpOnly().booleanValue());
+            }
+            if (sessionConfig.getCookieSecure() != null) {
+                scc.setSecure(sessionConfig.getCookieSecure().booleanValue());
+            }
+            if (sessionConfig.getCookieMaxAge() != null) {
+                scc.setMaxAge(sessionConfig.getCookieMaxAge().intValue());
+            }
+            if (sessionConfig.getSessionTrackingModes().size() > 0) {
+                context.getServletContext().setSessionTrackingModes(
+                        sessionConfig.getSessionTrackingModes());
+            }
+        }
+        for (Entry<String, String> entry : taglibs.entrySet()) {
+            TaglibDescriptor descriptor = new ApplicationTaglibDescriptor(
+                    entry.getValue(), entry.getKey());
+            context.getJspConfigDescriptor().getTaglibs().add(descriptor);
+        }
+        
+        // Context doesn't use version directly
+        
+        for (String welcomeFile : welcomeFiles) {
+            context.addWelcomeFile(welcomeFile);
+        }
+
+        // Do this last as it depends on servlets
+        for (JspPropertyGroup jspPropertyGroup : jspPropertyGroups) {
+            String jspServletName = context.findServletMapping("*.jsp");
+            if (jspServletName == null) {
+                jspServletName = "jsp";
+            }
+            if (context.findChild(jspServletName) != null) {
+                context.addServletMapping(jspPropertyGroup.getUrlPattern(),
+                        jspServletName, true);
+            } else {
+                if(log.isDebugEnabled())
+                    log.debug("Skiping " + jspPropertyGroup.getUrlPattern() +
+                            " , no servlet " + jspServletName);
+            }
+        }
+    }
+    
+    /**
+     * Merge the supplied web fragments into this main web.xml.
+     * 
+     * @param fragments     The fragments to merge in
+     * @return <code>true</code> if merge is successful, else
+     *         <code>false</code>
+     */
+    public boolean merge(Set<WebXml> fragments) {
+        // As far as possible, process in alphabetical order so it is easy to
+        // check everything is present
+        
+        // Merge rules vary from element to element. See SRV.8.2.3
+
+        WebXml temp = new WebXml();
+        Map<String,Boolean> mergeInjectionFlags =
+            new HashMap<String, Boolean>();
+
+        for (WebXml fragment : fragments) {
+            if (!mergeMap(fragment.getContextParams(), contextParams,
+                    temp.getContextParams(), fragment, "Context Parameter")) {
+                return false;
+            }
+        }
+        contextParams.putAll(temp.getContextParams());
+
+        if (displayName == null) {
+            for (WebXml fragment : fragments) {
+                String value = fragment.getDisplayName(); 
+                if (value != null) {
+                    if (temp.getDisplayName() == null) {
+                        temp.setDisplayName(value);
+                    } else {
+                        log.error(sm.getString(
+                                "webXml.mergeConflictDisplayName",
+                                fragment.getName(),
+                                fragment.getURL()));
+                        return false;
+                    }
+                }
+            }
+            displayName = temp.getDisplayName();
+        }
+
+        if (distributable) {
+            for (WebXml fragment : fragments) {
+                if (!fragment.isDistributable()) {
+                    distributable = false;
+                    break;
+                }
+            }
+        }
+
+        for (WebXml fragment : fragments) {
+            if (!mergeResourceMap(fragment.getEjbLocalRefs(), ejbLocalRefs,
+                    temp.getEjbLocalRefs(), mergeInjectionFlags, fragment)) {
+                return false;
+            }
+        }
+        ejbLocalRefs.putAll(temp.getEjbLocalRefs());
+        mergeInjectionFlags.clear();
+
+        for (WebXml fragment : fragments) {
+            if (!mergeResourceMap(fragment.getEjbRefs(), ejbRefs,
+                    temp.getEjbRefs(), mergeInjectionFlags, fragment)) {
+                return false;
+            }
+        }
+        ejbRefs.putAll(temp.getEjbRefs());
+        mergeInjectionFlags.clear();
+
+        for (WebXml fragment : fragments) {
+            if (!mergeResourceMap(fragment.getEnvEntries(), envEntries,
+                    temp.getEnvEntries(), mergeInjectionFlags, fragment)) {
+                return false;
+            }
+        }
+        envEntries.putAll(temp.getEnvEntries());
+        mergeInjectionFlags.clear();
+
+        for (WebXml fragment : fragments) {
+            if (!mergeMap(fragment.getErrorPages(), errorPages,
+                    temp.getErrorPages(), fragment, "Error Page")) {
+                return false;
+            }
+        }
+        errorPages.putAll(temp.getErrorPages());
+
+        // As per 'clarification' from the Servlet EG, filter mappings in the
+        // main web.xml override those in fragments and those in fragments
+        // override mappings in annotations
+        for (WebXml fragment : fragments) {
+            Iterator<FilterMap> iterFilterMaps =
+                fragment.getFilterMappings().iterator();
+            while (iterFilterMaps.hasNext()) {
+                FilterMap filterMap = iterFilterMaps.next();
+                if (filterMappingNames.contains(filterMap.getFilterName())) {
+                    iterFilterMaps.remove();
+                }
+            }
+        }
+        for (WebXml fragment : fragments) {
+            for (FilterMap filterMap : fragment.getFilterMappings()) {
+                // Additive
+                addFilterMapping(filterMap);
+            }
+        }
+
+        for (WebXml fragment : fragments) {
+            for (Map.Entry<String,FilterDef> entry :
+                    fragment.getFilters().entrySet()) {
+                if (filters.containsKey(entry.getKey())) {
+                    mergeFilter(entry.getValue(),
+                            filters.get(entry.getKey()), false);
+                } else {
+                    if (temp.getFilters().containsKey(entry.getKey())) {
+                        if (!(mergeFilter(entry.getValue(),
+                                temp.getFilters().get(entry.getKey()), true))) {
+                            log.error(sm.getString(
+                                    "webXml.mergeConflictFilter",
+                                    entry.getKey(),
+                                    fragment.getName(),
+                                    fragment.getURL()));
+    
+                            return false;
+                        }
+                    } else {
+                        temp.getFilters().put(entry.getKey(), entry.getValue());
+                    }
+                }
+            }
+        }
+        filters.putAll(temp.getFilters());
+
+        for (WebXml fragment : fragments) {
+            for (JspPropertyGroup jspPropertyGroup :
+                    fragment.getJspPropertyGroups()) {
+                // Always additive
+                addJspPropertyGroup(jspPropertyGroup);
+            }
+        }
+
+        for (WebXml fragment : fragments) {
+            for (String listener : fragment.getListeners()) {
+                // Always additive
+                addListener(listener);
+            }
+        }
+
+        for (WebXml fragment : fragments) {
+            if (!mergeMap(fragment.getLocalEncodingMappings(),
+                    localeEncodingMappings, temp.getLocalEncodingMappings(),
+                    fragment, "Locale Encoding Mapping")) {
+                return false;
+            }
+        }
+        localeEncodingMappings.putAll(temp.getLocalEncodingMappings());
+
+        if (getLoginConfig() == null) {
+            LoginConfig tempLoginConfig = null;
+            for (WebXml fragment : fragments) {
+                LoginConfig fragmentLoginConfig = fragment.loginConfig;
+                if (fragmentLoginConfig != null) {
+                    if (tempLoginConfig == null ||
+                            fragmentLoginConfig.equals(tempLoginConfig)) {
+                        tempLoginConfig = fragmentLoginConfig;
+                    } else {
+                        log.error(sm.getString(
+                                "webXml.mergeConflictLoginConfig",
+                                fragment.getName(),
+                                fragment.getURL()));
+                    }
+                }
+            }
+            loginConfig = tempLoginConfig;
+        }
+
+        for (WebXml fragment : fragments) {
+            if (!mergeResourceMap(fragment.getMessageDestinationRefs(), messageDestinationRefs,
+                    temp.getMessageDestinationRefs(), mergeInjectionFlags, fragment)) {
+                return false;
+            }
+        }
+        messageDestinationRefs.putAll(temp.getMessageDestinationRefs());
+        mergeInjectionFlags.clear();
+
+        for (WebXml fragment : fragments) {
+            if (!mergeResourceMap(fragment.getMessageDestinations(), messageDestinations,
+                    temp.getMessageDestinations(), mergeInjectionFlags, fragment)) {
+                return false;
+            }
+        }
+        messageDestinations.putAll(temp.getMessageDestinations());
+        mergeInjectionFlags.clear();
+
+        for (WebXml fragment : fragments) {
+            if (!mergeMap(fragment.getMimeMappings(), mimeMappings,
+                    temp.getMimeMappings(), fragment, "Mime Mapping")) {
+                return false;
+            }
+        }
+        mimeMappings.putAll(temp.getMimeMappings());
+
+        for (WebXml fragment : fragments) {
+            if (!mergeResourceMap(fragment.getResourceEnvRefs(), resourceEnvRefs,
+                    temp.getResourceEnvRefs(), mergeInjectionFlags, fragment)) {
+                return false;
+            }
+        }
+        resourceEnvRefs.putAll(temp.getResourceEnvRefs());
+        mergeInjectionFlags.clear();
+
+        for (WebXml fragment : fragments) {
+            if (!mergeResourceMap(fragment.getResourceRefs(), resourceRefs,
+                    temp.getResourceRefs(), mergeInjectionFlags, fragment)) {
+                return false;
+            }
+        }
+        resourceRefs.putAll(temp.getResourceRefs());
+        mergeInjectionFlags.clear();
+
+        for (WebXml fragment : fragments) {
+            for (SecurityConstraint constraint : fragment.getSecurityConstraints()) {
+                // Always additive
+                addSecurityConstraint(constraint);
+            }
+        }
+
+        for (WebXml fragment : fragments) {
+            for (String role : fragment.getSecurityRoles()) {
+                // Always additive
+                addSecurityRole(role);
+            }
+        }
+
+        for (WebXml fragment : fragments) {
+            if (!mergeResourceMap(fragment.getServiceRefs(), serviceRefs,
+                    temp.getServiceRefs(), mergeInjectionFlags, fragment)) {
+                return false;
+            }
+        }
+        serviceRefs.putAll(temp.getServiceRefs());
+        mergeInjectionFlags.clear();
+
+        // As per 'clarification' from the Servlet EG, servlet mappings in the
+        // main web.xml override those in fragments and those in fragments
+        // override mappings in annotations
+        for (WebXml fragment : fragments) {
+            Iterator<Map.Entry<String,String>> iterServletMaps =
+                fragment.getServletMappings().entrySet().iterator();
+            while (iterServletMaps.hasNext()) {
+                Map.Entry<String,String> servletMap = iterServletMaps.next();
+                if (servletMappingNames.contains(servletMap.getValue())) {
+                    iterServletMaps.remove();
+                }
+            }
+        }
+        for (WebXml fragment : fragments) {
+            for (Map.Entry<String,String> mapping :
+                    fragment.getServletMappings().entrySet()) {
+                // Additive
+                addServletMapping(mapping.getKey(), mapping.getValue());
+            }
+        }
+
+        for (WebXml fragment : fragments) {
+            for (Map.Entry<String,ServletDef> entry :
+                    fragment.getServlets().entrySet()) {
+                if (servlets.containsKey(entry.getKey())) {
+                    mergeServlet(entry.getValue(),
+                            servlets.get(entry.getKey()), false);
+                } else {
+                    if (temp.getServlets().containsKey(entry.getKey())) {
+                        if (!(mergeServlet(entry.getValue(),
+                                temp.getServlets().get(entry.getKey()), true))) {
+                            log.error(sm.getString(
+                                    "webXml.mergeConflictServlet",
+                                    entry.getKey(),
+                                    fragment.getName(),
+                                    fragment.getURL()));
+    
+                            return false;
+                        }
+                    } else {
+                        temp.getServlets().put(entry.getKey(), entry.getValue());
+                    }
+                }
+            }
+        }
+        servlets.putAll(temp.getServlets());
+        
+        if (sessionConfig.getSessionTimeout() == null) {
+            for (WebXml fragment : fragments) {
+                Integer value = fragment.getSessionConfig().getSessionTimeout();
+                if (value != null) {
+                    if (temp.getSessionConfig().getSessionTimeout() == null) {
+                        temp.getSessionConfig().setSessionTimeout(value.toString());
+                    } else if (value.equals(
+                            temp.getSessionConfig().getSessionTimeout())) {
+                        // Fragments use same value - no conflict
+                    } else {
+                        log.error(sm.getString(
+                                "webXml.mergeConflictSessionTimeout",
+                                fragment.getName(),
+                                fragment.getURL()));
+                        return false;
+                    }
+                }
+            }
+            if (temp.getSessionConfig().getSessionTimeout() != null) {
+                sessionConfig.setSessionTimeout(
+                        temp.getSessionConfig().getSessionTimeout().toString());
+            }
+        }
+        
+        if (sessionConfig.getCookieName() == null) {
+            for (WebXml fragment : fragments) {
+                String value = fragment.getSessionConfig().getCookieName();
+                if (value != null) {
+                    if (temp.getSessionConfig().getCookieName() == null) {
+                        temp.getSessionConfig().setCookieName(value);
+                    } else if (value.equals(
+                            temp.getSessionConfig().getCookieName())) {
+                        // Fragments use same value - no conflict
+                    } else {
+                        log.error(sm.getString(
+                                "webXml.mergeConflictSessionCookieName",
+                                fragment.getName(),
+                                fragment.getURL()));
+                        return false;
+                    }
+                }
+            }
+            sessionConfig.setCookieName(
+                    temp.getSessionConfig().getCookieName());
+        }
+        if (sessionConfig.getCookieDomain() == null) {
+            for (WebXml fragment : fragments) {
+                String value = fragment.getSessionConfig().getCookieDomain();
+                if (value != null) {
+                    if (temp.getSessionConfig().getCookieDomain() == null) {
+                        temp.getSessionConfig().setCookieDomain(value);
+                    } else if (value.equals(
+                            temp.getSessionConfig().getCookieDomain())) {
+                        // Fragments use same value - no conflict
+                    } else {
+                        log.error(sm.getString(
+                                "webXml.mergeConflictSessionCookieDomain",
+                                fragment.getName(),
+                                fragment.getURL()));
+                        return false;
+                    }
+                }
+            }
+            sessionConfig.setCookieDomain(
+                    temp.getSessionConfig().getCookieDomain());
+        }
+        if (sessionConfig.getCookiePath() == null) {
+            for (WebXml fragment : fragments) {
+                String value = fragment.getSessionConfig().getCookiePath();
+                if (value != null) {
+                    if (temp.getSessionConfig().getCookiePath() == null) {
+                        temp.getSessionConfig().setCookiePath(value);
+                    } else if (value.equals(
+                            temp.getSessionConfig().getCookiePath())) {
+                        // Fragments use same value - no conflict
+                    } else {
+                        log.error(sm.getString(
+                                "webXml.mergeConflictSessionCookiePath",
+                                fragment.getName(),
+                                fragment.getURL()));
+                        return false;
+                    }
+                }
+            }
+            sessionConfig.setCookiePath(
+                    temp.getSessionConfig().getCookiePath());
+        }
+        if (sessionConfig.getCookieComment() == null) {
+            for (WebXml fragment : fragments) {
+                String value = fragment.getSessionConfig().getCookieComment();
+                if (value != null) {
+                    if (temp.getSessionConfig().getCookieComment() == null) {
+                        temp.getSessionConfig().setCookieComment(value);
+                    } else if (value.equals(
+                            temp.getSessionConfig().getCookieComment())) {
+                        // Fragments use same value - no conflict
+                    } else {
+                        log.error(sm.getString(
+                                "webXml.mergeConflictSessionCookieComment",
+                                fragment.getName(),
+                                fragment.getURL()));
+                        return false;
+                    }
+                }
+            }
+            sessionConfig.setCookieComment(
+                    temp.getSessionConfig().getCookieComment());
+        }
+        if (sessionConfig.getCookieHttpOnly() == null) {
+            for (WebXml fragment : fragments) {
+                Boolean value = fragment.getSessionConfig().getCookieHttpOnly();
+                if (value != null) {
+                    if (temp.getSessionConfig().getCookieHttpOnly() == null) {
+                        temp.getSessionConfig().setCookieHttpOnly(value.toString());
+                    } else if (value.equals(
+                            temp.getSessionConfig().getCookieHttpOnly())) {
+                        // Fragments use same value - no conflict
+                    } else {
+                        log.error(sm.getString(
+                                "webXml.mergeConflictSessionCookieHttpOnly",
+                                fragment.getName(),
+                                fragment.getURL()));
+                        return false;
+                    }
+                }
+            }
+            if (temp.getSessionConfig().getCookieHttpOnly() != null) {
+                sessionConfig.setCookieHttpOnly(
+                        temp.getSessionConfig().getCookieHttpOnly().toString());
+            }
+        }
+        if (sessionConfig.getCookieSecure() == null) {
+            for (WebXml fragment : fragments) {
+                Boolean value = fragment.getSessionConfig().getCookieSecure();
+                if (value != null) {
+                    if (temp.getSessionConfig().getCookieSecure() == null) {
+                        temp.getSessionConfig().setCookieSecure(value.toString());
+                    } else if (value.equals(
+                            temp.getSessionConfig().getCookieSecure())) {
+                        // Fragments use same value - no conflict
+                    } else {
+                        log.error(sm.getString(
+                                "webXml.mergeConflictSessionCookieSecure",
+                                fragment.getName(),
+                                fragment.getURL()));
+                        return false;
+                    }
+                }
+            }
+            if (temp.getSessionConfig().getCookieSecure() != null) {
+                sessionConfig.setCookieSecure(
+                        temp.getSessionConfig().getCookieSecure().toString());
+            }
+        }
+        if (sessionConfig.getCookieMaxAge() == null) {
+            for (WebXml fragment : fragments) {
+                Integer value = fragment.getSessionConfig().getCookieMaxAge();
+                if (value != null) {
+                    if (temp.getSessionConfig().getCookieMaxAge() == null) {
+                        temp.getSessionConfig().setCookieMaxAge(value.toString());
+                    } else if (value.equals(
+                            temp.getSessionConfig().getCookieMaxAge())) {
+                        // Fragments use same value - no conflict
+                    } else {
+                        log.error(sm.getString(
+                                "webXml.mergeConflictSessionCookieMaxAge",
+                                fragment.getName(),
+                                fragment.getURL()));
+                        return false;
+                    }
+                }
+            }
+            if (temp.getSessionConfig().getCookieMaxAge() != null) {
+                sessionConfig.setCookieMaxAge(
+                        temp.getSessionConfig().getCookieMaxAge().toString());
+            }
+        }
+
+        if (sessionConfig.getSessionTrackingModes().size() == 0) {
+            for (WebXml fragment : fragments) {
+                EnumSet<SessionTrackingMode> value =
+                    fragment.getSessionConfig().getSessionTrackingModes();
+                if (value.size() > 0) {
+                    if (temp.getSessionConfig().getSessionTrackingModes().size() == 0) {
+                        temp.getSessionConfig().getSessionTrackingModes().addAll(value);
+                    } else if (value.equals(
+                            temp.getSessionConfig().getSessionTrackingModes())) {
+                        // Fragments use same value - no conflict
+                    } else {
+                        log.error(sm.getString(
+                                "webXml.mergeConflictSessionTrackingMode",
+                                fragment.getName(),
+                                fragment.getURL()));
+                        return false;
+                    }
+                }
+            }
+            sessionConfig.getSessionTrackingModes().addAll(
+                    temp.getSessionConfig().getSessionTrackingModes());
+        }
+        
+        for (WebXml fragment : fragments) {
+            if (!mergeMap(fragment.getTaglibs(), taglibs,
+                    temp.getTaglibs(), fragment, "Taglibs")) {
+                return false;
+            }
+        }
+        taglibs.putAll(temp.getTaglibs());
+
+        for (WebXml fragment : fragments) {
+            for (String welcomeFile : fragment.getWelcomeFiles()) {
+                // Always additive
+                addWelcomeFile(welcomeFile);
+            }
+        }
+
+        return true;
+    }
+    
+    private static <T extends ResourceBase> boolean mergeResourceMap(
+            Map<String, T> fragmentResources, Map<String, T> mainResources,
+            Map<String, T> tempResources,
+            Map<String,Boolean> mergeInjectionFlags, WebXml fragment) {
+        for (T resource : fragmentResources.values()) {
+            String resourceName = resource.getName();
+            boolean mergeInjectionFlag = false;
+            if (mainResources.containsKey(resourceName)) {
+                if (mergeInjectionFlags.containsKey(resourceName)) {
+                    mergeInjectionFlag =
+                        mergeInjectionFlags.get(resourceName).booleanValue(); 
+                } else {
+                    if (mainResources.get(
+                            resourceName).getInjectionTargets().size() == 0) {
+                        mergeInjectionFlag = true;
+                    }
+                    mergeInjectionFlags.put(resourceName,
+                            Boolean.valueOf(mergeInjectionFlag));
+                }
+                if (mergeInjectionFlag) {
+                    mainResources.get(resourceName).getInjectionTargets().addAll(
+                            resource.getInjectionTargets());
+                }
+            } else {
+                // Not defined in main web.xml
+                if (tempResources.containsKey(resourceName)) {
+                    log.error(sm.getString(
+                            "webXml.mergeConflictResource",
+                            resourceName,
+                            fragment.getName(),
+                            fragment.getURL()));
+                    return false;
+                } 
+                tempResources.put(resourceName, resource);
+            }
+        }
+        return true;
+    }
+    
+    private static <T> boolean mergeMap(Map<String,T> fragmentMap,
+            Map<String,T> mainMap, Map<String,T> tempMap, WebXml fragment,
+            String mapName) {
+        for (Entry<String, T> entry : fragmentMap.entrySet()) {
+            final String key = entry.getKey();
+            if (!mainMap.containsKey(key)) {
+                // Not defined in main web.xml
+                T value = entry.getValue();
+                if (tempMap.containsKey(key)) {
+                    if (value != null && !value.equals(
+                            tempMap.get(key))) {
+                        log.error(sm.getString(
+                                "webXml.mergeConflictString",
+                                mapName,
+                                key,
+                                fragment.getName(),
+                                fragment.getURL()));
+                        return false;
+                    }
+                } else {
+                    tempMap.put(key, value);
+                }
+            }
+        }
+        return true;
+    }
+    
+    private static boolean mergeFilter(FilterDef src, FilterDef dest,
+            boolean failOnConflict) {
+        if (dest.getAsyncSupported() == null) {
+            dest.setAsyncSupported(src.getAsyncSupported());
+        } else if (src.getAsyncSupported() != null) {
+            if (failOnConflict &&
+                    !src.getAsyncSupported().equals(dest.getAsyncSupported())) {
+                return false;
+            }
+        }
+
+        if (dest.getFilterClass()  == null) {
+            dest.setFilterClass(src.getFilterClass());
+        } else if (src.getFilterClass() != null) {
+            if (failOnConflict &&
+                    !src.getFilterClass().equals(dest.getFilterClass())) {
+                return false;
+            }
+        }
+        
+        for (Map.Entry<String,String> srcEntry :
+                src.getParameterMap().entrySet()) {
+            if (dest.getParameterMap().containsKey(srcEntry.getKey())) {
+                if (failOnConflict && !dest.getParameterMap().get(
+                        srcEntry.getKey()).equals(srcEntry.getValue())) {
+                    return false;
+                }
+            } else {
+                dest.addInitParameter(srcEntry.getKey(), srcEntry.getValue());
+            }
+        }
+        return true;
+    }
+    
+    private static boolean mergeServlet(ServletDef src, ServletDef dest,
+            boolean failOnConflict) {
+        // These tests should be unnecessary...
+        if (dest.getServletClass() != null && dest.getJspFile() != null) {
+            return false;
+        }
+        if (src.getServletClass() != null && src.getJspFile() != null) {
+            return false;
+        }
+        
+        
+        if (dest.getServletClass() == null && dest.getJspFile() == null) {
+            dest.setServletClass(src.getServletClass());
+            dest.setJspFile(src.getJspFile());
+        } else if (failOnConflict) {
+            if (src.getServletClass() != null &&
+                    (dest.getJspFile() != null ||
+                            !src.getServletClass().equals(dest.getServletClass()))) {
+                return false;
+            }
+            if (src.getJspFile() != null &&
+                    (dest.getServletClass() != null ||
+                            !src.getJspFile().equals(dest.getJspFile()))) {
+                return false;
+            }
+        }
+        
+        // Additive
+        for (SecurityRoleRef securityRoleRef : src.getSecurityRoleRefs()) {
+            dest.addSecurityRoleRef(securityRoleRef);
+        }
+        
+        if (dest.getLoadOnStartup() == null) {
+            if (src.getLoadOnStartup() != null) {
+                dest.setLoadOnStartup(src.getLoadOnStartup().toString());
+            }
+        } else if (src.getLoadOnStartup() != null) {
+            if (failOnConflict &&
+                    !src.getLoadOnStartup().equals(dest.getLoadOnStartup())) {
+                return false;
+            }
+        }
+        
+        if (dest.getEnabled() == null) {
+            if (src.getEnabled() != null) {
+                dest.setEnabled(src.getEnabled().toString());
+            }
+        } else if (src.getEnabled() != null) {
+            if (failOnConflict &&
+                    !src.getEnabled().equals(dest.getEnabled())) {
+                return false;
+            }
+        }
+        
+        for (Map.Entry<String,String> srcEntry :
+                src.getParameterMap().entrySet()) {
+            if (dest.getParameterMap().containsKey(srcEntry.getKey())) {
+                if (failOnConflict && !dest.getParameterMap().get(
+                        srcEntry.getKey()).equals(srcEntry.getValue())) {
+                    return false;
+                }
+            } else {
+                dest.addInitParameter(srcEntry.getKey(), srcEntry.getValue());
+            }
+        }
+        
+        if (dest.getMultipartDef() == null) {
+            dest.setMultipartDef(src.getMultipartDef());
+        } else if (src.getMultipartDef() != null) {
+            return mergeMultipartDef(src.getMultipartDef(),
+                    dest.getMultipartDef(), failOnConflict);
+        }
+        
+        if (dest.getAsyncSupported() == null) {
+            if (src.getAsyncSupported() != null) {
+                dest.setAsyncSupported(src.getAsyncSupported().toString());
+            }
+        } else if (src.getAsyncSupported() != null) {
+            if (failOnConflict &&
+                    !src.getAsyncSupported().equals(dest.getAsyncSupported())) {
+                return false;
+            }
+        }
+        
+        return true;
+    }
+
+    private static boolean mergeMultipartDef(MultipartDef src, MultipartDef dest,
+            boolean failOnConflict) {
+
+        if (dest.getLocation() == null) {
+            dest.setLocation(src.getLocation());
+        } else if (src.getLocation() != null) {
+            if (failOnConflict &&
+                    !src.getLocation().equals(dest.getLocation())) {
+                return false;
+            }
+        }
+
+        if (dest.getFileSizeThreshold() == null) {
+            dest.setFileSizeThreshold(src.getFileSizeThreshold());
+        } else if (src.getFileSizeThreshold() != null) {
+            if (failOnConflict &&
+                    !src.getFileSizeThreshold().equals(
+                            dest.getFileSizeThreshold())) {
+                return false;
+            }
+        }
+
+        if (dest.getMaxFileSize() == null) {
+            dest.setMaxFileSize(src.getMaxFileSize());
+        } else if (src.getLocation() != null) {
+            if (failOnConflict &&
+                    !src.getMaxFileSize().equals(dest.getMaxFileSize())) {
+                return false;
+            }
+        }
+
+        if (dest.getMaxRequestSize() == null) {
+            dest.setMaxRequestSize(src.getMaxRequestSize());
+        } else if (src.getMaxRequestSize() != null) {
+            if (failOnConflict &&
+                    !src.getMaxRequestSize().equals(
+                            dest.getMaxRequestSize())) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+    
+    
+    /**
+     * Generates the sub-set of the web-fragment.xml files to be processed in
+     * the order that the fragments must be processed as per the rules in the
+     * Servlet spec.
+     * 
+     * @param application   The application web.xml file
+     * @param fragments     The map of fragment names to web fragments
+     * @return Ordered list of web-fragment.xml files to process
+     */
+    public static Set<WebXml> orderWebFragments(WebXml application,
+            Map<String,WebXml> fragments) {
+
+        Set<WebXml> orderedFragments = new LinkedHashSet<WebXml>();
+        
+        boolean absoluteOrdering =
+            (application.getAbsoluteOrdering() != null);
+        
+        if (absoluteOrdering) {
+            // Only those fragments listed should be processed
+            Set<String> requestedOrder = application.getAbsoluteOrdering();
+            
+            for (String requestedName : requestedOrder) {
+                if (WebXml.ORDER_OTHERS.equals(requestedName)) {
+                    // Add all fragments not named explicitly at this point
+                    for (Entry<String, WebXml> entry : fragments.entrySet()) {
+                        if (!requestedOrder.contains(entry.getKey())) {
+                            WebXml fragment = entry.getValue();
+                            if (fragment != null) {
+                                orderedFragments.add(fragment);
+                            }
+                        }
+                    }
+                } else {
+                    WebXml fragment = fragments.get(requestedName);
+                    if (fragment != null) {
+                        orderedFragments.add(fragment);
+                    } else {
+                        log.warn(sm.getString("webXml.wrongFragmentName",requestedName));
+                    }
+                }
+            }
+        } else {
+            List<String> order = new LinkedList<String>();
+            // Start by adding all fragments - order doesn't matter
+            order.addAll(fragments.keySet());
+            
+            // Now go through and move elements to start/end depending on if
+            // they specify others
+            for (WebXml fragment : fragments.values()) {
+                String name = fragment.getName();
+                if (fragment.getBeforeOrdering().contains(WebXml.ORDER_OTHERS)) {
+                    // Move to beginning
+                    order.remove(name);
+                    order.add(0, name);
+                } else if (fragment.getAfterOrdering().contains(WebXml.ORDER_OTHERS)) {
+                    // Move to end
+                    order.remove(name);
+                    order.add(name);
+                }
+            }
+            
+            // Now apply remaining ordering
+            for (WebXml fragment : fragments.values()) {
+                String name = fragment.getName();
+                for (String before : fragment.getBeforeOrdering()) {
+                    if (!before.equals(WebXml.ORDER_OTHERS) &&
+                            order.contains(before) &&
+                            order.indexOf(before) < order.indexOf(name)) {
+                        order.remove(name);
+                        order.add(order.indexOf(before), name);
+                    }
+                }
+                for (String after : fragment.getAfterOrdering()) {
+                    if (!after.equals(WebXml.ORDER_OTHERS) &&
+                            order.contains(after) &&
+                            order.indexOf(after) > order.indexOf(name)) {
+                        order.remove(name);
+                        order.add(order.indexOf(after) + 1, name);
+                    }
+                }
+            }
+            
+            // Finally check ordering was applied correctly - if there are
+            // errors then that indicates circular references
+            for (WebXml fragment : fragments.values()) {
+                String name = fragment.getName();
+                for (String before : fragment.getBeforeOrdering()) {
+                    if (!before.equals(WebXml.ORDER_OTHERS) &&
+                            order.contains(before) &&
+                            order.indexOf(before) < order.indexOf(name)) {
+                        throw new IllegalArgumentException(sm.getString(""));
+                    }
+                }
+                for (String after : fragment.getAfterOrdering()) {
+                    if (!after.equals(WebXml.ORDER_OTHERS) &&
+                            order.contains(after) &&
+                            order.indexOf(after) > order.indexOf(name)) {
+                        throw new IllegalArgumentException();
+                    }
+                }
+            }
+            
+            // Build the ordered list
+            for (String name : order) {
+                orderedFragments.add(fragments.get(name));
+            }
+        }
+        
+        return orderedFragments;
+    }
+
+}    
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/mbeans-descriptors.xml b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/mbeans-descriptors.xml
new file mode 100644
index 0000000..fcff1e0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/mbeans-descriptors.xml
@@ -0,0 +1,210 @@
+<?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="ContextEnvironment"
+         className="org.apache.catalina.mbeans.ContextEnvironmentMBean"
+         description="Representation of an application environment entry"
+         domain="Catalina"
+         group="Resources"
+         type="org.apache.catalina.deploy.ContextEnvironment">
+    
+    <attribute name="className"
+               description="Fully qualified class name of the managed object"
+               type="java.lang.String"
+               writeable="false"/>
+      
+    <attribute name="description"
+               description="The description of this environment entry"
+               type="java.lang.String"/>
+
+    <attribute name="name"
+               description="The name of this environment entry"
+               type="java.lang.String"/>
+      
+    <attribute name="override"
+               description="Does this environment entry allow overrides by the
+               application deployment descriptor"
+               type="boolean"/>
+
+    <attribute name="type"
+               description="The type of this environment entry"
+               type="java.lang.String"/>
+
+    <attribute name="value"
+               description="The value of this environment entry"
+               type="java.lang.String"/>
+  </mbean>
+
+
+  <mbean         name="ContextResource"
+         className="org.apache.catalina.mbeans.ContextResourceMBean"
+         description="Representation of a resource reference for a web application"
+         domain="Catalina"
+         group="Resources"
+         type="org.apache.catalina.deploy.ContextResource">
+    
+    <attribute   name="auth"
+               description="The authorization requirement for this resource"
+               type="java.lang.String"/>
+      
+    <attribute   name="description"
+               description="The description of this resource"
+               type="java.lang.String"/>
+      
+    <attribute   name="name"
+               description="The name of this resource"
+               type="java.lang.String"/>
+      
+    <attribute   name="scope"
+               description="The sharing scope of this resource factory"
+               type="java.lang.String"/>
+      
+    <attribute   name="type"
+               description="The type of this environment entry"
+               type="java.lang.String"/>
+  </mbean>
+  
+  
+   <mbean         name="ContextResourceLink"
+          className="org.apache.catalina.mbeans.ContextResourceLinkMBean"
+          description="Representation of a resource link for a web application"
+          domain="Catalina"
+          group="Resources"
+          type="org.apache.catalina.deploy.ContextResourceLink">
+    
+    <attribute   name="global"
+               description="The global name of this resource"
+               type="java.lang.String"/>
+      
+    <attribute   name="name"
+               description="The name of this resource"
+               type="java.lang.String"/>
+               
+    <attribute   name="description"
+               description="The description of this resource"
+               type="java.lang.String"/>
+      
+    <attribute   name="type"
+               description="The type of this resource"
+               type="java.lang.String"/>
+      
+  </mbean>
+  
+  <mbean         name="NamingResources"
+            className="org.apache.catalina.mbeans.NamingResourcesMBean"
+          description="Holds and manages the naming resources defined in the
+                       J2EE Enterprise Naming Context and their associated 
+                       JNDI context"
+               domain="Catalina"
+                group="Resources"
+                 type="org.apache.catalina.deploy.NamingResources">
+                 
+    <attribute   name="container"
+          description="The container with which the naming resources are associated."
+                 type="java.lang.Object"
+            writeable="false"/>
+
+    <attribute   name="environments"
+          description="MBean Names of the set of defined environment entries
+                       for this web application"
+                 type="[Ljava.lang.String;"
+            writeable="false"/>
+
+    <attribute   name="resources"
+          description="MBean Names of all the defined resource references
+                       for this application."
+                 type="[Ljava.lang.String;"
+            writeable="false"/>
+
+    <attribute   name="resourceLinks"
+          description="MBean Names of all the defined resource link references
+                       for this application."
+                 type="[Ljava.lang.String;"
+            writeable="false"/>
+
+    <operation   name="addEnvironment"
+          description="Add an environment entry for this web application"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="envName"
+          description="New environment entry name"
+                 type="java.lang.String"/>
+      <parameter name="type"
+          description="New environment entry type"
+                 type="java.lang.String"/>
+      <parameter name="value"
+          description="New environment entry value"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="addResource"
+          description="Add a resource reference for this web application"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="resourceName"
+          description="New resource reference name"
+                 type="java.lang.String"/>
+      <parameter name="type"
+          description="New resource reference type"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="addResourceLink"
+          description="Add a resource link reference for this web application"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="resourceLinkName"
+          description="New resource reference name"
+                 type="java.lang.String"/>
+      <parameter name="type"
+          description="New resource reference type"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="removeEnvironment"
+          description="Remove any environment entry with the specified name"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="envName"
+          description="Name of the environment entry to remove"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="removeResource"
+          description="Remove any resource reference with the specified name"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="resourceName"
+          description="Name of the resource reference to remove"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="removeResourceLink"
+          description="Remove any resource link reference with the specified name"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="resourceLinkName"
+          description="Name of the resource reference to remove"
+                 type="java.lang.String"/>
+    </operation>
+
+  </mbean>
+
+
+</mbeans-descriptors>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/package.html b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/package.html
new file mode 100644
index 0000000..da90e0c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/deploy/package.html
@@ -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.
+-->
+<body>
+
+<p>This package contains Java objects that represent complex data structures
+from the web application deployment descriptor file (<code>web.xml</code>).
+It is assumed that these objects will be initialized within the context of
+a single thread, and then referenced in a read-only manner subsequent to that
+time.  Therefore, no multi-thread synchronization is utilized within the
+implementation classes.</p>
+
+</body>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/filters/AddDefaultCharsetFilter.java b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/AddDefaultCharsetFilter.java
new file mode 100644
index 0000000..a257046
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/AddDefaultCharsetFilter.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.filters;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+
+/**
+ * Filter that explicitly sets the default character set for media subtypes of
+ * the "text" type to ISO-8859-1, or another user defined character set. RFC2616
+ * explicitly states that browsers must use ISO-8859-1 if no character set is
+ * defined for media with subtype "text". However, browsers may attempt to
+ * auto-detect the character set. This may be exploited by an attacker to
+ * perform an XSS attack. Internet Explorer has this behaviour by default. Other
+ * browsers have an option to enable it.<br/>
+ * 
+ * This filter prevents the attack by explicitly setting a character set. Unless
+ * the provided character set is explicitly overridden by the user - in which
+ * case they deserve everything they get - the browser will adhere to an
+ * explicitly set character set, thus preventing the XSS attack.
+ */
+public class AddDefaultCharsetFilter extends FilterBase {
+
+    private static final Log log =
+        LogFactory.getLog(AddDefaultCharsetFilter.class);
+
+    private static final String DEFAULT_ENCODING = "ISO-8859-1";
+    
+    private String encoding;
+
+    public void setEncoding(String encoding) {
+        this.encoding = encoding;
+    }
+
+    @Override
+    protected Log getLogger() {
+        return log;
+    }
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+        super.init(filterConfig);
+        if (encoding == null || encoding.length() == 0 ||
+                encoding.equalsIgnoreCase("default")) {
+            encoding = DEFAULT_ENCODING;
+        } else if (encoding.equalsIgnoreCase("system")) {
+            encoding = Charset.defaultCharset().name();
+        } else if (!Charset.isSupported(encoding)) {
+            throw new IllegalArgumentException(sm.getString(
+                    "addDefaultCharset.unsupportedCharset", encoding));
+        }
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response,
+            FilterChain chain) throws IOException, ServletException {
+        
+        // Wrap the response
+        if (response instanceof HttpServletResponse) {
+            ResponseWrapper wrapped =
+                new ResponseWrapper((HttpServletResponse)response, encoding);
+            chain.doFilter(request, wrapped);
+        } else {
+            chain.doFilter(request, response);
+        }
+    }
+
+    /**
+     * Wrapper that adds a character set for text media types if no character
+     * set is specified.
+     */
+    public static class ResponseWrapper extends HttpServletResponseWrapper {
+
+        private String encoding;
+        
+        public ResponseWrapper(HttpServletResponse response, String encoding) {
+            super(response);
+            this.encoding = encoding;
+        }
+
+        @Override
+        public void setContentType(String ct) {
+            
+            if (ct != null && ct.startsWith("text/")) {
+                if (ct.indexOf("charset=") < 0) {
+                    super.setContentType(ct + ";charset=" + encoding);
+                } else {
+                    super.setContentType(ct);
+                    encoding = getCharacterEncoding();
+                }
+            } else {
+                super.setContentType(ct);
+            }
+
+        }
+
+        @Override
+        public void setCharacterEncoding(String charset) {
+            super.setCharacterEncoding(charset);
+            encoding = charset;
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/filters/Constants.java b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/Constants.java
new file mode 100644
index 0000000..06bdc3f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/Constants.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 org.apache.catalina.filters;
+
+
+/**
+ * Manifest constants for this Java package.
+ *
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: Constants.java,v 1.1 2011/06/28 21:08:25 rherrmann Exp $
+ */
+
+public final class Constants {
+
+    public static final String Package = "org.apache.catalina.filters";
+    
+    public static final String CSRF_NONCE_SESSION_ATTR_NAME =
+        "org.apache.catalina.filters.CSRF_NONCE";
+    
+    public static final String CSRF_NONCE_REQUEST_PARAM =
+        "org.apache.catalina.filters.CSRF_NONCE";
+
+    public static final String METHOD_GET = "GET";
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/filters/CsrfPreventionFilter.java b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/CsrfPreventionFilter.java
new file mode 100644
index 0000000..0189632
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/CsrfPreventionFilter.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.catalina.filters;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.security.SecureRandom;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+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 javax.servlet.http.HttpServletResponseWrapper;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * Provides basic CSRF protection for a web application. The filter assumes
+ * that:
+ * <ul>
+ * <li>The filter is mapped to /*</li>
+ * <li>{@link HttpServletResponse#encodeRedirectURL(String)} and
+ * {@link HttpServletResponse#encodeURL(String)} are used to encode all URLs
+ * returned to the client
+ * </ul>
+ */
+public class CsrfPreventionFilter extends FilterBase {
+
+    private static final Log log =
+        LogFactory.getLog(CsrfPreventionFilter.class);
+    
+    private String randomClass = SecureRandom.class.getName();
+    
+    private Random randomSource;
+
+    private final Set<String> entryPoints = new HashSet<String>();
+    
+    private int nonceCacheSize = 5;
+
+    @Override
+    protected Log getLogger() {
+        return log;
+    }
+
+    /**
+     * Entry points are URLs that will not be tested for the presence of a valid
+     * nonce. They are used to provide a way to navigate back to a protected
+     * application after navigating away from it. Entry points will be limited
+     * to HTTP GET requests and should not trigger any security sensitive
+     * actions.
+     * 
+     * @param entryPoints   Comma separated list of URLs to be configured as
+     *                      entry points.
+     */
+    public void setEntryPoints(String entryPoints) {
+        String values[] = entryPoints.split(",");
+        for (String value : values) {
+            this.entryPoints.add(value.trim());
+        }
+    }
+
+    /**
+     * Sets the number of previously issued nonces that will be cached on a LRU
+     * basis to support parallel requests, limited use of the refresh and back
+     * in the browser and similar behaviors that may result in the submission
+     * of a previous nonce rather than the current one. If not set, the default
+     * value of 5 will be used.
+     * 
+     * @param nonceCacheSize    The number of nonces to cache
+     */
+    public void setNonceCacheSize(int nonceCacheSize) {
+        this.nonceCacheSize = nonceCacheSize;
+    }
+    
+    /**
+     * Specify the class to use to generate the nonces. Must be in instance of
+     * {@link Random}.
+     * 
+     * @param randomClass   The name of the class to use
+     */
+    public void setRandomClass(String randomClass) {
+        this.randomClass = randomClass;
+    }
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+        // Set the parameters
+        super.init(filterConfig);
+        
+        try {
+            Class<?> clazz = Class.forName(randomClass);
+            randomSource = (Random) clazz.newInstance();
+        } catch (ClassNotFoundException e) {
+            ServletException se = new ServletException(sm.getString(
+                    "csrfPrevention.invalidRandomClass", randomClass), e);
+            throw se;
+        } catch (InstantiationException e) {
+            ServletException se = new ServletException(sm.getString(
+                    "csrfPrevention.invalidRandomClass", randomClass), e);
+            throw se;
+        } catch (IllegalAccessException e) {
+            ServletException se = new ServletException(sm.getString(
+                    "csrfPrevention.invalidRandomClass", randomClass), e);
+            throw se;
+        }
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response,
+            FilterChain chain) throws IOException, ServletException {
+
+        ServletResponse wResponse = null;
+        
+        if (request instanceof HttpServletRequest &&
+                response instanceof HttpServletResponse) {
+            
+            HttpServletRequest req = (HttpServletRequest) request;
+            HttpServletResponse res = (HttpServletResponse) response;
+
+            boolean skipNonceCheck = false;
+            
+            if (Constants.METHOD_GET.equals(req.getMethod())) {
+                String path = req.getServletPath();
+                if (req.getPathInfo() != null) {
+                    path = path + req.getPathInfo();
+                }
+                
+                if (entryPoints.contains(path)) {
+                    skipNonceCheck = true;
+                }
+            }
+
+            @SuppressWarnings("unchecked")
+            LruCache<String> nonceCache =
+                (LruCache<String>) req.getSession(true).getAttribute(
+                    Constants.CSRF_NONCE_SESSION_ATTR_NAME);
+            
+            if (!skipNonceCheck) {
+                String previousNonce =
+                    req.getParameter(Constants.CSRF_NONCE_REQUEST_PARAM);
+
+                if (nonceCache != null && !nonceCache.contains(previousNonce)) {
+                    res.sendError(HttpServletResponse.SC_FORBIDDEN);
+                    return;
+                }
+            }
+            
+            if (nonceCache == null) {
+                nonceCache = new LruCache<String>(nonceCacheSize);
+                req.getSession().setAttribute(
+                        Constants.CSRF_NONCE_SESSION_ATTR_NAME, nonceCache);
+            }
+            
+            String newNonce = generateNonce();
+            
+            nonceCache.add(newNonce);
+            
+            wResponse = new CsrfResponseWrapper(res, newNonce);
+        } else {
+            wResponse = response;
+        }
+        
+        chain.doFilter(request, wResponse);
+    }
+
+    /**
+     * 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 static class CsrfResponseWrapper
+            extends HttpServletResponseWrapper {
+
+        private String nonce;
+
+        public CsrfResponseWrapper(HttpServletResponse response, String nonce) {
+            super(response);
+            this.nonce = nonce;
+        }
+
+        @Override
+        @Deprecated
+        public String encodeRedirectUrl(String url) {
+            return encodeRedirectURL(url);
+        }
+
+        @Override
+        public String encodeRedirectURL(String url) {
+            return addNonce(super.encodeRedirectURL(url));
+        }
+
+        @Override
+        @Deprecated
+        public String encodeUrl(String url) {
+            return encodeURL(url);
+        }
+
+        @Override
+        public String encodeURL(String url) {
+            return addNonce(super.encodeURL(url));
+        }
+        
+        /**
+         * Return the specified URL with the nonce added to the query string. 
+         *
+         * @param url URL to be modified
+         * @param nonce The nonce to add
+         */
+        private String addNonce(String url) {
+
+            if ((url == null) || (nonce == null))
+                return (url);
+
+            String path = url;
+            String query = "";
+            String anchor = "";
+            int pound = path.indexOf('#');
+            if (pound >= 0) {
+                anchor = path.substring(pound);
+                path = path.substring(0, pound);
+            }
+            int question = path.indexOf('?');
+            if (question >= 0) {
+                query = path.substring(question);
+                path = path.substring(0, question);
+            }
+            StringBuilder sb = new StringBuilder(path);
+            if (query.length() >0) {
+                sb.append(query);
+                sb.append('&');
+            } else {
+                sb.append('?');
+            }
+            sb.append(Constants.CSRF_NONCE_REQUEST_PARAM);
+            sb.append('=');
+            sb.append(nonce);
+            sb.append(anchor);
+            return (sb.toString());
+        }
+    }
+    
+    protected static class LruCache<T> implements Serializable {
+
+        private static final long serialVersionUID = 1L;
+
+        // Although the internal implementation uses a Map, this cache
+        // implementation is only concerned with the keys.
+        private final Map<T,T> cache;
+        
+        public LruCache(final int cacheSize) {
+            cache = new LinkedHashMap<T,T>() {
+                private static final long serialVersionUID = 1L;
+                @Override
+                protected boolean removeEldestEntry(Map.Entry<T,T> eldest) {
+                    if (size() > cacheSize) {
+                        return true;
+                    }
+                    return false;
+                }
+            };
+        }
+        
+        public void add(T key) {
+            cache.put(key, null);
+        }
+        
+        public boolean contains(T key) {
+            return cache.containsKey(key);
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/filters/ExpiresFilter.java b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/ExpiresFilter.java
new file mode 100644
index 0000000..cefe8b0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/ExpiresFilter.java
@@ -0,0 +1,1600 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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.filters;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+import java.util.regex.Pattern;
+
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * <p>
+ * ExpiresFilter is a Java Servlet API port of <a
+ * href="http://httpd.apache.org/docs/2.2/mod/mod_expires.html">Apache
+ * mod_expires</a> to add ' <tt>Expires</tt>' and '
+ * <tt>Cache-Control: max-age=</tt>' headers to HTTP response according to its '
+ * <tt>Content-Type</tt>'.
+ * </p>
+ * 
+ * <p>
+ * Following documentation is inspired by <tt>mod_expires</tt> .
+ * </p>
+ * <h1>Summary</h1>
+ * <p>
+ * This filter controls the setting of the <tt>Expires</tt> HTTP header and the
+ * <tt>max-age</tt> directive of the <tt>Cache-Control</tt> HTTP header in
+ * server responses. The expiration date can set to be relative to either the
+ * time the source file was last modified, or to the time of the client access.
+ * </p>
+ * <p>
+ * These HTTP headers are an instruction to the client about the document&#x27;s
+ * validity and persistence. If cached, the document may be fetched from the
+ * cache rather than from the source until this time has passed. After that, the
+ * cache copy is considered &quot;expired&quot; and invalid, and a new copy must
+ * be obtained from the source.
+ * </p>
+ * <p>
+ * To modify <tt>Cache-Control</tt> directives other than <tt>max-age</tt> (see
+ * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9" >RFC
+ * 2616 section 14.9</a>), you can use other servlet filters or <a
+ * href="http://httpd.apache.org/docs/2.2/mod/mod_headers.html" >Apache Httpd
+ * mod_headers</a> module.
+ * </p>
+ * <h1>Filter Configuration</h1><h2>Basic configuration to add &#x27;
+ * <tt>Expires</tt>&#x27; and &#x27; <tt>Cache-Control: max-age=</tt>&#x27;
+ * headers to images, css and javascript</h2>
+ * 
+ * <code><pre>
+ * &lt;web-app ...&gt;
+ *    ...
+ *    &lt;filter&gt;
+ *       &lt;filter-name&gt;ExpiresFilter&lt;/filter-name&gt;
+ *       &lt;filter-class&gt;org.apache.catalina.filters.ExpiresFilter&lt;/filter-class&gt;
+ *       &lt;init-param&gt;
+ *          &lt;param-name&gt;ExpiresByType image&lt;/param-name&gt;
+ *          &lt;param-value&gt;access plus 10 minutes&lt;/param-value&gt;
+ *       &lt;/init-param&gt;
+ *       &lt;init-param&gt;
+ *          &lt;param-name&gt;ExpiresByType text/css&lt;/param-name&gt;
+ *          &lt;param-value&gt;access plus 10 minutes&lt;/param-value&gt;
+ *       &lt;/init-param&gt;
+ *       &lt;init-param&gt;
+ *          &lt;param-name&gt;ExpiresByType text/javascript&lt;/param-name&gt;
+ *          &lt;param-value&gt;access plus 10 minutes&lt;/param-value&gt;
+ *       &lt;/init-param&gt;
+ *    &lt;/filter&gt;
+ *    ...
+ *    &lt;filter-mapping&gt;
+ *       &lt;filter-name&gt;ExpiresFilter&lt;/filter-name&gt;
+ *       &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
+ *       &lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
+ *    &lt;/filter-mapping&gt;
+ *    ...
+ * &lt;/web-app&gt;
+ * </pre></code>
+ * 
+ * <h2>Configuration Parameters</h2>
+ * 
+ * <h3>
+ * <tt>ExpiresByType &lt;content-type&gt;</tt></h3>
+ * <p>
+ * This directive defines the value of the <tt>Expires</tt> header and the
+ * <tt>max-age</tt> directive of the <tt>Cache-Control</tt> header generated for
+ * documents of the specified type (<i>e.g.</i>, <tt>text/html</tt>). The second
+ * argument sets the number of seconds that will be added to a base time to
+ * construct the expiration date. The <tt>Cache-Control: max-age</tt> is
+ * calculated by subtracting the request time from the expiration date and
+ * expressing the result in seconds.
+ * </p>
+ * <p>
+ * The base time is either the last modification time of the file, or the time
+ * of the client&#x27;s access to the document. Which should be used is
+ * specified by the <tt>&lt;code&gt;</tt> field; <tt>M</tt> means that the
+ * file&#x27;s last modification time should be used as the base time, and
+ * <tt>A</tt> means the client&#x27;s access time should be used. The duration
+ * is expressed in seconds. <tt>A2592000</tt> stands for
+ * <tt>access plus 30 days</tt> in alternate syntax.
+ * </p>
+ * <p>
+ * The difference in effect is subtle. If <tt>M</tt> (<tt>modification</tt> in
+ * alternate syntax) is used, all current copies of the document in all caches
+ * will expire at the same time, which can be good for something like a weekly
+ * notice that&#x27;s always found at the same URL. If <tt>A</tt> (
+ * <tt>access</tt> or <tt>now</tt> in alternate syntax) is used, the date of
+ * expiration is different for each client; this can be good for image files
+ * that don&#x27;t change very often, particularly for a set of related
+ * documents that all refer to the same images (<i>i.e.</i>, the images will be
+ * accessed repeatedly within a relatively short timespan).
+ * </p>
+ * <p>
+ * <strong>Example:</strong>
+ * </p>
+ * 
+ * <code><pre>
+ * &lt;init-param&gt;
+ *    &lt;param-name&gt;ExpiresByType text/html&lt;/param-name&gt;&lt;param-value&gt;access plus 1 month 15   days 2 hours&lt;/param-value&gt;
+ * &lt;/init-param&gt;
+ *  
+ * &lt;init-param&gt;
+ *    &lt;!-- 2592000 seconds = 30 days --&gt;
+ *    &lt;param-name&gt;ExpiresByType image/gif&lt;/param-name&gt;&lt;param-value&gt;A2592000&lt;/param-value&gt;
+ * &lt;/init-param&gt;
+ * </pre></code>
+ * <p>
+ * Note that this directive only has effect if <tt>ExpiresActive On</tt> has
+ * been specified. It overrides, for the specified MIME type <i>only</i>, any
+ * expiration date set by the <tt>ExpiresDefault</tt> directive.
+ * </p>
+ * <p>
+ * You can also specify the expiration time calculation using an alternate
+ * syntax, described earlier in this document.
+ * </p>
+ * <h3>
+ * <tt>ExpiresExcludedResponseStatusCodes</tt></h3>
+ * <p>
+ * This directive defines the http response status codes for which the
+ * <tt>ExpiresFilter</tt> will not generate expiration headers. By default, the
+ * <tt>304</tt> status code (&quot;<tt>Not modified</tt>&quot;) is skipped. The
+ * value is a comma separated list of http status codes.
+ * </p>
+ * <p>
+ * This directive is useful to ease usage of <tt>ExpiresDefault</tt> directive.
+ * Indeed, the behavior of <tt>304 Not modified</tt> (which does specify a
+ * <tt>Content-Type</tt> header) combined with <tt>Expires</tt> and
+ * <tt>Cache-Control:max-age=</tt> headers can be unnecessarily tricky to
+ * understand.
+ * </p>
+ * <p>
+ * Configuration sample :
+ * </p>
+ * 
+ * <code><pre>
+ * &lt;init-param&gt;
+ *    &lt;param-name&gt;ExpiresExcludedResponseStatusCodes&lt;/param-name&gt;&lt;param-value&gt;302, 500, 503&lt;/param-value&gt;
+ * &lt;/init-param&gt;
+ * </pre></code>
+ * 
+ * <h3>ExpiresDefault</h3>
+ * <p>
+ * This directive sets the default algorithm for calculating the expiration time
+ * for all documents in the affected realm. It can be overridden on a
+ * type-by-type basis by the <tt>ExpiresByType</tt> directive. See the
+ * description of that directive for details about the syntax of the argument,
+ * and the "alternate syntax" description as well.
+ * </p>
+ * <h1>Alternate Syntax</h1>
+ * <p>
+ * The <tt>ExpiresDefault</tt> and <tt>ExpiresByType</tt> directives can also be
+ * defined in a more readable syntax of the form:
+ * </p>
+ * 
+ * <code><pre>
+ * &lt;init-param&gt;
+ *    &lt;param-name&gt;ExpiresDefault&lt;/param-name&gt;&lt;param-value&gt;&lt;base&gt; [plus] {&lt;num&gt;   &lt;type&gt;}*&lt;/param-value&gt;
+ * &lt;/init-param&gt;
+ *  
+ * &lt;init-param&gt;
+ *    &lt;param-name&gt;ExpiresByType type/encoding&lt;/param-name&gt;&lt;param-value&gt;&lt;base&gt; [plus]   {&lt;num&gt; &lt;type&gt;}*&lt;/param-value&gt;
+ * &lt;/init-param&gt;
+ * </pre></code>
+ * <p>
+ * where <tt>&lt;base&gt;</tt> is one of:
+ * <ul>
+ * <li><tt>access</tt></li>
+ * <li><tt>now</tt> (equivalent to &#x27;<tt>access</tt>&#x27;)</li>
+ * <li><tt>modification</tt></li>
+ * </ul>
+ * </p>
+ * <p>
+ * The <tt>plus</tt> keyword is optional. <tt>&lt;num&gt;</tt> should be an
+ * integer value (acceptable to <tt>Integer.parseInt()</tt>), and
+ * <tt>&lt;type&gt;</tt> is one of:
+ * <ul>
+ * <li><tt>years</tt></li>
+ * <li><tt>months</tt></li>
+ * <li><tt>weeks</tt></li>
+ * <li><tt>days</tt></li>
+ * <li><tt>hours</tt></li>
+ * <li><tt>minutes</tt></li>
+ * <li><tt>seconds</tt></li>
+ * </ul>
+ * For example, any of the following directives can be used to make documents
+ * expire 1 month after being accessed, by default:
+ * </p>
+ * 
+ * <code><pre>
+ * &lt;init-param&gt;
+ *    &lt;param-name&gt;ExpiresDefault&lt;/param-name&gt;&lt;param-value&gt;access plus 1 month&lt;/param-value&gt;
+ * &lt;/init-param&gt;
+ *  
+ * &lt;init-param&gt;
+ *    &lt;param-name&gt;ExpiresDefault&lt;/param-name&gt;&lt;param-value&gt;access plus 4 weeks&lt;/param-value&gt;
+ * &lt;/init-param&gt;
+ *  
+ * &lt;init-param&gt;
+ *    &lt;param-name&gt;ExpiresDefault&lt;/param-name&gt;&lt;param-value&gt;access plus 30 days&lt;/param-value&gt;
+ * &lt;/init-param&gt;
+ * </pre></code>
+ * <p>
+ * The expiry time can be fine-tuned by adding several &#x27;
+ * <tt>&lt;num&gt; &lt;type&gt;</tt>&#x27; clauses:
+ * </p>
+ * 
+ * <code><pre>
+ * &lt;init-param&gt;
+ *    &lt;param-name&gt;ExpiresByType text/html&lt;/param-name&gt;&lt;param-value&gt;access plus 1 month 15   days 2 hours&lt;/param-value&gt;
+ * &lt;/init-param&gt;
+ *  
+ * &lt;init-param&gt;
+ *    &lt;param-name&gt;ExpiresByType image/gif&lt;/param-name&gt;&lt;param-value&gt;modification plus 5 hours 3   minutes&lt;/param-value&gt;
+ * &lt;/init-param&gt;
+ * </pre></code>
+ * <p>
+ * Note that if you use a modification date based setting, the <tt>Expires</tt>
+ * header will <strong>not</strong> be added to content that does not come from
+ * a file on disk. This is due to the fact that there is no modification time
+ * for such content.
+ * </p>
+ * <h1>Expiration headers generation eligibility</h1>
+ * <p>
+ * A response is eligible to be enriched by <tt>ExpiresFilter</tt> if :
+ * <ol>
+ * <li>no expiration header is defined (<tt>Expires</tt> header or the
+ * <tt>max-age</tt> directive of the <tt>Cache-Control</tt> header),</li>
+ * <li>the response status code is not excluded by the directive
+ * <tt>ExpiresExcludedResponseStatusCodes</tt>,</li>
+ * <li>the <tt>Content-Type</tt> of the response matches one of the types
+ * defined the in <tt>ExpiresByType</tt> directives or the
+ * <tt>ExpiresDefault</tt> directive is defined.</li>
+ * </ol>
+ * </p>
+ * <p>
+ * Note :
+ * <ul>
+ * <li>If <tt>Cache-Control</tt> header contains other directives than
+ * <tt>max-age</tt>, they are concatenated with the <tt>max-age</tt> directive
+ * that is added by the <tt>ExpiresFilter</tt>.</li>
+ * </ul>
+ * </p>
+ * <h1>Expiration configuration selection</h1>
+ * <p>
+ * The expiration configuration if elected according to the following algorithm:
+ * <ol>
+ * <li><tt>ExpiresByType</tt> matching the exact content-type returned by
+ * <tt>HttpServletResponse.getContentType()</tt> possibly including the charset
+ * (e.g. &#x27;<tt>text/xml;charset=UTF-8</tt>&#x27;),</li>
+ * <li><tt>ExpiresByType</tt> matching the content-type without the charset if
+ * <tt>HttpServletResponse.getContentType()</tt> contains a charset (e.g. &#x27;
+ * <tt>text/xml;charset=UTF-8</tt>&#x27; -&gt; &#x27;<tt>text/xml</tt>&#x27;),</li>
+ * <li><tt>ExpiresByType</tt> matching the major type (e.g. substring before
+ * &#x27;<tt>/</tt>&#x27;) of <tt>HttpServletResponse.getContentType()</tt>
+ * (e.g. &#x27;<tt>text/xml;charset=UTF-8</tt>&#x27; -&gt; &#x27;<tt>text</tt>
+ * &#x27;),</li>
+ * <li><tt>ExpiresDefault</tt></li>
+ * </ol>
+ * </p>
+ * <h1>Implementation Details</h1><h2>When to write the expiration headers ?</h2>
+ * <p>
+ * The <tt>ExpiresFilter</tt> traps the &#x27;on before write response
+ * body&#x27; event to decide whether it should generate expiration headers or
+ * not.
+ * </p>
+ * <p>
+ * To trap the &#x27;before write response body&#x27; event, the
+ * <tt>ExpiresFilter</tt> wraps the http servlet response&#x27;s writer and
+ * outputStream to intercept calls to the methods <tt>write()</tt>,
+ * <tt>print()</tt>, <tt>close()</tt> and <tt>flush()</tt>. For empty response
+ * body (e.g. empty files), the <tt>write()</tt>, <tt>print()</tt>,
+ * <tt>close()</tt> and <tt>flush()</tt> methods are not called; to handle this
+ * case, the <tt>ExpiresFilter</tt>, at the end of its <tt>doFilter()</tt>
+ * method, manually triggers the <tt>onBeforeWriteResponseBody()</tt> method.
+ * </p>
+ * <h2>Configuration syntax</h2>
+ * <p>
+ * The <tt>ExpiresFilter</tt> supports the same configuration syntax as Apache
+ * Httpd mod_expires.
+ * </p>
+ * <p>
+ * A challenge has been to choose the name of the <tt>&lt;param-name&gt;</tt>
+ * associated with <tt>ExpiresByType</tt> in the <tt>&lt;filter&gt;</tt>
+ * declaration. Indeed, Several <tt>ExpiresByType</tt> directives can be
+ * declared when <tt>web.xml</tt> syntax does not allow to declare several
+ * <tt>&lt;init-param&gt;</tt> with the same name.
+ * </p>
+ * <p>
+ * The workaround has been to declare the content type in the
+ * <tt>&lt;param-name&gt;</tt> rather than in the <tt>&lt;param-value&gt;</tt>.
+ * </p>
+ * <h2>Designed for extension : the open/close principle</h2>
+ * <p>
+ * The <tt>ExpiresFilter</tt> has been designed for extension following the
+ * open/close principle.
+ * </p>
+ * <p>
+ * Key methods to override for extension are :
+ * <ul>
+ * <li>
+ * {@link #isEligibleToExpirationHeaderGeneration(HttpServletRequest, XHttpServletResponse)}
+ * </li>
+ * <li>
+ * {@link #getExpirationDate(XHttpServletResponse)}</li>
+ * </ul>
+ * </p>
+ * <h1>Troubleshooting</h1>
+ * <p>
+ * To troubleshoot, enable logging on the
+ * <tt>org.apache.catalina.filters.ExpiresFilter</tt>.
+ * </p>
+ * <p>
+ * Extract of logging.properties
+ * </p>
+ * 
+ * <code><pre>
+ * org.apache.catalina.filters.ExpiresFilter.level = FINE
+ * </pre></code>
+ * <p>
+ * Sample of initialization log message :
+ * </p>
+ * 
+ * <code><pre>
+ * Mar 26, 2010 2:01:41 PM org.apache.catalina.filters.ExpiresFilter init
+ * FINE: Filter initialized with configuration ExpiresFilter[
+ *    excludedResponseStatusCode=[304], 
+ *    default=null, 
+ *    byType={
+ *       image=ExpiresConfiguration[startingPoint=ACCESS_TIME, duration=[10 MINUTE]], 
+ *       text/css=ExpiresConfiguration[startingPoint=ACCESS_TIME, duration=[10 MINUTE]], 
+ *       text/javascript=ExpiresConfiguration[startingPoint=ACCESS_TIME, duration=[10 MINUTE]]}]
+ * </pre></code>
+ * <p>
+ * Sample of per-request log message where <tt>ExpiresFilter</tt> adds an
+ * expiration date
+ * </p>
+ * 
+ * <code><pre>
+ * Mar 26, 2010 2:09:47 PM org.apache.catalina.filters.ExpiresFilter onBeforeWriteResponseBody
+ * FINE: Request "/tomcat.gif" with response status "200" content-type "image/gif", set expiration date 3/26/10 2:19 PM
+ * </pre></code>
+ * <p>
+ * Sample of per-request log message where <tt>ExpiresFilter</tt> does not add
+ * an expiration date
+ * </p>
+ * 
+ * <code><pre>
+ * Mar 26, 2010 2:10:27 PM org.apache.catalina.filters.ExpiresFilter onBeforeWriteResponseBody
+ * FINE: Request "/docs/config/manager.html" with response status "200" content-type "text/html", no expiration configured
+ * </pre></code>
+ * 
+ */
+public class ExpiresFilter extends FilterBase {
+
+    /**
+     * Duration composed of an {@link #amount} and a {@link #unit}
+     */
+    protected static class Duration {
+
+        protected final int amount;
+
+        protected final DurationUnit unit;
+
+        public Duration(int amount, DurationUnit unit) {
+            super();
+            this.amount = amount;
+            this.unit = unit;
+        }
+
+        public int getAmount() {
+            return amount;
+        }
+
+        public DurationUnit getUnit() {
+            return unit;
+        }
+
+        @Override
+        public String toString() {
+            return amount + " " + unit;
+        }
+    }
+
+    /**
+     * Duration unit
+     */
+    protected enum DurationUnit {
+        DAY(Calendar.DAY_OF_YEAR), HOUR(Calendar.HOUR), MINUTE(Calendar.MINUTE), MONTH(
+                Calendar.MONTH), SECOND(Calendar.SECOND), WEEK(
+                Calendar.WEEK_OF_YEAR), YEAR(Calendar.YEAR);
+        private final int calendardField;
+
+        private DurationUnit(int calendardField) {
+            this.calendardField = calendardField;
+        }
+
+        public int getCalendardField() {
+            return calendardField;
+        }
+
+    }
+
+    /**
+     * <p>
+     * Main piece of configuration of the filter.
+     * </p>
+     * <p>
+     * Can be expressed like '<tt>access plus 1 month 15   days 2 hours</tt>'.
+     * </p>
+     */
+    protected static class ExpiresConfiguration {
+        /**
+         * List of duration elements.
+         */
+        private List<Duration> durations;
+
+        /**
+         * Starting point of the elaspse to set in the response.
+         */
+        private StartingPoint startingPoint;
+
+        public ExpiresConfiguration(StartingPoint startingPoint,
+                List<Duration> durations) {
+            super();
+            this.startingPoint = startingPoint;
+            this.durations = durations;
+        }
+
+        public List<Duration> getDurations() {
+            return durations;
+        }
+
+        public StartingPoint getStartingPoint() {
+            return startingPoint;
+        }
+
+        @Override
+        public String toString() {
+            return "ExpiresConfiguration[startingPoint=" + startingPoint +
+                    ", duration=" + durations + "]";
+        }
+    }
+
+    /**
+     * Expiration configuration starting point. Either the time the
+     * html-page/servlet-response was served ({@link StartingPoint#ACCESS_TIME})
+     * or the last time the html-page/servlet-response was modified (
+     * {@link StartingPoint#LAST_MODIFICATION_TIME}).
+     */
+    protected enum StartingPoint {
+        ACCESS_TIME, LAST_MODIFICATION_TIME
+    }
+
+    /**
+     * <p>
+     * Wrapping extension of the {@link HttpServletResponse} to yrap the
+     * "Start Write Response Body" event.
+     * </p>
+     * <p>
+     * For performance optimization : this extended response holds the
+     * {@link #lastModifiedHeader} and {@link #cacheControlHeader} values access
+     * to the slow {@link #getHeader(String)} and to spare the <tt>string</tt>
+     * to <tt>date</tt> to <tt>long</tt> conversion.
+     * </p>
+     */
+    public class XHttpServletResponse extends HttpServletResponseWrapper {
+
+        /**
+         * Value of the <tt>Cache-Control/tt> http response header if it has
+         * been set.
+         */
+        private String cacheControlHeader;
+
+        /**
+         * Value of the <tt>Last-Modified</tt> http response header if it has
+         * been set.
+         */
+        private long lastModifiedHeader;
+
+        private boolean lastModifiedHeaderSet;
+
+        private PrintWriter printWriter;
+
+        private HttpServletRequest request;
+
+        private ServletOutputStream servletOutputStream;
+
+        /**
+         * Indicates whether calls to write methods (<tt>write(...)</tt>,
+         * <tt>print(...)</tt>, etc) of the response body have been called or
+         * not.
+         */
+        private boolean writeResponseBodyStarted;
+
+        public XHttpServletResponse(HttpServletRequest request,
+                HttpServletResponse response) {
+            super(response);
+            this.request = request;
+        }
+
+        @Override
+        public void addDateHeader(String name, long date) {
+            super.addDateHeader(name, date);
+            if (!lastModifiedHeaderSet) {
+                this.lastModifiedHeader = date;
+                this.lastModifiedHeaderSet = true;
+            }
+        }
+
+        @Override
+        public void addHeader(String name, String value) {
+            super.addHeader(name, value);
+            if (HEADER_CACHE_CONTROL.equalsIgnoreCase(name) &&
+                    cacheControlHeader == null) {
+                cacheControlHeader = value;
+            }
+        }
+
+        public String getCacheControlHeader() {
+            return cacheControlHeader;
+        }
+
+        public long getLastModifiedHeader() {
+            return lastModifiedHeader;
+        }
+
+        @Override
+        public ServletOutputStream getOutputStream() throws IOException {
+            if (servletOutputStream == null) {
+                servletOutputStream = new XServletOutputStream(
+                        super.getOutputStream(), request, this);
+            }
+            return servletOutputStream;
+        }
+
+        @Override
+        public PrintWriter getWriter() throws IOException {
+            if (printWriter == null) {
+                printWriter = new XPrintWriter(super.getWriter(), request, this);
+            }
+            return printWriter;
+        }
+
+        public boolean isLastModifiedHeaderSet() {
+            return lastModifiedHeaderSet;
+        }
+
+        public boolean isWriteResponseBodyStarted() {
+            return writeResponseBodyStarted;
+        }
+
+        @Override
+        public void reset() {
+            super.reset();
+            this.lastModifiedHeader = 0;
+            this.lastModifiedHeaderSet = false;
+            this.cacheControlHeader = null;
+        }
+
+        @Override
+        public void setDateHeader(String name, long date) {
+            super.setDateHeader(name, date);
+            if (HEADER_LAST_MODIFIED.equalsIgnoreCase(name)) {
+                this.lastModifiedHeader = date;
+                this.lastModifiedHeaderSet = true;
+            }
+        }
+
+        @Override
+        public void setHeader(String name, String value) {
+            super.setHeader(name, value);
+            if (HEADER_CACHE_CONTROL.equalsIgnoreCase(name)) {
+                this.cacheControlHeader = value;
+            }
+        }
+
+        public void setWriteResponseBodyStarted(boolean writeResponseBodyStarted) {
+            this.writeResponseBodyStarted = writeResponseBodyStarted;
+        }
+    }
+
+    /**
+     * Wrapping extension of {@link PrintWriter} to trap the
+     * "Start Write Response Body" event.
+     */
+    public class XPrintWriter extends PrintWriter {
+        private PrintWriter out;
+
+        private HttpServletRequest request;
+
+        private XHttpServletResponse response;
+
+        public XPrintWriter(PrintWriter out, HttpServletRequest request,
+                XHttpServletResponse response) {
+            super(out);
+            this.out = out;
+            this.request = request;
+            this.response = response;
+        }
+
+        @Override
+        public PrintWriter append(char c) {
+            fireBeforeWriteResponseBodyEvent();
+            return out.append(c);
+        }
+
+        @Override
+        public PrintWriter append(CharSequence csq) {
+            fireBeforeWriteResponseBodyEvent();
+            return out.append(csq);
+        }
+
+        @Override
+        public PrintWriter append(CharSequence csq, int start, int end) {
+            fireBeforeWriteResponseBodyEvent();
+            return out.append(csq, start, end);
+        }
+
+        @Override
+        public void close() {
+            fireBeforeWriteResponseBodyEvent();
+            out.close();
+        }
+
+        private void fireBeforeWriteResponseBodyEvent() {
+            if (!this.response.isWriteResponseBodyStarted()) {
+                this.response.setWriteResponseBodyStarted(true);
+                onBeforeWriteResponseBody(request, response);
+            }
+        }
+
+        @Override
+        public void flush() {
+            fireBeforeWriteResponseBodyEvent();
+            out.flush();
+        }
+
+        @Override
+        public void print(boolean b) {
+            fireBeforeWriteResponseBodyEvent();
+            out.print(b);
+        }
+
+        @Override
+        public void print(char c) {
+            fireBeforeWriteResponseBodyEvent();
+            out.print(c);
+        }
+
+        @Override
+        public void print(char[] s) {
+            fireBeforeWriteResponseBodyEvent();
+            out.print(s);
+        }
+
+        @Override
+        public void print(double d) {
+            fireBeforeWriteResponseBodyEvent();
+            out.print(d);
+        }
+
+        @Override
+        public void print(float f) {
+            fireBeforeWriteResponseBodyEvent();
+            out.print(f);
+        }
+
+        @Override
+        public void print(int i) {
+            fireBeforeWriteResponseBodyEvent();
+            out.print(i);
+        }
+
+        @Override
+        public void print(long l) {
+            fireBeforeWriteResponseBodyEvent();
+            out.print(l);
+        }
+
+        @Override
+        public void print(Object obj) {
+            fireBeforeWriteResponseBodyEvent();
+            out.print(obj);
+        }
+
+        @Override
+        public void print(String s) {
+            fireBeforeWriteResponseBodyEvent();
+            out.print(s);
+        }
+
+        @Override
+        public PrintWriter printf(Locale l, String format, Object... args) {
+            fireBeforeWriteResponseBodyEvent();
+            return out.printf(l, format, args);
+        }
+
+        @Override
+        public PrintWriter printf(String format, Object... args) {
+            fireBeforeWriteResponseBodyEvent();
+            return out.printf(format, args);
+        }
+
+        @Override
+        public void println() {
+            fireBeforeWriteResponseBodyEvent();
+            out.println();
+        }
+
+        @Override
+        public void println(boolean x) {
+            fireBeforeWriteResponseBodyEvent();
+            out.println(x);
+        }
+
+        @Override
+        public void println(char x) {
+            fireBeforeWriteResponseBodyEvent();
+            out.println(x);
+        }
+
+        @Override
+        public void println(char[] x) {
+            fireBeforeWriteResponseBodyEvent();
+            out.println(x);
+        }
+
+        @Override
+        public void println(double x) {
+            fireBeforeWriteResponseBodyEvent();
+            out.println(x);
+        }
+
+        @Override
+        public void println(float x) {
+            fireBeforeWriteResponseBodyEvent();
+            out.println(x);
+        }
+
+        @Override
+        public void println(int x) {
+            fireBeforeWriteResponseBodyEvent();
+            out.println(x);
+        }
+
+        @Override
+        public void println(long x) {
+            fireBeforeWriteResponseBodyEvent();
+            out.println(x);
+        }
+
+        @Override
+        public void println(Object x) {
+            fireBeforeWriteResponseBodyEvent();
+            out.println(x);
+        }
+
+        @Override
+        public void println(String x) {
+            fireBeforeWriteResponseBodyEvent();
+            out.println(x);
+        }
+
+        @Override
+        public void write(char[] buf) {
+            fireBeforeWriteResponseBodyEvent();
+            out.write(buf);
+        }
+
+        @Override
+        public void write(char[] buf, int off, int len) {
+            fireBeforeWriteResponseBodyEvent();
+            out.write(buf, off, len);
+        }
+
+        @Override
+        public void write(int c) {
+            fireBeforeWriteResponseBodyEvent();
+            out.write(c);
+        }
+
+        @Override
+        public void write(String s) {
+            fireBeforeWriteResponseBodyEvent();
+            out.write(s);
+        }
+
+        @Override
+        public void write(String s, int off, int len) {
+            fireBeforeWriteResponseBodyEvent();
+            out.write(s, off, len);
+        }
+
+    }
+
+    /**
+     * Wrapping extension of {@link ServletOutputStream} to trap the
+     * "Start Write Response Body" event.
+     */
+    public class XServletOutputStream extends ServletOutputStream {
+
+        private HttpServletRequest request;
+
+        private XHttpServletResponse response;
+
+        private ServletOutputStream servletOutputStream;
+
+        public XServletOutputStream(ServletOutputStream servletOutputStream,
+                HttpServletRequest request, XHttpServletResponse response) {
+            super();
+            this.servletOutputStream = servletOutputStream;
+            this.response = response;
+            this.request = request;
+        }
+
+        @Override
+        public void close() throws IOException {
+            fireOnBeforeWriteResponseBodyEvent();
+            servletOutputStream.close();
+        }
+
+        private void fireOnBeforeWriteResponseBodyEvent() {
+            if (!this.response.isWriteResponseBodyStarted()) {
+                this.response.setWriteResponseBodyStarted(true);
+                onBeforeWriteResponseBody(request, response);
+            }
+        }
+
+        @Override
+        public void flush() throws IOException {
+            fireOnBeforeWriteResponseBodyEvent();
+            servletOutputStream.flush();
+        }
+
+        @Override
+        public void print(boolean b) throws IOException {
+            fireOnBeforeWriteResponseBodyEvent();
+            servletOutputStream.print(b);
+        }
+
+        @Override
+        public void print(char c) throws IOException {
+            fireOnBeforeWriteResponseBodyEvent();
+            servletOutputStream.print(c);
+        }
+
+        @Override
+        public void print(double d) throws IOException {
+            fireOnBeforeWriteResponseBodyEvent();
+            servletOutputStream.print(d);
+        }
+
+        @Override
+        public void print(float f) throws IOException {
+            fireOnBeforeWriteResponseBodyEvent();
+            servletOutputStream.print(f);
+        }
+
+        @Override
+        public void print(int i) throws IOException {
+            fireOnBeforeWriteResponseBodyEvent();
+            servletOutputStream.print(i);
+        }
+
+        @Override
+        public void print(long l) throws IOException {
+            fireOnBeforeWriteResponseBodyEvent();
+            servletOutputStream.print(l);
+        }
+
+        @Override
+        public void print(String s) throws IOException {
+            fireOnBeforeWriteResponseBodyEvent();
+            servletOutputStream.print(s);
+        }
+
+        @Override
+        public void println() throws IOException {
+            fireOnBeforeWriteResponseBodyEvent();
+            servletOutputStream.println();
+        }
+
+        @Override
+        public void println(boolean b) throws IOException {
+            fireOnBeforeWriteResponseBodyEvent();
+            servletOutputStream.println(b);
+        }
+
+        @Override
+        public void println(char c) throws IOException {
+            fireOnBeforeWriteResponseBodyEvent();
+            servletOutputStream.println(c);
+        }
+
+        @Override
+        public void println(double d) throws IOException {
+            fireOnBeforeWriteResponseBodyEvent();
+            servletOutputStream.println(d);
+        }
+
+        @Override
+        public void println(float f) throws IOException {
+            fireOnBeforeWriteResponseBodyEvent();
+            servletOutputStream.println(f);
+        }
+
+        @Override
+        public void println(int i) throws IOException {
+            fireOnBeforeWriteResponseBodyEvent();
+            servletOutputStream.println(i);
+        }
+
+        @Override
+        public void println(long l) throws IOException {
+            fireOnBeforeWriteResponseBodyEvent();
+            servletOutputStream.println(l);
+        }
+
+        @Override
+        public void println(String s) throws IOException {
+            fireOnBeforeWriteResponseBodyEvent();
+            servletOutputStream.println(s);
+        }
+
+        @Override
+        public void write(byte[] b) throws IOException {
+            fireOnBeforeWriteResponseBodyEvent();
+            servletOutputStream.write(b);
+        }
+
+        @Override
+        public void write(byte[] b, int off, int len) throws IOException {
+            fireOnBeforeWriteResponseBodyEvent();
+            servletOutputStream.write(b, off, len);
+        }
+
+        @Override
+        public void write(int b) throws IOException {
+            fireOnBeforeWriteResponseBodyEvent();
+            servletOutputStream.write(b);
+        }
+
+    }
+
+    /**
+     * {@link Pattern} for a comma delimited string that support whitespace
+     * characters
+     */
+    private static final Pattern commaSeparatedValuesPattern = Pattern.compile("\\s*,\\s*");
+
+    private static final String HEADER_CACHE_CONTROL = "Cache-Control";
+
+    private static final String HEADER_EXPIRES = "Expires";
+
+    private static final String HEADER_LAST_MODIFIED = "Last-Modified";
+
+    private static final Log log = LogFactory.getLog(ExpiresFilter.class);
+
+    private static final String PARAMETER_EXPIRES_BY_TYPE = "ExpiresByType";
+
+    private static final String PARAMETER_EXPIRES_DEFAULT = "ExpiresDefault";
+
+    private static final String PARAMETER_EXPIRES_EXCLUDED_RESPONSE_STATUS_CODES = "ExpiresExcludedResponseStatusCodes";
+
+    /**
+     * Convert a comma delimited list of numbers into an <tt>int[]</tt>.
+     * 
+     * @param commaDelimitedInts
+     *            can be <code>null</code>
+     * @return never <code>null</code> array
+     */
+    protected static int[] commaDelimitedListToIntArray(
+            String commaDelimitedInts) {
+        String[] intsAsStrings = commaDelimitedListToStringArray(commaDelimitedInts);
+        int[] ints = new int[intsAsStrings.length];
+        for (int i = 0; i < intsAsStrings.length; i++) {
+            String intAsString = intsAsStrings[i];
+            try {
+                ints[i] = Integer.parseInt(intAsString);
+            } catch (NumberFormatException e) {
+                throw new RuntimeException("Exception parsing number '" + i +
+                        "' (zero based) of comma delimited list '" +
+                        commaDelimitedInts + "'");
+            }
+        }
+        return ints;
+    }
+
+    /**
+     * Convert a given comma delimited list of strings into an array of String
+     * 
+     * @return array of patterns (non <code>null</code>)
+     */
+    protected static String[] commaDelimitedListToStringArray(
+            String commaDelimitedStrings) {
+        return (commaDelimitedStrings == null || commaDelimitedStrings.length() == 0) ? new String[0]
+                : commaSeparatedValuesPattern.split(commaDelimitedStrings);
+    }
+
+    /**
+     * Return <code>true</code> if the given <code>str</code> contains the given
+     * <code>searchStr</code>.
+     */
+    protected static boolean contains(String str, String searchStr) {
+        if (str == null || searchStr == null) {
+            return false;
+        }
+        return str.indexOf(searchStr) >= 0;
+    }
+
+    /**
+     * Convert an array of ints into a comma delimited string
+     */
+    protected static String intsToCommaDelimitedString(int[] ints) {
+        if (ints == null) {
+            return "";
+        }
+
+        StringBuilder result = new StringBuilder();
+
+        for (int i = 0; i < ints.length; i++) {
+            result.append(ints[i]);
+            if (i < (ints.length - 1)) {
+                result.append(", ");
+            }
+        }
+        return result.toString();
+    }
+
+    /**
+     * Return <code>true</code> if the given <code>str</code> is
+     * <code>null</code> or has a zero characters length.
+     */
+    protected static boolean isEmpty(String str) {
+        return str == null || str.length() == 0;
+    }
+
+    /**
+     * Return <code>true</code> if the given <code>str</code> has at least one
+     * character (can be a withespace).
+     */
+    protected static boolean isNotEmpty(String str) {
+        return !isEmpty(str);
+    }
+
+    /**
+     * Return <code>true</code> if the given <code>string</code> starts with the
+     * given <code>prefix</code> ignoring case.
+     * 
+     * @param string
+     *            can be <code>null</code>
+     * @param prefix
+     *            can be <code>null</code>
+     */
+    protected static boolean startsWithIgnoreCase(String string, String prefix) {
+        if (string == null || prefix == null) {
+            return string == null && prefix == null;
+        }
+        if (prefix.length() > string.length()) {
+            return false;
+        }
+
+        return string.regionMatches(true, 0, prefix, 0, prefix.length());
+    }
+
+    /**
+     * Return the subset of the given <code>str</code> that is before the first
+     * occurence of the given <code>separator</code>. Return <code>null</code>
+     * if the given <code>str</code> or the given <code>separator</code> is
+     * null. Return and empty string if the <code>separator</code> is empty.
+     * 
+     * @param str
+     *            can be <code>null</code>
+     * @param separator
+     *            can be <code>null</code>
+     */
+    protected static String substringBefore(String str, String separator) {
+        if (str == null || str.isEmpty() || separator == null) {
+            return null;
+        }
+
+        if (separator.isEmpty()) {
+            return "";
+        }
+
+        int separatorIndex = str.indexOf(separator);
+        if (separatorIndex == -1) {
+            return str;
+        }
+        return str.substring(0, separatorIndex);
+    }
+
+    /**
+     * Default Expires configuration.
+     */
+    private ExpiresConfiguration defaultExpiresConfiguration;
+
+    /**
+     * list of response status code for which the {@link ExpiresFilter} will not
+     * generate expiration headers.
+     */
+    private int[] excludedResponseStatusCodes = new int[] { HttpServletResponse.SC_NOT_MODIFIED };
+
+    /**
+     * Expires configuration by content type. Visible for test.
+     */
+    private Map<String, ExpiresConfiguration> expiresConfigurationByContentType = new LinkedHashMap<String, ExpiresConfiguration>();
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response,
+            FilterChain chain) throws IOException, ServletException {
+        if (request instanceof HttpServletRequest &&
+                response instanceof HttpServletResponse) {
+            HttpServletRequest httpRequest = (HttpServletRequest) request;
+            HttpServletResponse httpResponse = (HttpServletResponse) response;
+
+            if (response.isCommitted()) {
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString(
+                            "expiresFilter.responseAlreadyCommited",
+                            httpRequest.getRequestURL()));
+                }
+                chain.doFilter(request, response);
+            } else {
+                XHttpServletResponse xResponse = new XHttpServletResponse(
+                        httpRequest, httpResponse);
+                chain.doFilter(request, xResponse);
+                if (!xResponse.isWriteResponseBodyStarted()) {
+                    // Empty response, manually trigger
+                    // onBeforeWriteResponseBody()
+                    onBeforeWriteResponseBody(httpRequest, xResponse);
+                }
+            }
+        } else {
+            chain.doFilter(request, response);
+        }
+    }
+
+    public ExpiresConfiguration getDefaultExpiresConfiguration() {
+        return defaultExpiresConfiguration;
+    }
+
+    public String getExcludedResponseStatusCodes() {
+        return intsToCommaDelimitedString(excludedResponseStatusCodes);
+    }
+
+    public int[] getExcludedResponseStatusCodesAsInts() {
+        return excludedResponseStatusCodes;
+    }
+
+    /**
+     * <p>
+     * Returns the expiration date of the given {@link XHttpServletResponse} or
+     * <code>null</code> if no expiration date has been configured for the
+     * declared content type.
+     * </p>
+     * <p>
+     * <code>protected</code> for extension.
+     * </p>
+     * 
+     * @see HttpServletResponse#getContentType()
+     */
+    protected Date getExpirationDate(XHttpServletResponse response) {
+        String contentType = response.getContentType();
+
+        // lookup exact content-type match (e.g.
+        // "text/html; charset=iso-8859-1")
+        ExpiresConfiguration configuration = expiresConfigurationByContentType.get(contentType);
+        if (configuration != null) {
+            Date result = getExpirationDate(configuration, response);
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString(
+                        "expiresFilter.useMatchingConfiguration",
+                        configuration, contentType, contentType, result));
+            }
+            return result;
+        }
+
+        if (contains(contentType, ";")) {
+            // lookup content-type without charset match (e.g. "text/html")
+            String contentTypeWithoutCharset = substringBefore(contentType, ";").trim();
+            configuration = expiresConfigurationByContentType.get(contentTypeWithoutCharset);
+
+            if (configuration != null) {
+                Date result = getExpirationDate(configuration, response);
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString(
+                            "expiresFilter.useMatchingConfiguration",
+                            configuration, contentTypeWithoutCharset,
+                            contentType, result));
+                }
+                return result;
+            }
+        }
+
+        if (contains(contentType, "/")) {
+            // lookup major type match (e.g. "text")
+            String majorType = substringBefore(contentType, "/");
+            configuration = expiresConfigurationByContentType.get(majorType);
+            if (configuration != null) {
+                Date result = getExpirationDate(configuration, response);
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString(
+                            "expiresFilter.useMatchingConfiguration",
+                            configuration, majorType, contentType, result));
+                }
+                return result;
+            }
+        }
+
+        if (defaultExpiresConfiguration != null) {
+            Date result = getExpirationDate(defaultExpiresConfiguration,
+                    response);
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString("expiresFilter.useDefaultConfiguration",
+                        defaultExpiresConfiguration, contentType, result));
+            }
+            return result;
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug(sm.getString(
+                    "expiresFilter.noExpirationConfiguredForContentType",
+                    contentType));
+        }
+        return null;
+    }
+
+    /**
+     * <p>
+     * Returns the expiration date of the given {@link ExpiresConfiguration},
+     * {@link HttpServletRequest} and {@link XHttpServletResponse}.
+     * </p>
+     * <p>
+     * <code>protected</code> for extension.
+     * </p>
+     */
+    protected Date getExpirationDate(ExpiresConfiguration configuration,
+            XHttpServletResponse response) {
+        Calendar calendar;
+        switch (configuration.getStartingPoint()) {
+        case ACCESS_TIME:
+            calendar = Calendar.getInstance();
+            break;
+        case LAST_MODIFICATION_TIME:
+            if (response.isLastModifiedHeaderSet()) {
+                try {
+                    long lastModified = response.getLastModifiedHeader();
+                    calendar = Calendar.getInstance();
+                    calendar.setTimeInMillis(lastModified);
+                } catch (NumberFormatException e) {
+                    // default to now
+                    calendar = Calendar.getInstance();
+                }
+            } else {
+                // Last-Modified header not found, use now
+                calendar = Calendar.getInstance();
+            }
+            break;
+        default:
+            throw new IllegalStateException(sm.getString(
+                    "expiresFilter.unsupportedStartingPoint",
+                    configuration.getStartingPoint()));
+        }
+        for (Duration duration : configuration.getDurations()) {
+            calendar.add(duration.getUnit().getCalendardField(),
+                    duration.getAmount());
+        }
+
+        return calendar.getTime();
+    }
+
+    public Map<String, ExpiresConfiguration> getExpiresConfigurationByContentType() {
+        return expiresConfigurationByContentType;
+    }
+
+    @Override
+    protected Log getLogger() {
+        return log;
+    }
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+        for (Enumeration<String> names = filterConfig.getInitParameterNames(); names.hasMoreElements();) {
+            String name = names.nextElement();
+            String value = filterConfig.getInitParameter(name);
+
+            try {
+                if (name.startsWith(PARAMETER_EXPIRES_BY_TYPE)) {
+                    String contentType = name.substring(
+                            PARAMETER_EXPIRES_BY_TYPE.length()).trim();
+                    ExpiresConfiguration expiresConfiguration = parseExpiresConfiguration(value);
+                    this.expiresConfigurationByContentType.put(contentType,
+                            expiresConfiguration);
+                } else if (name.equalsIgnoreCase(PARAMETER_EXPIRES_DEFAULT)) {
+                    ExpiresConfiguration expiresConfiguration = parseExpiresConfiguration(value);
+                    this.defaultExpiresConfiguration = expiresConfiguration;
+                } else if (name.equalsIgnoreCase(PARAMETER_EXPIRES_EXCLUDED_RESPONSE_STATUS_CODES)) {
+                    this.excludedResponseStatusCodes = commaDelimitedListToIntArray(value);
+                } else {
+                    log.warn(sm.getString(
+                            "expiresFilter.unknownParameterIgnored", name,
+                            value));
+                }
+            } catch (RuntimeException e) {
+                throw new ServletException(sm.getString(
+                        "expiresFilter.exceptionProcessingParameter", name,
+                        value), e);
+            }
+        }
+
+        log.debug(sm.getString("expiresFilter.filterInitialized",
+                this.toString()));
+    }
+
+    /**
+     * 
+     * <p>
+     * <code>protected</code> for extension.
+     * </p>
+     */
+    protected boolean isEligibleToExpirationHeaderGeneration(
+            HttpServletRequest request, XHttpServletResponse response) {
+        boolean expirationHeaderHasBeenSet = response.containsHeader(HEADER_EXPIRES) ||
+                contains(response.getCacheControlHeader(), "max-age");
+        if (expirationHeaderHasBeenSet) {
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString(
+                        "expiresFilter.expirationHeaderAlreadyDefined",
+                        request.getRequestURI(),
+                        Integer.valueOf(response.getStatus()),
+                        response.getContentType()));
+            }
+            return false;
+        }
+
+        for (int skippedStatusCode : this.excludedResponseStatusCodes) {
+            if (response.getStatus() == skippedStatusCode) {
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString("expiresFilter.skippedStatusCode",
+                            request.getRequestURI(),
+                            Integer.valueOf(response.getStatus()),
+                            response.getContentType()));
+                }
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * <p>
+     * If no expiration header has been set by the servlet and an expiration has
+     * been defined in the {@link ExpiresFilter} configuration, sets the '
+     * <tt>Expires</tt>' header and the attribute '<tt>max-age</tt>' of the '
+     * <tt>Cache-Control</tt>' header.
+     * </p>
+     * <p>
+     * Must be called on the "Start Write Response Body" event.
+     * </p>
+     * <p>
+     * Invocations to <tt>Logger.debug(...)</tt> are guarded by
+     * {@link Log#isDebugEnabled()} because
+     * {@link HttpServletRequest#getRequestURI()} and
+     * {@link HttpServletResponse#getContentType()} costs <tt>String</tt>
+     * objects instantiations (as of Tomcat 7).
+     * </p>
+     */
+    public void onBeforeWriteResponseBody(HttpServletRequest request,
+            XHttpServletResponse response) {
+
+        if (!isEligibleToExpirationHeaderGeneration(request, response)) {
+            return;
+        }
+
+        Date expirationDate = getExpirationDate(response);
+        if (expirationDate == null) {
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString("expiresFilter.noExpirationConfigured",
+                        request.getRequestURI(),
+                        Integer.valueOf(response.getStatus()),
+                        response.getContentType()));
+            }
+        } else {
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString("expiresFilter.setExpirationDate",
+                        request.getRequestURI(),
+                        Integer.valueOf(response.getStatus()),
+                        response.getContentType(), expirationDate));
+            }
+
+            String maxAgeDirective = "max-age=" +
+                    ((expirationDate.getTime() - System.currentTimeMillis()) / 1000);
+
+            String cacheControlHeader = response.getCacheControlHeader();
+            String newCacheControlHeader = (cacheControlHeader == null) ? maxAgeDirective
+                    : cacheControlHeader + ", " + maxAgeDirective;
+            response.setHeader(HEADER_CACHE_CONTROL, newCacheControlHeader);
+            response.setDateHeader(HEADER_EXPIRES, expirationDate.getTime());
+        }
+
+    }
+
+    /**
+     * Parse configuration lines like '
+     * <tt>access plus 1 month 15 days 2 hours</tt>' or '
+     * <tt>modification 1 day 2 hours 5 seconds</tt>'
+     * 
+     * @param inputLine
+     */
+    protected ExpiresConfiguration parseExpiresConfiguration(String inputLine) {
+        String line = inputLine.trim();
+
+        StringTokenizer tokenizer = new StringTokenizer(line, " ");
+
+        String currentToken;
+
+        try {
+            currentToken = tokenizer.nextToken();
+        } catch (NoSuchElementException e) {
+            throw new IllegalStateException(sm.getString(
+                    "expiresFilter.startingPointNotFound", line));
+        }
+
+        StartingPoint startingPoint;
+        if ("access".equalsIgnoreCase(currentToken) ||
+                "now".equalsIgnoreCase(currentToken)) {
+            startingPoint = StartingPoint.ACCESS_TIME;
+        } else if ("modification".equalsIgnoreCase(currentToken)) {
+            startingPoint = StartingPoint.LAST_MODIFICATION_TIME;
+        } else if (!tokenizer.hasMoreTokens() &&
+                startsWithIgnoreCase(currentToken, "a")) {
+            startingPoint = StartingPoint.ACCESS_TIME;
+            // trick : convert duration configuration from old to new style
+            tokenizer = new StringTokenizer(currentToken.substring(1) +
+                    " seconds", " ");
+        } else if (!tokenizer.hasMoreTokens() &&
+                startsWithIgnoreCase(currentToken, "m")) {
+            startingPoint = StartingPoint.LAST_MODIFICATION_TIME;
+            // trick : convert duration configuration from old to new style
+            tokenizer = new StringTokenizer(currentToken.substring(1) +
+                    " seconds", " ");
+        } else {
+            throw new IllegalStateException(sm.getString(
+                    "expiresFilter.startingPointInvalid", currentToken, line));
+        }
+
+        try {
+            currentToken = tokenizer.nextToken();
+        } catch (NoSuchElementException e) {
+            throw new IllegalStateException(sm.getString(
+                    "Duration not found in directive '{}'", line));
+        }
+
+        if ("plus".equalsIgnoreCase(currentToken)) {
+            // skip
+            try {
+                currentToken = tokenizer.nextToken();
+            } catch (NoSuchElementException e) {
+                throw new IllegalStateException(sm.getString(
+                        "Duration not found in directive '{}'", line));
+            }
+        }
+
+        List<Duration> durations = new ArrayList<Duration>();
+
+        while (currentToken != null) {
+            int amount;
+            try {
+                amount = Integer.parseInt(currentToken);
+            } catch (NumberFormatException e) {
+                throw new IllegalStateException(sm.getString(
+                        "Invalid duration (number) '{}' in directive '{}'",
+                        currentToken, line));
+            }
+
+            try {
+                currentToken = tokenizer.nextToken();
+            } catch (NoSuchElementException e) {
+                throw new IllegalStateException(
+                        sm.getString(
+                                "Duration unit not found after amount {} in directive '{}'",
+                                Integer.valueOf(amount), line));
+            }
+            DurationUnit durationUnit;
+            if ("years".equalsIgnoreCase(currentToken)) {
+                durationUnit = DurationUnit.YEAR;
+            } else if ("month".equalsIgnoreCase(currentToken) ||
+                    "months".equalsIgnoreCase(currentToken)) {
+                durationUnit = DurationUnit.MONTH;
+            } else if ("week".equalsIgnoreCase(currentToken) ||
+                    "weeks".equalsIgnoreCase(currentToken)) {
+                durationUnit = DurationUnit.WEEK;
+            } else if ("day".equalsIgnoreCase(currentToken) ||
+                    "days".equalsIgnoreCase(currentToken)) {
+                durationUnit = DurationUnit.DAY;
+            } else if ("hour".equalsIgnoreCase(currentToken) ||
+                    "hours".equalsIgnoreCase(currentToken)) {
+                durationUnit = DurationUnit.HOUR;
+            } else if ("minute".equalsIgnoreCase(currentToken) ||
+                    "minutes".equalsIgnoreCase(currentToken)) {
+                durationUnit = DurationUnit.MINUTE;
+            } else if ("second".equalsIgnoreCase(currentToken) ||
+                    "seconds".equalsIgnoreCase(currentToken)) {
+                durationUnit = DurationUnit.SECOND;
+            } else {
+                throw new IllegalStateException(
+                        sm.getString(
+                                "Invalid duration unit (years|months|weeks|days|hours|minutes|seconds) '{}' in directive '{}'",
+                                currentToken, line));
+            }
+
+            Duration duration = new Duration(amount, durationUnit);
+            durations.add(duration);
+
+            if (tokenizer.hasMoreTokens()) {
+                currentToken = tokenizer.nextToken();
+            } else {
+                currentToken = null;
+            }
+        }
+
+        return new ExpiresConfiguration(startingPoint, durations);
+    }
+
+    public void setDefaultExpiresConfiguration(
+            ExpiresConfiguration defaultExpiresConfiguration) {
+        this.defaultExpiresConfiguration = defaultExpiresConfiguration;
+    }
+
+    public void setExcludedResponseStatusCodes(int[] excludedResponseStatusCodes) {
+        this.excludedResponseStatusCodes = excludedResponseStatusCodes;
+    }
+
+    public void setExpiresConfigurationByContentType(
+            Map<String, ExpiresConfiguration> expiresConfigurationByContentType) {
+        this.expiresConfigurationByContentType = expiresConfigurationByContentType;
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "[excludedResponseStatusCode=[" +
+                intsToCommaDelimitedString(this.excludedResponseStatusCodes) +
+                "], default=" + this.defaultExpiresConfiguration + ", byType=" +
+                this.expiresConfigurationByContentType + "]";
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/filters/FilterBase.java b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/FilterBase.java
new file mode 100644
index 0000000..7bb5206
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/FilterBase.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.filters;
+
+import java.util.Enumeration;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+
+import org.apache.juli.logging.Log;
+import org.apache.tomcat.util.IntrospectionUtils;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Base class for filters that provides generic initialisation and a simple
+ * no-op destruction. 
+ * 
+ * @author xxd
+ *
+ */
+public abstract class FilterBase implements Filter {
+    
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    protected abstract Log getLogger();
+    
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+        Enumeration<String> paramNames = filterConfig.getInitParameterNames();
+        while (paramNames.hasMoreElements()) {
+            String paramName = paramNames.nextElement();
+            if (!IntrospectionUtils.setProperty(this, paramName,
+                    filterConfig.getInitParameter(paramName))) {
+                getLogger().warn(sm.getString("filterbase.noSuchProperty",
+                        paramName, this.getClass().getName()));
+            }
+        }    
+    }
+
+    @Override
+    public void destroy() {
+        // NOOP
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/filters/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/LocalStrings.properties
new file mode 100644
index 0000000..213dbb8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/LocalStrings.properties
@@ -0,0 +1,36 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+addDefaultCharset.unsupportedCharset=Specified character set [{0}] is not supported
+csrfPrevention.invalidRandomClass=Unable to create Random source using class [{0}]
+filterbase.noSuchProperty=The property "{0}" is not defined for filters of type "{1}"
+
+http.403=Access to the specified resource ({0}) has been forbidden.
+
+expiresFilter.noExpirationConfigured=Request "{0}" with response status "{1}" content-type "{2}", no expiration configured
+expiresFilter.setExpirationDate=Request "{0}" with response status "{1}" content-type "{2}", set expiration date {3}
+expiresFilter.startingPointNotFound=Starting point (access|now|modification|a<seconds>|m<seconds>) not found in directive "{0}"
+expiresFilter.startingPointInvalid=Invalid starting point (access|now|modification|a<seconds>|m<seconds>) "{0}" in directive "{1}"
+expiresFilter.responseAlreadyCommited=Request "{0}", can not apply ExpiresFilter on already committed response.
+expiresFilter.noExpirationConfiguredForContentType=No Expires configuration found for content-type "{0}"
+expiresFilter.useMatchingConfiguration=Use {0} matching "{1}" for content-type "{2}" returns {3}
+expiresFilter.useDefaultConfiguration=Use default {0} for content-type "{1}" returns {2}
+expiresFilter.unsupportedStartingPoint=Unsupported startingPoint "{0}"
+expiresFilter.unknownParameterIgnored=Unknown parameter "{0}" with value "{1}" is ignored !
+expiresFilter.exceptionProcessingParameter=Exception processing configuration parameter "{0}":"{1}"
+expiresFilter.filterInitialized=Filter initialized with configuration {0}
+expiresFilter.expirationHeaderAlreadyDefined=Request "{0}" with response status "{1}" content-type "{2}", expiration header already defined
+expiresFilter.skippedStatusCode=Request "{0}" with response status "{1}" content-type "{1}", skip expiration header generation for given status
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/filters/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/LocalStrings_es.properties
new file mode 100644
index 0000000..ffffd48
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/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.
+
+http.403=El acceso al recurso especificado ({0}) ha sido prohibido.
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/filters/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/LocalStrings_fr.properties
new file mode 100644
index 0000000..a2f474a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/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.
+
+http.403=L''acc\u00e8s \u00e0 la ressource demand\u00e9e ({0}) a \u00e9t\u00e9 interdit.
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/filters/RemoteAddrFilter.java b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/RemoteAddrFilter.java
new file mode 100644
index 0000000..7246900
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/RemoteAddrFilter.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 org.apache.catalina.filters;
+
+
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.apache.catalina.comet.CometEvent;
+import org.apache.catalina.comet.CometFilterChain;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+
+/**
+ * Concrete implementation of <code>RequestFilter</code> that filters
+ * based on the string representation of the remote client's IP address.
+ *
+ * @author Craig R. McClanahan
+ * 
+ */
+
+public final class RemoteAddrFilter
+    extends RequestFilter {
+
+    // ----------------------------------------------------- Instance Variables
+    private static final Log log = LogFactory.getLog(RemoteAddrFilter.class);
+
+
+    // ------------------------------------------------------------- Properties
+
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Extract the desired request property, and pass it (along with the
+     * specified request and response objects and associated filter chain) to
+     * the protected <code>process()</code> method to perform the actual
+     * filtering.
+     *
+     * @param request  The servlet request to be processed
+     * @param response The servlet response to be created
+     * @param chain    The filter chain for this request
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response,
+            FilterChain chain) throws IOException, ServletException {
+        
+        process(request.getRemoteAddr(), request, response, chain);
+
+    }
+
+    /**
+     * Extract the desired request property, and pass it (along with the comet
+     * event and filter chain) to the protected <code>process()</code> method
+     * to perform the actual filtering.
+     *
+     * @param event The comet event to be processed
+     * @param chain The filter chain for this event
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    @Override
+    public void doFilterEvent(CometEvent event, CometFilterChain chain)
+            throws IOException, ServletException {
+        processCometEvent(event.getHttpServletRequest().getRemoteHost(),
+                event, chain);        
+    }
+
+    @Override
+    protected Log getLogger() {
+        return log;
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/filters/RemoteHostFilter.java b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/RemoteHostFilter.java
new file mode 100644
index 0000000..1b78320
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/RemoteHostFilter.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.catalina.filters;
+
+
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.apache.catalina.comet.CometEvent;
+import org.apache.catalina.comet.CometFilterChain;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+
+/**
+ * Concrete implementation of <code>RequestFilter</code> that filters
+ * based on the remote client's host name.
+ *
+ * @author Craig R. McClanahan
+ * 
+ */
+
+public final class RemoteHostFilter
+    extends RequestFilter {
+
+    
+    // ----------------------------------------------------- Instance Variables
+    private static final Log log = LogFactory.getLog(RemoteHostFilter.class);
+
+
+    // ------------------------------------------------------------- Properties
+
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Extract the desired request property, and pass it (along with the
+     * specified request and response objects and associated filter chain) to
+     * the protected <code>process()</code> method to perform the actual
+     * filtering.
+     *
+     * @param request  The servlet request to be processed
+     * @param response The servlet response to be created
+     * @param chain    The filter chain for this request
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response,
+            FilterChain chain) throws IOException, ServletException {
+        
+        process(request.getRemoteHost(), request, response, chain);
+
+    }
+    
+    /**
+     * Extract the desired request property, and pass it (along with the comet
+     * event and filter chain) to the protected <code>process()</code> method
+     * to perform the actual filtering.
+     *
+     * @param event The comet event to be processed
+     * @param chain The filter chain for this event
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    @Override
+    public void doFilterEvent(CometEvent event, CometFilterChain chain)
+            throws IOException, ServletException {
+        processCometEvent(event.getHttpServletRequest().getRemoteHost(),
+                event, chain);        
+    }
+
+    @Override
+    protected Log getLogger() {
+        return log;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/filters/RemoteIpFilter.java b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/RemoteIpFilter.java
new file mode 100644
index 0000000..da2a1be
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/RemoteIpFilter.java
@@ -0,0 +1,1053 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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.filters;
+
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+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.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.AccessLog;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * <p>
+ * Servlet filter to integrate "X-Forwarded-For" and "X-Forwarded-Proto" HTTP headers.
+ * </p>
+ * <p>
+ * Most of the design of this Servlet Filter is a port of <a
+ * href="http://httpd.apache.org/docs/trunk/mod/mod_remoteip.html">mod_remoteip</a>, this servlet filter replaces the apparent client remote
+ * IP address and hostname for the request with the IP address list presented by a proxy or a load balancer via a request headers (e.g.
+ * "X-Forwarded-For").
+ * </p>
+ * <p>
+ * Another feature of this servlet filter is to replace the apparent scheme (http/https) and server port with the scheme presented by a
+ * proxy or a load balancer via a request header (e.g. "X-Forwarded-Proto").
+ * </p>
+ * <p>
+ * This servlet filter proceeds as follows:
+ * </p>
+ * <p>
+ * If the incoming <code>request.getRemoteAddr()</code> matches the servlet filter's list of internal proxies :
+ * <ul>
+ * <li>Loop on the comma delimited list of IPs and hostnames passed by the preceding load balancer or proxy in the given request's Http
+ * header named <code>$remoteIpHeader</code> (default value <code>x-forwarded-for</code>). Values are processed in right-to-left order.</li>
+ * <li>For each ip/host of the list:
+ * <ul>
+ * <li>if it matches the internal proxies list, the ip/host is swallowed</li>
+ * <li>if it matches the trusted proxies list, the ip/host is added to the created proxies header</li>
+ * <li>otherwise, the ip/host is declared to be the remote ip and looping is stopped.</li>
+ * </ul>
+ * </li>
+ * <li>If the request http header named <code>$protocolHeader</code> (e.g. <code>x-forwarded-for</code>) equals to the value of
+ * <code>protocolHeaderHttpsValue</code> configuration parameter (default <code>https</code>) then <code>request.isSecure = true</code>,
+ * <code>request.scheme = https</code> and <code>request.serverPort = 443</code>. Note that 443 can be overwritten with the
+ * <code>$httpsServerPort</code> configuration parameter.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * <strong>Configuration parameters:</strong>
+ * <table border="1">
+ * <tr>
+ * <th>XForwardedFilter property</th>
+ * <th>Description</th>
+ * <th>Equivalent mod_remoteip directive</th>
+ * <th>Format</th>
+ * <th>Default Value</th>
+ * </tr>
+ * <tr>
+ * <td>remoteIpHeader</td>
+ * <td>Name of the Http Header read by this servlet filter that holds the list of traversed IP addresses starting from the requesting client
+ * </td>
+ * <td>RemoteIPHeader</td>
+ * <td>Compliant http header name</td>
+ * <td>x-forwarded-for</td>
+ * </tr>
+ * <tr>
+ * <td>internalProxies</td>
+ * <td>Regular expression that matches the IP addresses of internal proxies.
+ * If they appear in the <code>remoteIpHeader</code> value, they will be
+ * trusted and will not appear
+ * in the <code>proxiesHeader</code> value</td>
+ * <td>RemoteIPInternalProxy</td>
+ * <td>Regular expression (in the syntax supported by
+ * {@link java.util.regex.Pattern java.util.regex})</td>
+ * <td>10\.\d{1,3}\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}|169\.254\.\d{1,3}\.\d{1,3}|127\.\d{1,3}\.\d{1,3}\.\d{1,3} <br/>
+ * By default, 10/8, 192.168/16, 169.254/16 and 127/8 are allowed ; 172.16/12 has not been enabled by default because it is complex to
+ * describe with regular expressions</td>
+ * </tr>
+ * </tr>
+ * <tr>
+ * <td>proxiesHeader</td>
+ * <td>Name of the http header created by this servlet filter to hold the list of proxies that have been processed in the incoming
+ * <code>remoteIpHeader</code></td>
+ * <td>RemoteIPProxiesHeader</td>
+ * <td>Compliant http header name</td>
+ * <td>x-forwarded-by</td>
+ * </tr>
+ * <tr>
+ * <td>trustedProxies</td>
+ * <td>Regular expression that matches the IP addresses of trusted proxies.
+ * If they appear in the <code>remoteIpHeader</code> value, they will be
+ * trusted and will appear in the <code>proxiesHeader</code> value</td>
+ * <td>RemoteIPTrustedProxy</td>
+ * <td>Regular expression (in the syntax supported by
+ * {@link java.util.regex.Pattern java.util.regex})</td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>protocolHeader</td>
+ * <td>Name of the http header read by this servlet filter that holds the flag that this request</td>
+ * <td>N/A</td>
+ * <td>Compliant http header name like <code>X-Forwarded-Proto</code>, <code>X-Forwarded-Ssl</code> or <code>Front-End-Https</code></td>
+ * <td><code>null</code></td>
+ * </tr>
+ * <tr>
+ * <td>protocolHeaderHttpsValue</td>
+ * <td>Value of the <code>protocolHeader</code> to indicate that it is an Https request</td>
+ * <td>N/A</td>
+ * <td>String like <code>https</code> or <code>ON</code></td>
+ * <td><code>https</code></td>
+ * </tr>
+ * <tr>
+ * <td>httpServerPort</td>
+ * <td>Value returned by {@link ServletRequest#getServerPort()} when the <code>protocolHeader</code> indicates <code>http</code> protocol</td>
+ * <td>N/A</td>
+ * <td>integer</td>
+ * <td>80</td>
+ * </tr>
+ * <tr>
+ * <td>httpsServerPort</td>
+ * <td>Value returned by {@link ServletRequest#getServerPort()} when the <code>protocolHeader</code> indicates <code>https</code> protocol</td>
+ * <td>N/A</td>
+ * <td>integer</td>
+ * <td>443</td>
+ * </tr>
+ * </table>
+ * </p>
+ * <p>
+ * <p>
+ * <strong>Regular expression vs. IP address blocks:</strong> <code>mod_remoteip</code> allows to use address blocks (e.g.
+ * <code>192.168/16</code>) to configure <code>RemoteIPInternalProxy</code> and <code>RemoteIPTrustedProxy</code> ; as the JVM doesn't have a
+ * library similar to <a
+ * href="http://apr.apache.org/docs/apr/1.3/group__apr__network__io.html#gb74d21b8898b7c40bf7fd07ad3eb993d">apr_ipsubnet_test</a>, we rely on 
+ * regular expressions.
+ * </p>
+ * <hr/>
+ * <p>
+ * <strong>Sample with internal proxies</strong>
+ * </p>
+ * <p>
+ * XForwardedFilter configuration:
+ * </p>
+ * <code><pre>
+ * &lt;filter&gt;
+ *    &lt;filter-name&gt;RemoteIpFilter&lt;/filter-name&gt;
+ *    &lt;filter-class&gt;org.apache.catalina.filters.RemoteIpFilter&lt;/filter-class&gt;
+ *    &lt;init-param&gt;
+ *       &lt;param-name&gt;internalProxies&lt;/param-name&gt;
+ *       &lt;param-value&gt;192\.168\.0\.10|192\.168\.0\.11&lt;/param-value&gt;
+ *    &lt;/init-param&gt;
+ *    &lt;init-param&gt;
+ *       &lt;param-name&gt;remoteIpHeader&lt;/param-name&gt;
+ *       &lt;param-value&gt;x-forwarded-for&lt;/param-value&gt;
+ *    &lt;/init-param&gt;
+ *    &lt;init-param&gt;
+ *       &lt;param-name&gt;remoteIpProxiesHeader&lt;/param-name&gt;
+ *       &lt;param-value&gt;x-forwarded-by&lt;/param-value&gt;
+ *    &lt;/init-param&gt;
+ *    &lt;init-param&gt;
+ *       &lt;param-name&gt;protocolHeader&lt;/param-name&gt;
+ *       &lt;param-value&gt;x-forwarded-proto&lt;/param-value&gt;
+ *    &lt;/init-param&gt;
+ * &lt;/filter&gt;
+ * 
+ * &lt;filter-mapping&gt;
+ *    &lt;filter-name&gt;RemoteIpFilter&lt;/filter-name&gt;
+ *    &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
+ *    &lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
+ * &lt;/filter-mapping&gt;</pre></code>
+ * <p>
+ * Request values:
+ * <table border="1">
+ * <tr>
+ * <th>property</th>
+ * <th>Value Before RemoteIpFilter</th>
+ * <th>Value After RemoteIpFilter</th>
+ * </tr>
+ * <tr>
+ * <td>request.remoteAddr</td>
+ * <td>192.168.0.10</td>
+ * <td>140.211.11.130</td>
+ * </tr>
+ * <tr>
+ * <td>request.header['x-forwarded-for']</td>
+ * <td>140.211.11.130, 192.168.0.10</td>
+ * <td>null</td>
+ * </tr>
+ * <tr>
+ * <td>request.header['x-forwarded-by']</td>
+ * <td>null</td>
+ * <td>null</td>
+ * </tr>
+ * <tr>
+ * <td>request.header['x-forwarded-proto']</td>
+ * <td>https</td>
+ * <td>https</td>
+ * </tr>
+ * <tr>
+ * <td>request.scheme</td>
+ * <td>http</td>
+ * <td>https</td>
+ * </tr>
+ * <tr>
+ * <td>request.secure</td>
+ * <td>false</td>
+ * <td>true</td>
+ * </tr>
+ * <tr>
+ * <td>request.serverPort</td>
+ * <td>80</td>
+ * <td>443</td>
+ * </tr>
+ * </table>
+ * Note : <code>x-forwarded-by</code> header is null because only internal proxies as been traversed by the request.
+ * <code>x-forwarded-by</code> is null because all the proxies are trusted or internal.
+ * </p>
+ * <hr/>
+ * <p>
+ * <strong>Sample with trusted proxies</strong>
+ * </p>
+ * <p>
+ * RemoteIpFilter configuration:
+ * </p>
+ * <code><pre>
+ * &lt;filter&gt;
+ *    &lt;filter-name&gt;RemoteIpFilter&lt;/filter-name&gt;
+ *    &lt;filter-class&gt;org.apache.catalina.filters.RemoteIpFilter&lt;/filter-class&gt;
+ *    &lt;init-param&gt;
+ *       &lt;param-name&gt;internalProxies&lt;/param-name&gt;
+ *       &lt;param-value&gt;192\.168\.0\.10|192\.168\.0\.11&lt;/param-value&gt;
+ *    &lt;/init-param&gt;
+ *    &lt;init-param&gt;
+ *       &lt;param-name&gt;remoteIpHeader&lt;/param-name&gt;
+ *       &lt;param-value&gt;x-forwarded-for&lt;/param-value&gt;
+ *    &lt;/init-param&gt;
+ *    &lt;init-param&gt;
+ *       &lt;param-name&gt;remoteIpProxiesHeader&lt;/param-name&gt;
+ *       &lt;param-value&gt;x-forwarded-by&lt;/param-value&gt;
+ *    &lt;/init-param&gt;
+ *    &lt;init-param&gt;
+ *       &lt;param-name&gt;trustedProxies&lt;/param-name&gt;
+ *       &lt;param-value&gt;proxy1|proxy2&lt;/param-value&gt;
+ *    &lt;/init-param&gt;
+ * &lt;/filter&gt;
+ * 
+ * &lt;filter-mapping&gt;
+ *    &lt;filter-name&gt;RemoteIpFilter&lt;/filter-name&gt;
+ *    &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
+ *    &lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
+ * &lt;/filter-mapping&gt;</pre></code>
+ * <p>
+ * Request values:
+ * <table border="1">
+ * <tr>
+ * <th>property</th>
+ * <th>Value Before RemoteIpFilter</th>
+ * <th>Value After RemoteIpFilter</th>
+ * </tr>
+ * <tr>
+ * <td>request.remoteAddr</td>
+ * <td>192.168.0.10</td>
+ * <td>140.211.11.130</td>
+ * </tr>
+ * <tr>
+ * <td>request.header['x-forwarded-for']</td>
+ * <td>140.211.11.130, proxy1, proxy2</td>
+ * <td>null</td>
+ * </tr>
+ * <tr>
+ * <td>request.header['x-forwarded-by']</td>
+ * <td>null</td>
+ * <td>proxy1, proxy2</td>
+ * </tr>
+ * </table>
+ * Note : <code>proxy1</code> and <code>proxy2</code> are both trusted proxies that come in <code>x-forwarded-for</code> header, they both
+ * are migrated in <code>x-forwarded-by</code> header. <code>x-forwarded-by</code> is null because all the proxies are trusted or internal.
+ * </p>
+ * <hr/>
+ * <p>
+ * <strong>Sample with internal and trusted proxies</strong>
+ * </p>
+ * <p>
+ * RemoteIpFilter configuration:
+ * </p>
+ * <code><pre>
+ * &lt;filter&gt;
+ *    &lt;filter-name&gt;RemoteIpFilter&lt;/filter-name&gt;
+ *    &lt;filter-class&gt;org.apache.catalina.filters.RemoteIpFilter&lt;/filter-class&gt;
+ *    &lt;init-param&gt;
+ *       &lt;param-name&gt;internalProxies&lt;/param-name&gt;
+ *       &lt;param-value&gt;192\.168\.0\.10|192\.168\.0\.11&lt;/param-value&gt;
+ *    &lt;/init-param&gt;
+ *    &lt;init-param&gt;
+ *       &lt;param-name&gt;remoteIpHeader&lt;/param-name&gt;
+ *       &lt;param-value&gt;x-forwarded-for&lt;/param-value&gt;
+ *    &lt;/init-param&gt;
+ *    &lt;init-param&gt;
+ *       &lt;param-name&gt;remoteIpProxiesHeader&lt;/param-name&gt;
+ *       &lt;param-value&gt;x-forwarded-by&lt;/param-value&gt;
+ *    &lt;/init-param&gt;
+ *    &lt;init-param&gt;
+ *       &lt;param-name&gt;trustedProxies&lt;/param-name&gt;
+ *       &lt;param-value&gt;proxy1|proxy2&lt;/param-value&gt;
+ *    &lt;/init-param&gt;
+ * &lt;/filter&gt;
+ * 
+ * &lt;filter-mapping&gt;
+ *    &lt;filter-name&gt;RemoteIpFilter&lt;/filter-name&gt;
+ *    &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
+ *    &lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
+ * &lt;/filter-mapping&gt;</pre></code>
+ * <p>
+ * Request values:
+ * <table border="1">
+ * <tr>
+ * <th>property</th>
+ * <th>Value Before RemoteIpFilter</th>
+ * <th>Value After RemoteIpFilter</th>
+ * </tr>
+ * <tr>
+ * <td>request.remoteAddr</td>
+ * <td>192.168.0.10</td>
+ * <td>140.211.11.130</td>
+ * </tr>
+ * <tr>
+ * <td>request.header['x-forwarded-for']</td>
+ * <td>140.211.11.130, proxy1, proxy2, 192.168.0.10</td>
+ * <td>null</td>
+ * </tr>
+ * <tr>
+ * <td>request.header['x-forwarded-by']</td>
+ * <td>null</td>
+ * <td>proxy1, proxy2</td>
+ * </tr>
+ * </table>
+ * Note : <code>proxy1</code> and <code>proxy2</code> are both trusted proxies that come in <code>x-forwarded-for</code> header, they both
+ * are migrated in <code>x-forwarded-by</code> header. As <code>192.168.0.10</code> is an internal proxy, it does not appear in
+ * <code>x-forwarded-by</code>. <code>x-forwarded-by</code> is null because all the proxies are trusted or internal.
+ * </p>
+ * <hr/>
+ * <p>
+ * <strong>Sample with an untrusted proxy</strong>
+ * </p>
+ * <p>
+ * RemoteIpFilter configuration:
+ * </p>
+ * <code><pre>
+ * &lt;filter&gt;
+ *    &lt;filter-name&gt;RemoteIpFilter&lt;/filter-name&gt;
+ *    &lt;filter-class&gt;org.apache.catalina.filters.RemoteIpFilter&lt;/filter-class&gt;
+ *    &lt;init-param&gt;
+ *       &lt;param-name&gt;internalProxies&lt;/param-name&gt;
+ *       &lt;param-value&gt;192\.168\.0\.10|192\.168\.0\.11&lt;/param-value&gt;
+ *    &lt;/init-param&gt;
+ *    &lt;init-param&gt;
+ *       &lt;param-name&gt;remoteIpHeader&lt;/param-name&gt;
+ *       &lt;param-value&gt;x-forwarded-for&lt;/param-value&gt;
+ *    &lt;/init-param&gt;
+ *    &lt;init-param&gt;
+ *       &lt;param-name&gt;remoteIpProxiesHeader&lt;/param-name&gt;
+ *       &lt;param-value&gt;x-forwarded-by&lt;/param-value&gt;
+ *    &lt;/init-param&gt;
+ *    &lt;init-param&gt;
+ *       &lt;param-name&gt;trustedProxies&lt;/param-name&gt;
+ *       &lt;param-value&gt;proxy1|proxy2&lt;/param-value&gt;
+ *    &lt;/init-param&gt;
+ * &lt;/filter&gt;
+ * 
+ * &lt;filter-mapping&gt;
+ *    &lt;filter-name&gt;RemoteIpFilter&lt;/filter-name&gt;
+ *    &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
+ *    &lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
+ * &lt;/filter-mapping&gt;</pre></code>
+ * <p>
+ * Request values:
+ * <table border="1">
+ * <tr>
+ * <th>property</th>
+ * <th>Value Before RemoteIpFilter</th>
+ * <th>Value After RemoteIpFilter</th>
+ * </tr>
+ * <tr>
+ * <td>request.remoteAddr</td>
+ * <td>192.168.0.10</td>
+ * <td>untrusted-proxy</td>
+ * </tr>
+ * <tr>
+ * <td>request.header['x-forwarded-for']</td>
+ * <td>140.211.11.130, untrusted-proxy, proxy1</td>
+ * <td>140.211.11.130</td>
+ * </tr>
+ * <tr>
+ * <td>request.header['x-forwarded-by']</td>
+ * <td>null</td>
+ * <td>proxy1</td>
+ * </tr>
+ * </table>
+ * Note : <code>x-forwarded-by</code> holds the trusted proxy <code>proxy1</code>. <code>x-forwarded-by</code> holds
+ * <code>140.211.11.130</code> because <code>untrusted-proxy</code> is not trusted and thus, we can not trust that
+ * <code>untrusted-proxy</code> is the actual remote ip. <code>request.remoteAddr</code> is <code>untrusted-proxy</code> that is an IP
+ * verified by <code>proxy1</code>.
+ * </p>
+ * <hr/>
+ */
+public class RemoteIpFilter implements Filter {
+    public static class XForwardedRequest extends HttpServletRequestWrapper {
+        
+        static final ThreadLocal<SimpleDateFormat[]> threadLocalDateFormats = new ThreadLocal<SimpleDateFormat[]>() {
+            @Override
+            protected SimpleDateFormat[] initialValue() {
+                return new SimpleDateFormat[] {
+                    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 Map<String, List<String>> headers;
+        
+        protected String remoteAddr;
+        
+        protected String remoteHost;
+        
+        protected String scheme;
+        
+        protected boolean secure;
+        
+        protected int serverPort;
+        
+        public XForwardedRequest(HttpServletRequest request) {
+            super(request);
+            this.remoteAddr = request.getRemoteAddr();
+            this.remoteHost = request.getRemoteHost();
+            this.scheme = request.getScheme();
+            this.secure = request.isSecure();
+            this.serverPort = request.getServerPort();
+            
+            headers = new HashMap<String, List<String>>();
+            for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements();) {
+                String header = headerNames.nextElement();
+                headers.put(header, Collections.list(request.getHeaders(header)));
+            }
+        }
+        
+        @Override
+        public long getDateHeader(String name) {
+            String value = getHeader(name);
+            if (value == null) {
+                return -1;
+            }
+            DateFormat[] dateFormats = threadLocalDateFormats.get();
+            Date date = null;
+            for (int i = 0; ((i < dateFormats.length) && (date == null)); i++) {
+                DateFormat dateFormat = dateFormats[i];
+                try {
+                    date = dateFormat.parse(value);
+                } catch (Exception ParseException) {
+                    // Ignore
+                }
+            }
+            if (date == null) {
+                throw new IllegalArgumentException(value);
+            }
+            return date.getTime();
+        }
+        
+        @Override
+        public String getHeader(String name) {
+            Map.Entry<String, List<String>> header = getHeaderEntry(name);
+            if (header == null || header.getValue() == null || header.getValue().isEmpty()) {
+                return null;
+            }
+            return header.getValue().get(0);
+        }
+        
+        protected Map.Entry<String, List<String>> getHeaderEntry(String name) {
+            for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
+                if (entry.getKey().equalsIgnoreCase(name)) {
+                    return entry;
+                }
+            }
+            return null;
+        }
+        
+        @Override
+        public Enumeration<String> getHeaderNames() {
+            return Collections.enumeration(headers.keySet());
+        }
+        
+        @Override
+        public Enumeration<String> getHeaders(String name) {
+            Map.Entry<String, List<String>> header = getHeaderEntry(name);
+            if (header == null || header.getValue() == null) {
+                return Collections.enumeration(Collections.<String>emptyList());
+            }
+            return Collections.enumeration(header.getValue());
+        }
+        
+        @Override
+        public int getIntHeader(String name) {
+            String value = getHeader(name);
+            if (value == null) {
+                return -1;
+            }
+            return Integer.parseInt(value);
+        }
+        
+        @Override
+        public String getRemoteAddr() {
+            return this.remoteAddr;
+        }
+        
+        @Override
+        public String getRemoteHost() {
+            return this.remoteHost;
+        }
+        
+        @Override
+        public String getScheme() {
+            return scheme;
+        }
+        
+        @Override
+        public int getServerPort() {
+            return serverPort;
+        }
+        
+        @Override
+        public boolean isSecure() {
+            return secure;
+        }
+        
+        public void removeHeader(String name) {
+            Map.Entry<String, List<String>> header = getHeaderEntry(name);
+            if (header != null) {
+                headers.remove(header.getKey());
+            }
+        }
+        
+        public void setHeader(String name, String value) {
+            List<String> values = Arrays.asList(value);
+            Map.Entry<String, List<String>> header = getHeaderEntry(name);
+            if (header == null) {
+                headers.put(name, values);
+            } else {
+                header.setValue(values);
+            }
+            
+        }
+        
+        public void setRemoteAddr(String remoteAddr) {
+            this.remoteAddr = remoteAddr;
+        }
+        
+        public void setRemoteHost(String remoteHost) {
+            this.remoteHost = remoteHost;
+        }
+        
+        public void setScheme(String scheme) {
+            this.scheme = scheme;
+        }
+        
+        public void setSecure(boolean secure) {
+            this.secure = secure;
+        }
+        
+        public void setServerPort(int serverPort) {
+            this.serverPort = serverPort;
+        }
+    }
+    
+    /**
+     * {@link Pattern} for a comma delimited string that support whitespace characters
+     */
+    private static final Pattern commaSeparatedValuesPattern = Pattern.compile("\\s*,\\s*");
+    
+    protected static final String HTTP_SERVER_PORT_PARAMETER = "httpServerPort";
+
+    protected static final String HTTPS_SERVER_PORT_PARAMETER = "httpsServerPort";
+    
+    protected static final String INTERNAL_PROXIES_PARAMETER = "internalProxies";
+    
+    /**
+     * Logger
+     */
+    private static final Log log = LogFactory.getLog(RemoteIpFilter.class);
+    
+    protected static final String PROTOCOL_HEADER_PARAMETER = "protocolHeader";
+    
+    protected static final String PROTOCOL_HEADER_HTTPS_VALUE_PARAMETER = "protocolHeaderHttpsValue";
+    
+    protected static final String PROXIES_HEADER_PARAMETER = "proxiesHeader";
+    
+    protected static final String REMOTE_IP_HEADER_PARAMETER = "remoteIpHeader";
+    
+    protected static final String TRUSTED_PROXIES_PARAMETER = "trustedProxies";
+    
+    /**
+     * Convert a given comma delimited list of regular expressions into an array of String
+     * 
+     * @return array of patterns (non <code>null</code>)
+     */
+    protected static String[] commaDelimitedListToStringArray(String commaDelimitedStrings) {
+        return (commaDelimitedStrings == null || commaDelimitedStrings.length() == 0) ? new String[0] : commaSeparatedValuesPattern
+            .split(commaDelimitedStrings);
+    }
+    
+    /**
+     * Convert an array of strings in a comma delimited string
+     */
+    protected static String listToCommaDelimitedString(List<String> stringList) {
+        if (stringList == null) {
+            return "";
+        }
+        StringBuilder result = new StringBuilder();
+        for (Iterator<String> it = stringList.iterator(); it.hasNext();) {
+            Object element = it.next();
+            if (element != null) {
+                result.append(element);
+                if (it.hasNext()) {
+                    result.append(", ");
+                }
+            }
+        }
+        return result.toString();
+    }
+    
+    /**
+     * @see #setHttpServerPort(int)
+     */
+    private int httpServerPort = 80;
+
+    /**
+     * @see #setHttpsServerPort(int)
+     */
+    private int httpsServerPort = 443;
+
+    /**
+     * @see #setInternalProxies(String)
+     */
+    private Pattern internalProxies = Pattern.compile(
+            "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" +
+            "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" +
+            "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" +
+            "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}");
+    
+    /**
+     * @see #setProtocolHeader(String)
+     */
+    private String protocolHeader = null;
+    
+    private String protocolHeaderHttpsValue = "https";
+    
+    /**
+     * @see #setProxiesHeader(String)
+     */
+    private String proxiesHeader = "X-Forwarded-By";
+    
+    /**
+     * @see #setRemoteIpHeader(String)
+     */
+    private String remoteIpHeader = "X-Forwarded-For";
+    
+    /**
+     * @see #setRequestAttributesEnabled(boolean)
+     */
+    private boolean requestAttributesEnabled = true;
+
+    /**
+     * @see #setTrustedProxies(String)
+     */
+    private Pattern trustedProxies = null;
+    
+    @Override
+    public void destroy() {
+        // NOOP
+    }
+    
+    public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
+        
+        if (internalProxies != null &&
+                internalProxies.matcher(request.getRemoteAddr()).matches()) {
+            String remoteIp = null;
+            // In java 6, proxiesHeaderValue should be declared as a java.util.Deque
+            LinkedList<String> proxiesHeaderValue = new LinkedList<String>();
+            StringBuilder concatRemoteIpHeaderValue = new StringBuilder();
+            
+            for (Enumeration<String> e = request.getHeaders(remoteIpHeader); e.hasMoreElements();) {
+                if (concatRemoteIpHeaderValue.length() > 0) {
+                    concatRemoteIpHeaderValue.append(", ");
+                }
+
+                concatRemoteIpHeaderValue.append(e.nextElement());
+            }
+
+            String[] remoteIpHeaderValue = commaDelimitedListToStringArray(concatRemoteIpHeaderValue.toString());
+            int idx;
+            // loop on remoteIpHeaderValue to find the first trusted remote ip and to build the proxies chain
+            for (idx = remoteIpHeaderValue.length - 1; idx >= 0; idx--) {
+                String currentRemoteIp = remoteIpHeaderValue[idx];
+                remoteIp = currentRemoteIp;
+                if (internalProxies.matcher(currentRemoteIp).matches()) {
+                    // do nothing, internalProxies IPs are not appended to the
+                } else if (trustedProxies != null &&
+                        trustedProxies.matcher(currentRemoteIp).matches()) {
+                    proxiesHeaderValue.addFirst(currentRemoteIp);
+                } else {
+                    idx--; // decrement idx because break statement doesn't do it
+                    break;
+                }
+            }
+            // continue to loop on remoteIpHeaderValue to build the new value of the remoteIpHeader
+            LinkedList<String> newRemoteIpHeaderValue = new LinkedList<String>();
+            for (; idx >= 0; idx--) {
+                String currentRemoteIp = remoteIpHeaderValue[idx];
+                newRemoteIpHeaderValue.addFirst(currentRemoteIp);
+            }
+            
+            XForwardedRequest xRequest = new XForwardedRequest(request);
+            if (remoteIp != null) {
+                
+                xRequest.setRemoteAddr(remoteIp);
+                xRequest.setRemoteHost(remoteIp);
+                
+                if (proxiesHeaderValue.size() == 0) {
+                    xRequest.removeHeader(proxiesHeader);
+                } else {
+                    String commaDelimitedListOfProxies = listToCommaDelimitedString(proxiesHeaderValue);
+                    xRequest.setHeader(proxiesHeader, commaDelimitedListOfProxies);
+                }
+                if (newRemoteIpHeaderValue.size() == 0) {
+                    xRequest.removeHeader(remoteIpHeader);
+                } else {
+                    String commaDelimitedRemoteIpHeaderValue = listToCommaDelimitedString(newRemoteIpHeaderValue);
+                    xRequest.setHeader(remoteIpHeader, commaDelimitedRemoteIpHeaderValue);
+                }
+            }
+            
+            if (protocolHeader != null) {
+                String protocolHeaderValue = request.getHeader(protocolHeader);
+                if (protocolHeaderValue == null) {
+                    // don't modify the secure,scheme and serverPort attributes of the request
+                } else if (protocolHeaderHttpsValue.equalsIgnoreCase(protocolHeaderValue)) {
+                    xRequest.setSecure(true);
+                    xRequest.setScheme("https");
+                    xRequest.setServerPort(httpsServerPort);
+                } else {
+                    xRequest.setSecure(false);
+                    xRequest.setScheme("http");
+                    xRequest.setServerPort(httpServerPort);
+                }
+            }
+            
+            if (log.isDebugEnabled()) {
+                log.debug("Incoming request " + request.getRequestURI() + " with originalRemoteAddr '" + request.getRemoteAddr()
+                        + "', originalRemoteHost='" + request.getRemoteHost() + "', originalSecure='" + request.isSecure()
+                        + "', originalScheme='" + request.getScheme() + "', original[" + remoteIpHeader + "]='"
+                        + concatRemoteIpHeaderValue + "', original[" + protocolHeader + "]='"
+                        + (protocolHeader == null ? null : request.getHeader(protocolHeader)) + "' will be seen as newRemoteAddr='"
+                        + xRequest.getRemoteAddr() + "', newRemoteHost='" + xRequest.getRemoteHost() + "', newScheme='"
+                        + xRequest.getScheme() + "', newSecure='" + xRequest.isSecure() + "', new[" + remoteIpHeader + "]='"
+                        + xRequest.getHeader(remoteIpHeader) + "', new[" + proxiesHeader + "]='" + xRequest.getHeader(proxiesHeader) + "'");
+            }
+            if (requestAttributesEnabled) {
+                request.setAttribute(AccessLog.REMOTE_ADDR_ATTRIBUTE,
+                        request.getRemoteAddr());
+                request.setAttribute(AccessLog.REMOTE_HOST_ATTRIBUTE,
+                        request.getRemoteHost());
+                request.setAttribute(AccessLog.PROTOCOL_ATTRIBUTE,
+                        request.getProtocol());
+                request.setAttribute(AccessLog.SERVER_PORT_ATTRIBUTE,
+                        Integer.valueOf(request.getServerPort()));
+            }
+            chain.doFilter(xRequest, response);
+        } else {
+            if (log.isDebugEnabled()) {
+                log.debug("Skip RemoteIpFilter for request " + request.getRequestURI() + " with originalRemoteAddr '"
+                        + request.getRemoteAddr() + "'");
+            }
+            chain.doFilter(request, response);
+        }
+        
+    }
+    
+    /**
+     * Wrap the incoming <code>request</code> in a {@link XForwardedRequest} if the http header <code>x-forwareded-for</code> is not empty.
+     */
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+        if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
+            doFilter((HttpServletRequest)request, (HttpServletResponse)response, chain);
+        } else {
+            chain.doFilter(request, response);
+        }
+    }
+    
+    public int getHttpsServerPort() {
+        return httpsServerPort;
+    }
+    
+    public Pattern getInternalProxies() {
+        return internalProxies;
+    }
+    
+    public String getProtocolHeader() {
+        return protocolHeader;
+    }
+    
+    public String getProtocolHeaderHttpsValue() {
+        return protocolHeaderHttpsValue;
+    }
+    
+    public String getProxiesHeader() {
+        return proxiesHeader;
+    }
+    
+    public String getRemoteIpHeader() {
+        return remoteIpHeader;
+    }
+    
+    /**
+     * @see #setRequestAttributesEnabled(boolean)
+     * @return <code>true</code> if the attributes will be logged, otherwise
+     *         <code>false</code>
+     */
+    public boolean getRequestAttributesEnabled() {
+        return requestAttributesEnabled;
+    }
+
+    public Pattern getTrustedProxies() {
+        return trustedProxies;
+    }
+    
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+        if (filterConfig.getInitParameter(INTERNAL_PROXIES_PARAMETER) != null) {
+            setInternalProxies(filterConfig.getInitParameter(INTERNAL_PROXIES_PARAMETER));
+        }
+        
+        if (filterConfig.getInitParameter(PROTOCOL_HEADER_PARAMETER) != null) {
+            setProtocolHeader(filterConfig.getInitParameter(PROTOCOL_HEADER_PARAMETER));
+        }
+        
+        if (filterConfig.getInitParameter(PROTOCOL_HEADER_HTTPS_VALUE_PARAMETER) != null) {
+            setProtocolHeaderHttpsValue(filterConfig.getInitParameter(PROTOCOL_HEADER_HTTPS_VALUE_PARAMETER));
+        }
+        
+        if (filterConfig.getInitParameter(PROXIES_HEADER_PARAMETER) != null) {
+            setProxiesHeader(filterConfig.getInitParameter(PROXIES_HEADER_PARAMETER));
+        }
+        
+        if (filterConfig.getInitParameter(REMOTE_IP_HEADER_PARAMETER) != null) {
+            setRemoteIpHeader(filterConfig.getInitParameter(REMOTE_IP_HEADER_PARAMETER));
+        }
+        
+        if (filterConfig.getInitParameter(TRUSTED_PROXIES_PARAMETER) != null) {
+            setTrustedProxies(filterConfig.getInitParameter(TRUSTED_PROXIES_PARAMETER));
+        }
+        
+        if (filterConfig.getInitParameter(HTTP_SERVER_PORT_PARAMETER) != null) {
+            try {
+                setHttpServerPort(Integer.parseInt(filterConfig.getInitParameter(HTTP_SERVER_PORT_PARAMETER)));
+            } catch (NumberFormatException e) {
+                throw new NumberFormatException("Illegal " + HTTP_SERVER_PORT_PARAMETER + " : " + e.getMessage());
+            }
+        }
+        
+        if (filterConfig.getInitParameter(HTTPS_SERVER_PORT_PARAMETER) != null) {
+            try {
+                setHttpsServerPort(Integer.parseInt(filterConfig.getInitParameter(HTTPS_SERVER_PORT_PARAMETER)));
+            } catch (NumberFormatException e) {
+                throw new NumberFormatException("Illegal " + HTTPS_SERVER_PORT_PARAMETER + " : " + e.getMessage());
+            }
+        }
+    }
+    
+    /**
+     * <p>
+     * Server Port value if the {@link #protocolHeader} indicates HTTP (i.e. {@link #protocolHeader} is not null and
+     * has a value different of {@link #protocolHeaderHttpsValue}). 
+     * </p>
+     * <p>
+     * Default value : 80
+     * </p>
+     */
+    public void setHttpServerPort(int httpServerPort) {
+        this.httpServerPort = httpServerPort;
+    }
+    
+    /**
+     * <p>
+     * Server Port value if the {@link #protocolHeader} indicates HTTPS
+     * </p>
+     * <p>
+     * Default value : 443
+     * </p>
+     */
+    public void setHttpsServerPort(int httpsServerPort) {
+        this.httpsServerPort = httpsServerPort;
+    }
+    
+    /**
+     * <p>
+     * Regular expression that defines the internal proxies.
+     * </p>
+     * <p>
+     * Default value : 10\.\d{1,3}\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}|169\.254.\d{1,3}.\d{1,3}|127\.\d{1,3}\.\d{1,3}\.\d{1,3}
+     * </p>
+     */
+    public void setInternalProxies(String internalProxies) {
+        if (internalProxies == null || internalProxies.length() == 0) {
+            this.internalProxies = null;
+        } else {
+            this.internalProxies = Pattern.compile(internalProxies);
+        }
+    }
+    
+    /**
+     * <p>
+     * Header that holds the incoming protocol, usally named <code>X-Forwarded-Proto</code>. If <code>null</code>, request.scheme and
+     * request.secure will not be modified.
+     * </p>
+     * <p>
+     * Default value : <code>null</code>
+     * </p>
+     */
+    public void setProtocolHeader(String protocolHeader) {
+        this.protocolHeader = protocolHeader;
+    }
+    
+    /**
+     * <p>
+     * Case insensitive value of the protocol header to indicate that the incoming http request uses HTTPS.
+     * </p>
+     * <p>
+     * Default value : <code>https</code>
+     * </p>
+     */
+    public void setProtocolHeaderHttpsValue(String protocolHeaderHttpsValue) {
+        this.protocolHeaderHttpsValue = protocolHeaderHttpsValue;
+    }
+    
+    /**
+     * <p>
+     * The proxiesHeader directive specifies a header into which mod_remoteip will collect a list of all of the intermediate client IP
+     * addresses trusted to resolve the actual remote IP. Note that intermediate RemoteIPTrustedProxy addresses are recorded in this header,
+     * while any intermediate RemoteIPInternalProxy addresses are discarded.
+     * </p>
+     * <p>
+     * Name of the http header that holds the list of trusted proxies that has been traversed by the http request.
+     * </p>
+     * <p>
+     * The value of this header can be comma delimited.
+     * </p>
+     * <p>
+     * Default value : <code>X-Forwarded-By</code>
+     * </p>
+     */
+    public void setProxiesHeader(String proxiesHeader) {
+        this.proxiesHeader = proxiesHeader;
+    }
+    
+    /**
+     * <p>
+     * Name of the http header from which the remote ip is extracted.
+     * </p>
+     * <p>
+     * The value of this header can be comma delimited.
+     * </p>
+     * <p>
+     * Default value : <code>X-Forwarded-For</code>
+     * </p>
+     */
+    public void setRemoteIpHeader(String remoteIpHeader) {
+        this.remoteIpHeader = remoteIpHeader;
+    }
+    
+    /**
+     * Should this filter set request attributes for IP address, Hostname,
+     * protocol and port used for the request? This are typically used in
+     * conjunction with an {@link AccessLog} which will otherwise log the
+     * original values. Default is <code>true</code>.
+     * 
+     * The attributes set are:
+     * <ul>
+     * <li>org.apache.catalina.RemoteAddr</li>
+     * <li>org.apache.catalina.RemoteHost</li>
+     * <li>org.apache.catalina.Protocol</li>
+     * <li>org.apache.catalina.ServerPost</li>
+     * </ul>
+     * 
+     * @param requestAttributesEnabled  <code>true</code> causes the attributes
+     *                                  to be set, <code>false</code> disables
+     *                                  the setting of the attributes. 
+     */
+    public void setRequestAttributesEnabled(boolean requestAttributesEnabled) {
+        this.requestAttributesEnabled = requestAttributesEnabled;
+    }
+
+    /**
+     * <p>
+     * Regular expression defining proxies that are trusted when they appear in
+     * the {@link #remoteIpHeader} header.
+     * </p>
+     * <p>
+     * Default value : empty list, no external proxy is trusted.
+     * </p>
+     */
+    public void setTrustedProxies(String trustedProxies) {
+        if (trustedProxies == null || trustedProxies.length() == 0) {
+            this.trustedProxies = null;
+        } else {
+            this.trustedProxies = Pattern.compile(trustedProxies);
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/filters/RequestDumperFilter.java b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/RequestDumperFilter.java
new file mode 100644
index 0000000..64f86fd
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/RequestDumperFilter.java
@@ -0,0 +1,285 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.filters;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Enumeration;
+
+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.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+
+/**
+ * <p>Implementation of a Filter that logs interesting contents from the
+ * specified Request (before processing) and the corresponding Response
+ * (after processing).  It is especially useful in debugging problems
+ * related to headers and cookies.</p>
+ * 
+ * <p>When using this Filter, it is strongly recommended that the
+ * <code>org.apache.catalina.filter.RequestDumperFilter</code> logger is
+ * directed to a dedicated file and that the
+ * <code>org.apache.juli.VerbatimFormmater</code> is used.</p>
+ *
+ * @author Craig R. McClanahan
+ */
+
+public class RequestDumperFilter implements Filter {
+
+    private static final String NON_HTTP_REQ_MSG =
+        "Not available. Non-http request.";
+    private static final String NON_HTTP_RES_MSG =
+        "Not available. Non-http response.";
+
+    private static final ThreadLocal<Timestamp> timestamp =
+            new ThreadLocal<Timestamp>() {
+        @Override
+        protected Timestamp initialValue() {
+            return new Timestamp();
+        }
+    };
+
+    /**
+     * The logger for this class.
+     */
+    private static final Log log = LogFactory.getLog(RequestDumperFilter.class);
+
+
+    /**
+     * Log the interesting request parameters, invoke the next Filter in the
+     * sequence, and log the interesting response parameters.
+     *
+     * @param request  The servlet request to be processed
+     * @param response The servlet response to be created
+     * @param chain    The filter chain being processed
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response,
+            FilterChain chain)
+        throws IOException, ServletException {
+
+        HttpServletRequest hRequest = null;
+        HttpServletResponse hResponse = null;
+        
+        if (request instanceof HttpServletRequest) {
+            hRequest = (HttpServletRequest) request;
+        }
+        if (response instanceof HttpServletResponse) {
+            hResponse = (HttpServletResponse) response;
+        }
+
+        // Log pre-service information
+        doLog("START TIME        ", getTimestamp());
+        
+        if (hRequest == null) {
+            doLog("        requestURI", NON_HTTP_REQ_MSG);
+            doLog("          authType", NON_HTTP_REQ_MSG);
+        } else {
+            doLog("        requestURI", hRequest.getRequestURI());
+            doLog("          authType", hRequest.getAuthType());
+        }
+        
+        doLog(" characterEncoding", request.getCharacterEncoding());
+        doLog("     contentLength",
+                Integer.valueOf(request.getContentLength()).toString());
+        doLog("       contentType", request.getContentType());
+        
+        if (hRequest == null) {
+            doLog("       contextPath", NON_HTTP_REQ_MSG);
+            doLog("            cookie", NON_HTTP_REQ_MSG);
+            doLog("            header", NON_HTTP_REQ_MSG);
+        } else {
+            doLog("       contextPath", hRequest.getContextPath());
+            Cookie cookies[] = hRequest.getCookies();
+            if (cookies != null) {
+                for (int i = 0; i < cookies.length; i++)
+                    doLog("            cookie", cookies[i].getName() +
+                            "=" + cookies[i].getValue());
+            }
+            Enumeration<String> hnames = hRequest.getHeaderNames();
+            while (hnames.hasMoreElements()) {
+                String hname = hnames.nextElement();
+                Enumeration<String> hvalues = hRequest.getHeaders(hname);
+                while (hvalues.hasMoreElements()) {
+                    String hvalue = hvalues.nextElement();
+                    doLog("            header", hname + "=" + hvalue);
+                }
+            }
+        }
+        
+        doLog("            locale", request.getLocale().toString());
+        
+        if (hRequest == null) {
+            doLog("            method", NON_HTTP_REQ_MSG);
+        } else {
+            doLog("            method", hRequest.getMethod());
+        }
+        
+        Enumeration<String> pnames = request.getParameterNames();
+        while (pnames.hasMoreElements()) {
+            String pname = pnames.nextElement();
+            String pvalues[] = request.getParameterValues(pname);
+            StringBuilder result = new StringBuilder(pname);
+            result.append('=');
+            for (int i = 0; i < pvalues.length; i++) {
+                if (i > 0)
+                    result.append(", ");
+                result.append(pvalues[i]);
+            }
+            doLog("         parameter", result.toString());
+        }
+        
+        if (hRequest == null) {
+            doLog("          pathInfo", NON_HTTP_REQ_MSG);
+        } else {
+            doLog("          pathInfo", hRequest.getPathInfo());
+        }
+        
+        doLog("          protocol", request.getProtocol());
+        
+        if (hRequest == null) {
+            doLog("       queryString", NON_HTTP_REQ_MSG);
+        } else {
+            doLog("       queryString", hRequest.getQueryString());
+        }
+        
+        doLog("        remoteAddr", request.getRemoteAddr());
+        doLog("        remoteHost", request.getRemoteHost());
+        
+        if (hRequest == null) {
+            doLog("        remoteUser", NON_HTTP_REQ_MSG);
+            doLog("requestedSessionId", NON_HTTP_REQ_MSG);
+        } else {
+            doLog("        remoteUser", hRequest.getRemoteUser());
+            doLog("requestedSessionId", hRequest.getRequestedSessionId());
+        }
+        
+        doLog("            scheme", request.getScheme());
+        doLog("        serverName", request.getServerName());
+        doLog("        serverPort",
+                Integer.valueOf(request.getServerPort()).toString());
+        
+        if (hRequest == null) {
+            doLog("       servletPath", NON_HTTP_REQ_MSG);
+        } else {
+            doLog("       servletPath", hRequest.getServletPath());
+        }
+        
+        doLog("          isSecure",
+                Boolean.valueOf(request.isSecure()).toString());
+        doLog("------------------",
+                "--------------------------------------------");
+
+        // Perform the request
+        chain.doFilter(request, response);
+
+        // Log post-service information
+        doLog("------------------",
+                "--------------------------------------------");
+        if (hRequest == null) {
+            doLog("          authType", NON_HTTP_REQ_MSG);
+        } else {
+            doLog("          authType", hRequest.getAuthType());
+        }
+        
+        doLog("       contentType", response.getContentType());
+        
+        if (hResponse == null) {
+            doLog("            header", NON_HTTP_RES_MSG);
+        } else {
+            Iterable<String> rhnames = hResponse.getHeaderNames();
+            for (String rhname : rhnames) {
+                Iterable<String> rhvalues = hResponse.getHeaders(rhname);
+                for (String rhvalue : rhvalues)
+                    doLog("            header", rhname + "=" + rhvalue);
+            }
+        }
+
+        if (hRequest == null) {
+            doLog("        remoteUser", NON_HTTP_REQ_MSG);
+        } else {
+            doLog("        remoteUser", hRequest.getRemoteUser());
+        }
+        
+        if (hResponse == null) {
+            doLog("        remoteUser", NON_HTTP_RES_MSG);
+        } else {
+            doLog("            status",
+                    Integer.valueOf(hResponse.getStatus()).toString());
+        }
+
+        doLog("END TIME          ", getTimestamp());
+        doLog("==================",
+                "============================================");
+    }
+
+    private void doLog(String attribute, String value) {
+        StringBuilder sb = new StringBuilder(80);
+        sb.append(Thread.currentThread().getName());
+        sb.append(' ');
+        sb.append(attribute);
+        sb.append('=');
+        sb.append(value);
+        log.info(sb.toString());
+    }
+
+    private String getTimestamp() {
+        Timestamp ts = timestamp.get();
+        long currentTime = System.currentTimeMillis();
+        
+        if ((ts.date.getTime() + 999) < currentTime) {
+            ts.date.setTime(currentTime - (currentTime % 1000));
+            ts.update();
+        }
+        return ts.dateString;
+    }
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+        // NOOP
+    }
+
+    @Override
+    public void destroy() {
+        // NOOP
+    }
+
+    private static final class Timestamp {
+        private Date date = new Date(0);
+        private SimpleDateFormat format =
+            new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss");
+        private String dateString = format.format(date);
+        private void update() {
+            dateString = format.format(date);
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/filters/RequestFilter.java b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/RequestFilter.java
new file mode 100644
index 0000000..a417ebc
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/RequestFilter.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.filters;
+
+
+import java.io.IOException;
+import java.util.regex.Pattern;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.comet.CometEvent;
+import org.apache.catalina.comet.CometFilter;
+import org.apache.catalina.comet.CometFilterChain;
+
+/**
+ * Implementation of a Filter that performs filtering based on comparing the
+ * appropriate request property (selected based on which subclass you choose
+ * to configure into your Container's pipeline) against the regular expressions
+ * configured for this Filter.
+ * <p>
+ * This filter is configured by setting the <code>allow</code> and/or
+ * <code>deny</code> properties to a regular expressions (in the syntax
+ * supported by {@link Pattern}) to which the appropriate request property will
+ * be compared.  Evaluation proceeds as follows:
+ * <ul>
+ * <li>The subclass extracts the request property to be filtered, and
+ *     calls the common <code>process()</code> method.
+ * <li>If there is a deny expression configured, the property will be compared
+ *     to the expression. If a match is found, this request will be rejected
+ *     with a "Forbidden" HTTP response.</li>
+ * <li>If there is a allow expression configured, the property will be compared
+ *     to the expression. If a match is found, this request will be allowed to
+ *     pass through to the next filter in the current pipeline.</li>
+ * <li>If a deny expression was specified but no allow expression, allow this
+ *     request to pass through (because none of the deny expressions matched
+ *     it).
+ * <li>The request will be rejected with a "Forbidden" HTTP response.</li>
+ * </ul>
+ */
+
+public abstract class RequestFilter
+    extends FilterBase implements CometFilter {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * The regular expression used to test for allowed requests.
+     */
+    protected Pattern allow = null;
+
+    /**
+     * The regular expression used to test for denied requests.
+     */
+    protected Pattern deny = null;
+    
+    /**
+     * mime type -- "text/plain"
+     */
+    private static final String PLAIN_TEXT_MIME_TYPE = "text/plain";
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the regular expression used to test for allowed requests for this
+     * Filter, if any; otherwise, return <code>null</code>.
+     */
+    public String getAllow() {
+        if (allow == null) {
+            return null;
+        }
+        return allow.toString();
+    }
+
+
+    /**
+     * Set the regular expression used to test for allowed requests for this
+     * Filter, if any.
+     *
+     * @param allow The new allow expression
+     */
+    public void setAllow(String allow) {
+        if (allow == null || allow.length() == 0) {
+            this.allow = null;
+        } else {
+            this.allow = Pattern.compile(allow);
+        }
+    }
+
+
+    /**
+     * Return the regular expression used to test for denied requests for this
+     * Filter, if any; otherwise, return <code>null</code>.
+     */
+    public String getDeny() {
+        if (deny == null) {
+            return null;
+        }
+        return deny.toString();
+    }
+
+
+    /**
+     * Set the regular expression used to test for denied requests for this
+     * Filter, if any.
+     *
+     * @param deny The new deny expression
+     */
+    public void setDeny(String deny) {
+        if (deny == null || deny.length() == 0) {
+            this.deny = null;
+        } else {
+            this.deny = Pattern.compile(deny);
+        }
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Extract the desired request property, and pass it (along with the
+     * specified request and response objects) to the protected
+     * <code>process()</code> method to perform the actual filtering.
+     * This method must be implemented by a concrete subclass.
+     *
+     * @param request The servlet request to be processed
+     * @param response The servlet response to be created
+     * @param chain The filter chain
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    @Override
+    public abstract void doFilter(ServletRequest request,
+            ServletResponse response, FilterChain chain) throws IOException,
+            ServletException;
+
+      
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Perform the filtering that has been configured for this Filter, matching
+     * against the specified request property.
+     *
+     * @param property The request property on which to filter
+     * @param request The servlet request to be processed
+     * @param response The servlet response to be processed
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    protected void process(String property, ServletRequest request,
+            ServletResponse response, FilterChain chain)
+            throws IOException, ServletException {
+
+        if (isAllowed(property)) {
+            chain.doFilter(request, response);
+        } else {
+            if (response instanceof HttpServletResponse) {
+                ((HttpServletResponse) response)
+                        .sendError(HttpServletResponse.SC_FORBIDDEN);
+            } else {
+                sendErrorWhenNotHttp(response);
+            }
+        }
+    }
+
+    /**
+     * Perform the filtering that has been configured for this Filter, matching
+     * against the specified request property.
+     * 
+     * @param property  The property to check against the allow/deny rules
+     * @param event     The comet event to be filtered
+     * @param chain     The comet filter chain
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    protected void processCometEvent(String property, CometEvent event,
+            CometFilterChain chain) throws IOException, ServletException {
+        HttpServletResponse response = event.getHttpServletResponse();
+        
+        if (isAllowed(property)) {
+            chain.doFilterEvent(event);
+        } else {
+            response.sendError(HttpServletResponse.SC_FORBIDDEN);
+            event.close();
+        }
+    }
+
+    /**
+     * Process the allow and deny rules for the provided property.
+     * 
+     * @param property  The property to test against the allow and deny lists
+     * @return          <code>true</code> if this request should be allowed,
+     *                  <code>false</code> otherwise
+     */
+    private boolean isAllowed(String property) {
+        if (deny != null && deny.matcher(property).matches()) {
+            return false;
+        }
+     
+        // Check the allow patterns, if any
+        if (allow != null && allow.matcher(property).matches()) {
+            return true;
+        }
+
+        // Allow if denies specified but not allows
+        if (deny != null && allow == null) {
+            return true;
+        }
+
+        // Deny this request
+        return false;
+    }
+
+    private void sendErrorWhenNotHttp(ServletResponse response)
+            throws IOException {
+        response.setContentType(PLAIN_TEXT_MIME_TYPE);
+        response.getWriter().write(sm.getString("http.403"));
+        response.getWriter().flush();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/filters/WebdavFixFilter.java b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/WebdavFixFilter.java
new file mode 100644
index 0000000..2b825e8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/filters/WebdavFixFilter.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.filters;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Filter that attempts to force MS WebDAV clients connecting on port 80 to use
+ * a WebDAV client that actually works. Other workarounds that might help
+ * include:
+ * <ul>
+ *   <li>Specifying the port, even if it is port 80, when trying to
+ *       connect.</li>
+ *   <li>Cancelling the first authentication dialog box and then trying to
+ *       reconnect.</li>
+ * </ul>
+ * 
+ * Generally each different version of the MS client has a different set of
+ * problems.
+ * <p>
+ * TODO: Update this filter to recognise specific MS clients and apply the
+ *       appropriate workarounds for that particular client
+ * <p>      
+ * As a filter, this is configured in web.xml like any other Filter. You usually
+ * want to map this filter to whatever your WebDAV servlet is mapped to.
+ * <p>
+ * In addition to the issues fixed by this Filter, the following issues have
+ * also been observed that cannot be fixed by this filter. Where possible the
+ * filter will add an message to the logs.
+ * <p>
+ * XP x64 SP2 (MiniRedir Version 3790)
+ * <ul>
+ *   <li>Only connects to port 80</li>
+ *   <li>Unknown issue means it doesn't work</li>
+ * </ul>
+ */
+
+public class WebdavFixFilter implements Filter {
+
+    private static final String LOG_MESSAGE_PREAMBLE =
+        "WebdavFixFilter: Detected client problem: ";
+
+    /* Start string for all versions */
+    private static final String UA_MINIDIR_START =
+        "Microsoft-WebDAV-MiniRedir";
+    /* XP 32-bit SP3 */
+    private static final String UA_MINIDIR_5_1_2600 =
+        "Microsoft-WebDAV-MiniRedir/5.1.2600";
+    
+    /* XP 64-bit SP2 */
+    private static final String UA_MINIDIR_5_2_3790 =
+        "Microsoft-WebDAV-MiniRedir/5.2.3790";
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+        // NOOP
+    }
+
+    @Override
+    public void destroy() {
+        // NOOP
+    }
+
+    /**
+     * Check for the broken MS WebDAV client and if detected issue a re-direct
+     * that hopefully will cause the non-broken client to be used.
+     */
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response,
+            FilterChain chain) throws IOException, ServletException {
+        if (!(request instanceof HttpServletRequest) ||
+                !(response instanceof HttpServletResponse)) {
+            chain.doFilter(request, response);
+            return;
+        }
+        HttpServletRequest httpRequest = ((HttpServletRequest) request);
+        HttpServletResponse httpResponse = ((HttpServletResponse) response);
+        String ua = httpRequest.getHeader("User-Agent");
+        
+        if (ua == null || ua.length() == 0 ||
+                !ua.startsWith(UA_MINIDIR_START)) {
+            // No UA or starts with non MS value
+            // Hope everything just works...
+            chain.doFilter(request, response);
+        } else if (ua.startsWith(UA_MINIDIR_5_1_2600)) {
+            // XP 32-bit SP3 - needs redirect with explicit port
+            httpResponse.sendRedirect(buildRedirect(httpRequest));
+        } else if (ua.startsWith(UA_MINIDIR_5_2_3790)) {
+            // XP 64-bit SP2
+            if (!"".equals(httpRequest.getContextPath())) {
+                log(request,
+                        "XP-x64-SP2 clients only work with the root context");
+            }
+            // Namespace issue maybe
+            // see http://greenbytes.de/tech/webdav/webdav-redirector-list.html
+            log(request, "XP-x64-SP2 is known not to work with WebDAV Servlet");
+            
+            chain.doFilter(request, response);
+        } else {
+            // Don't know which MS client it is - try the redirect with an
+            // explicit port in the hope that it moves the client to a different
+            // WebDAV implementation that works
+            httpResponse.sendRedirect(buildRedirect(httpRequest));
+        }        
+    }
+
+    private String buildRedirect(HttpServletRequest request) {
+        StringBuilder location =
+            new StringBuilder(request.getRequestURL().length());
+        location.append(request.getScheme());
+        location.append("://");
+        location.append(request.getServerName());
+        location.append(':');
+        // If we include the port, even if it is 80, then MS clients will use
+        // a WebDAV client that works rather than the MiniRedir that has
+        // problems with BASIC authentication
+        location.append(request.getServerPort());
+        location.append(request.getRequestURI());
+        return location.toString();
+    }
+
+    private void log(ServletRequest request, String msg) {
+        StringBuilder builder = new StringBuilder(LOG_MESSAGE_PREAMBLE);
+        builder.append(msg);
+        request.getServletContext().log(builder.toString());
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/BackupManager.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/BackupManager.java
new file mode 100644
index 0000000..316cac5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/BackupManager.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.catalina.ha.session;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.apache.catalina.DistributedManager;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.Session;
+import org.apache.catalina.ha.CatalinaCluster;
+import org.apache.catalina.ha.ClusterManager;
+import org.apache.catalina.ha.ClusterMessage;
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.tipis.AbstractReplicatedMap.MapOwner;
+import org.apache.catalina.tribes.tipis.LazyReplicatedMap;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ *@author Filip Hanik
+ *@version 1.0
+ */
+public class BackupManager extends ClusterManagerBase
+        implements MapOwner, DistributedManager {
+
+    private static final Log log = LogFactory.getLog(BackupManager.class);
+
+    protected static long DEFAULT_REPL_TIMEOUT = 15000;//15 seconds
+
+    /** Set to true if we don't want the sessions to expire on shutdown */
+    protected boolean mExpireSessionsOnShutdown = true;
+    
+    /**
+     * The name of this manager
+     */
+    protected String name;
+
+    /**
+     * A reference to the cluster
+     */
+    protected CatalinaCluster cluster;
+    
+    /**
+     * Should listeners be notified?
+     */
+    private boolean notifyListenersOnReplication;
+    /**
+     * 
+     */
+    private int mapSendOptions = Channel.SEND_OPTIONS_SYNCHRONIZED_ACK|Channel.SEND_OPTIONS_USE_ACK;
+
+    /**
+     * Constructor, just calls super()
+     *
+     */
+    public BackupManager() {
+        super();
+    }
+
+
+//******************************************************************************/
+//      ClusterManager Interface     
+//******************************************************************************/
+
+    @Override
+    public void messageDataReceived(ClusterMessage msg) {
+    }
+
+    public void setExpireSessionsOnShutdown(boolean expireSessionsOnShutdown)
+    {
+        mExpireSessionsOnShutdown = expireSessionsOnShutdown;
+    }
+
+    @Override
+    public void setCluster(CatalinaCluster cluster) {
+        if(log.isDebugEnabled())
+            log.debug("Cluster associated with BackupManager");
+        this.cluster = cluster;
+    }
+
+    public boolean getExpireSessionsOnShutdown()
+    {
+        return mExpireSessionsOnShutdown;
+    }
+
+
+    @Override
+    public ClusterMessage requestCompleted(String sessionId) {
+        if (!getState().isAvailable()) return null;
+        LazyReplicatedMap map = (LazyReplicatedMap)sessions;
+        map.replicate(sessionId,false);
+        return null;
+    }
+
+
+//=========================================================================
+// OVERRIDE THESE METHODS TO IMPLEMENT THE REPLICATION
+//=========================================================================
+    @Override
+    public void objectMadePrimay(Object key, Object value) {
+        if (value!=null && value instanceof DeltaSession) {
+            DeltaSession session = (DeltaSession)value;
+            synchronized (session) {
+                session.access();
+                session.setPrimarySession(true);
+                session.endAccess();
+            }
+        }
+    }
+
+    @Override
+    public Session createEmptySession() {
+        return new DeltaSession(this);
+    }
+    
+
+    @Override
+    public String getName() {
+        return this.name;
+    }
+
+
+    /**
+     * Start this component and implement the requirements
+     * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
+     *
+     * Starts the cluster communication channel, this will connect with the
+     * other nodes in the cluster, and request the current session state to be
+     * transferred to this node.
+     * 
+     * @exception LifecycleException if this component detects a fatal error
+     *  that prevents this component from being used
+     */
+    @Override
+    protected synchronized void startInternal() throws LifecycleException {
+        
+        super.startInternal();
+
+        try {
+            cluster.registerManager(this);
+            CatalinaCluster catclust = cluster;
+            LazyReplicatedMap map = new LazyReplicatedMap(this,
+                                                          catclust.getChannel(),
+                                                          DEFAULT_REPL_TIMEOUT,
+                                                          getMapName(),
+                                                          getClassLoaders());
+            map.setChannelSendOptions(mapSendOptions);
+            this.sessions = map;
+        }  catch ( Exception x ) {
+            log.error("Unable to start BackupManager",x);
+            throw new LifecycleException("Failed to start BackupManager",x);
+        }
+        setState(LifecycleState.STARTING);
+    }
+    
+    public String getMapName() {
+        CatalinaCluster catclust = cluster;
+        String name = catclust.getManagerName(getName(),this)+"-"+"map";
+        if ( log.isDebugEnabled() ) log.debug("Backup manager, Setting map name to:"+name);
+        return name;
+    }
+
+
+    /**
+     * Stop this component and implement the requirements
+     * of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
+     * 
+     * This will disconnect the cluster communication channel and stop the
+     * listener thread.
+     *
+     * @exception LifecycleException if this component detects a fatal error
+     *  that prevents this component from being used
+     */
+    @Override
+    protected synchronized void stopInternal() throws LifecycleException {
+
+        if (log.isDebugEnabled())
+            log.debug("Stopping");
+
+        setState(LifecycleState.STOPPING);
+
+        if (sessions instanceof LazyReplicatedMap) {
+            LazyReplicatedMap map = (LazyReplicatedMap)sessions;
+            map.breakdown();
+        }
+
+        cluster.removeManager(this);
+        super.stopInternal();
+    }
+
+    @Override
+    public void setDistributable(boolean dist) {
+        this.distributable = dist;
+    }
+
+    @Override
+    public void setName(String name) {
+        this.name = name;
+    }
+    @Override
+    public boolean isNotifyListenersOnReplication() {
+        return notifyListenersOnReplication;
+    }
+    public void setNotifyListenersOnReplication(boolean notifyListenersOnReplication) {
+        this.notifyListenersOnReplication = notifyListenersOnReplication;
+    }
+
+    public void setMapSendOptions(int mapSendOptions) {
+        this.mapSendOptions = mapSendOptions;
+    }
+
+    /* 
+     * @see org.apache.catalina.ha.ClusterManager#getCluster()
+     */
+    @Override
+    public CatalinaCluster getCluster() {
+        return cluster;
+    }
+
+    public int getMapSendOptions() {
+        return mapSendOptions;
+    }
+
+    @Override
+    public String[] getInvalidatedSessions() {
+        return new String[0];
+    }
+    
+    @Override
+    public ClusterManager cloneFromTemplate() {
+        BackupManager result = new BackupManager();
+        result.mExpireSessionsOnShutdown = mExpireSessionsOnShutdown;
+        result.name = "Clone-from-"+name;
+        result.cluster = cluster;
+        result.notifyListenersOnReplication = notifyListenersOnReplication;
+        result.mapSendOptions = mapSendOptions;
+        result.maxActiveSessions = maxActiveSessions;
+        return result;
+    }
+
+    @Override
+    public int getActiveSessionsFull() {
+        LazyReplicatedMap map = (LazyReplicatedMap)sessions;
+        return map.sizeFull();
+    }
+
+    @Override
+    public Set<String> getSessionIdsFull() {
+        Set<String> sessionIds = new HashSet<String>();
+        LazyReplicatedMap map = (LazyReplicatedMap)sessions;
+        @SuppressWarnings("unchecked") // sessions is of type Map<String, Session>
+        Iterator<String> keys = map.keySetFull().iterator();
+        while (keys.hasNext()) {
+            sessionIds.add(keys.next());
+        }
+        return sessionIds;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/ClusterManagerBase.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/ClusterManagerBase.java
new file mode 100644
index 0000000..22bd52f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/ClusterManagerBase.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.catalina.ha.session;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Loader;
+import org.apache.catalina.ha.ClusterManager;
+import org.apache.catalina.session.ManagerBase;
+import org.apache.catalina.tribes.io.ReplicationStream;
+
+/**
+ * 
+ * @author Filip Hanik
+ * @version $Id: ClusterManagerBase.java,v 1.1 2011/06/28 21:08:17 rherrmann Exp $
+ */
+
+public abstract class ClusterManagerBase extends ManagerBase
+        implements ClusterManager {
+
+    public static ClassLoader[] getClassLoaders(Container container) {
+        Loader loader = null;
+        ClassLoader classLoader = null;
+        if (container != null) loader = container.getLoader();
+        if (loader != null) classLoader = loader.getClassLoader();
+        else classLoader = Thread.currentThread().getContextClassLoader();
+        if ( classLoader == Thread.currentThread().getContextClassLoader() ) {
+            return new ClassLoader[] {classLoader};
+        } else {
+            return new ClassLoader[] {classLoader,Thread.currentThread().getContextClassLoader()};
+        }
+    }
+
+
+    public ClassLoader[] getClassLoaders() {
+        return getClassLoaders(container);
+    }
+
+    /**
+     * Open Stream and use correct ClassLoader (Container) Switch
+     * ThreadClassLoader
+     * 
+     * @param data
+     * @return The object input stream
+     * @throws IOException
+     */
+    @Override
+    public ReplicationStream getReplicationStream(byte[] data) throws IOException {
+        return getReplicationStream(data,0,data.length);
+    }
+
+    @Override
+    public ReplicationStream getReplicationStream(byte[] data, int offset, int length) throws IOException {
+        ByteArrayInputStream fis = new ByteArrayInputStream(data, offset, length);
+        return new ReplicationStream(fis, getClassLoaders());
+    }    
+
+
+    //  ---------------------------------------------------- persistence handler
+
+    /**
+     * {@link org.apache.catalina.Manager} implementations that also implement
+     * {@link ClusterManager} do not support local session persistence.
+     */
+    @Override
+    public void load() {
+        // NOOP 
+    }
+
+    @Override
+    public void unload() {
+        // NOOP
+    }
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/ClusterSessionListener.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/ClusterSessionListener.java
new file mode 100644
index 0000000..f0aaf13
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/ClusterSessionListener.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.catalina.ha.session;
+
+import java.util.Map;
+
+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 SessionMessage form other cluster node.
+ * @author Filip Hanik
+ * @author Peter Rossbach
+ * @version $Id: ClusterSessionListener.java,v 1.1 2011/06/28 21:08:17 rherrmann Exp $
+ */
+public class ClusterSessionListener extends ClusterListener {
+
+    private static final Log log =
+        LogFactory.getLog(ClusterSessionListener.class);
+    
+    /**
+     * The descriptive information about this implementation.
+     */
+    protected static final String info = "org.apache.catalina.ha.session.ClusterSessionListener/1.1";
+
+    //--Constructor---------------------------------------------
+
+    public ClusterSessionListener() {
+        // NO-OP
+    }
+
+    //--Logic---------------------------------------------------
+
+    /**
+     * Return descriptive information about this implementation.
+     */
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+    /**
+     * 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 SessionMessage) {
+            SessionMessage msg = (SessionMessage) myobj;
+            String ctxname = msg.getContextName();
+            //check if the message is a EVT_GET_ALL_SESSIONS,
+            //if so, wait until we are fully started up
+            Map<String,ClusterManager> managers = cluster.getManagers() ;
+            if (ctxname == null) {
+                for (Map.Entry<String, ClusterManager> entry :
+                        managers.entrySet()) {
+                    if (entry.getValue() != null)
+                        entry.getValue().messageDataReceived(msg);
+                    else {
+                        //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)
+                    mgr.messageDataReceived(msg);
+                else if (log.isWarnEnabled())
+                    log.warn("Context manager doesn't exist:" + ctxname);
+            }
+        }
+        return;
+    }
+
+    /**
+     * Accept only SessionMessage
+     * 
+     * @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 SessionMessage);
+    }
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/Constants.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/Constants.java
new file mode 100644
index 0000000..6fe996b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/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.ha.session;
+
+/**
+ * Manifest constants for the <code>org.apache.catalina.ha.session</code>
+ * package.
+ *
+ * @author Peter Rossbach Pero
+ */
+
+public class Constants {
+
+    public static final String Package = "org.apache.catalina.ha.session";
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/DeltaManager.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/DeltaManager.java
new file mode 100644
index 0000000..86079fe
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/DeltaManager.java
@@ -0,0 +1,1516 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.session;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+
+import org.apache.catalina.Cluster;
+import org.apache.catalina.Container;
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Host;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.Session;
+import org.apache.catalina.Valve;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.ha.CatalinaCluster;
+import org.apache.catalina.ha.ClusterManager;
+import org.apache.catalina.ha.ClusterMessage;
+import org.apache.catalina.ha.tcp.ReplicationValve;
+import org.apache.catalina.session.ManagerBase;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.io.ReplicationStream;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * The DeltaManager manages replicated sessions by only replicating the deltas
+ * in data. For applications written to handle this, the DeltaManager is the
+ * optimal way of replicating data.
+ * 
+ * This code is almost identical to StandardManager with a difference in how it
+ * persists sessions and some modifications to it.
+ * 
+ * <b>IMPLEMENTATION NOTE </b>: Correct behavior of session storing and
+ * reloading depends upon external calls to the <code>start()</code> and
+ * <code>stop()</code> methods of this class at the correct times.
+ * 
+ * @author Filip Hanik
+ * @author Craig R. McClanahan
+ * @author Jean-Francois Arcand
+ * @author Peter Rossbach
+ * @version $Id: DeltaManager.java,v 1.1 2011/06/28 21:08:17 rherrmann Exp $
+ */
+
+public class DeltaManager extends ClusterManagerBase{
+
+    // ---------------------------------------------------- Security Classes
+    public static final org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(DeltaManager.class);
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm = StringManager.getManager(Constants.Package);
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * The descriptive information about this implementation.
+     */
+    private static final String info = "DeltaManager/2.1";
+
+    /**
+     * The descriptive name of this Manager implementation (for logging).
+     */
+    protected static String managerName = "DeltaManager";
+    protected String name = null;
+    private CatalinaCluster cluster = null;
+
+    /**
+     * cached replication valve cluster container!
+     */
+    private volatile ReplicationValve replicationValve = null ;
+    
+    private boolean expireSessionsOnShutdown = false;
+    private boolean notifyListenersOnReplication = true;
+    private boolean notifySessionListenersOnReplication = true;
+    private volatile boolean stateTransfered = false ;
+    private int stateTransferTimeout = 60;
+    private boolean sendAllSessions = true;
+    private int sendAllSessionsSize = 1000 ;
+    
+    /**
+     * wait time between send session block (default 2 sec) 
+     */
+    private int sendAllSessionsWaitTime = 2 * 1000 ; 
+    private ArrayList<SessionMessage> receivedMessageQueue =
+        new ArrayList<SessionMessage>() ;
+    private boolean receiverQueue = false ;
+    private boolean stateTimestampDrop = true ;
+    private long stateTransferCreateSendTime; 
+    
+    // ------------------------------------------------------------------ stats attributes
+    
+    private long sessionReplaceCounter = 0 ;
+    private long counterReceive_EVT_GET_ALL_SESSIONS = 0 ;
+    private long counterReceive_EVT_ALL_SESSION_DATA = 0 ;
+    private long counterReceive_EVT_SESSION_CREATED = 0 ;
+    private long counterReceive_EVT_SESSION_EXPIRED = 0;
+    private long counterReceive_EVT_SESSION_ACCESSED = 0 ;
+    private long counterReceive_EVT_SESSION_DELTA = 0;
+    private int counterReceive_EVT_ALL_SESSION_TRANSFERCOMPLETE = 0 ;
+    private long counterReceive_EVT_CHANGE_SESSION_ID = 0 ;
+    private long counterSend_EVT_GET_ALL_SESSIONS = 0 ;
+    private long counterSend_EVT_ALL_SESSION_DATA = 0 ;
+    private long counterSend_EVT_SESSION_CREATED = 0;
+    private long counterSend_EVT_SESSION_DELTA = 0 ;
+    private long counterSend_EVT_SESSION_ACCESSED = 0;
+    private long counterSend_EVT_SESSION_EXPIRED = 0;
+    private int counterSend_EVT_ALL_SESSION_TRANSFERCOMPLETE = 0 ;
+    private long counterSend_EVT_CHANGE_SESSION_ID = 0;
+    private int counterNoStateTransfered = 0 ;
+
+    // ------------------------------------------------------------- Constructor
+    public DeltaManager() {
+        super();
+    }
+
+    // ------------------------------------------------------------- Properties
+    
+    /**
+     * Return descriptive information about this Manager implementation and the
+     * corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    @Override
+    public String getInfo() {
+        return info;
+    }
+
+    @Override
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Return the descriptive short name of this Manager implementation.
+     */
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * @return Returns the counterSend_EVT_GET_ALL_SESSIONS.
+     */
+    public long getCounterSend_EVT_GET_ALL_SESSIONS() {
+        return counterSend_EVT_GET_ALL_SESSIONS;
+    }
+    
+    /**
+     * @return Returns the counterSend_EVT_SESSION_ACCESSED.
+     */
+    public long getCounterSend_EVT_SESSION_ACCESSED() {
+        return counterSend_EVT_SESSION_ACCESSED;
+    }
+    
+    /**
+     * @return Returns the counterSend_EVT_SESSION_CREATED.
+     */
+    public long getCounterSend_EVT_SESSION_CREATED() {
+        return counterSend_EVT_SESSION_CREATED;
+    }
+
+    /**
+     * @return Returns the counterSend_EVT_SESSION_DELTA.
+     */
+    public long getCounterSend_EVT_SESSION_DELTA() {
+        return counterSend_EVT_SESSION_DELTA;
+    }
+
+    /**
+     * @return Returns the counterSend_EVT_SESSION_EXPIRED.
+     */
+    public long getCounterSend_EVT_SESSION_EXPIRED() {
+        return counterSend_EVT_SESSION_EXPIRED;
+    }
+ 
+    /**
+     * @return Returns the counterSend_EVT_ALL_SESSION_DATA.
+     */
+    public long getCounterSend_EVT_ALL_SESSION_DATA() {
+        return counterSend_EVT_ALL_SESSION_DATA;
+    }
+
+    /**
+     * @return Returns the counterSend_EVT_ALL_SESSION_TRANSFERCOMPLETE.
+     */
+    public int getCounterSend_EVT_ALL_SESSION_TRANSFERCOMPLETE() {
+        return counterSend_EVT_ALL_SESSION_TRANSFERCOMPLETE;
+    }
+
+    /**
+     * @return Returns the counterSend_EVT_CHANGE_SESSION_ID.
+     */
+    public long getCounterSend_EVT_CHANGE_SESSION_ID() {
+        return counterSend_EVT_CHANGE_SESSION_ID;
+    }
+
+    /**
+     * @return Returns the counterReceive_EVT_ALL_SESSION_DATA.
+     */
+    public long getCounterReceive_EVT_ALL_SESSION_DATA() {
+        return counterReceive_EVT_ALL_SESSION_DATA;
+    }
+    
+    /**
+     * @return Returns the counterReceive_EVT_GET_ALL_SESSIONS.
+     */
+    public long getCounterReceive_EVT_GET_ALL_SESSIONS() {
+        return counterReceive_EVT_GET_ALL_SESSIONS;
+    }
+    
+    /**
+     * @return Returns the counterReceive_EVT_SESSION_ACCESSED.
+     */
+    public long getCounterReceive_EVT_SESSION_ACCESSED() {
+        return counterReceive_EVT_SESSION_ACCESSED;
+    }
+    
+    /**
+     * @return Returns the counterReceive_EVT_SESSION_CREATED.
+     */
+    public long getCounterReceive_EVT_SESSION_CREATED() {
+        return counterReceive_EVT_SESSION_CREATED;
+    }
+    
+    /**
+     * @return Returns the counterReceive_EVT_SESSION_DELTA.
+     */
+    public long getCounterReceive_EVT_SESSION_DELTA() {
+        return counterReceive_EVT_SESSION_DELTA;
+    }
+    
+    /**
+     * @return Returns the counterReceive_EVT_SESSION_EXPIRED.
+     */
+    public long getCounterReceive_EVT_SESSION_EXPIRED() {
+        return counterReceive_EVT_SESSION_EXPIRED;
+    }
+    
+    
+    /**
+     * @return Returns the counterReceive_EVT_ALL_SESSION_TRANSFERCOMPLETE.
+     */
+    public int getCounterReceive_EVT_ALL_SESSION_TRANSFERCOMPLETE() {
+        return counterReceive_EVT_ALL_SESSION_TRANSFERCOMPLETE;
+    }
+
+    /**
+     * @return Returns the counterReceive_EVT_CHANGE_SESSION_ID.
+     */
+    public long getCounterReceive_EVT_CHANGE_SESSION_ID() {
+        return counterReceive_EVT_CHANGE_SESSION_ID;
+    }
+    
+    /**
+     * @return Returns the processingTime.
+     */
+    @Override
+    public long getProcessingTime() {
+        return processingTime;
+    }
+ 
+    /**
+     * @return Returns the sessionReplaceCounter.
+     */
+    public long getSessionReplaceCounter() {
+        return sessionReplaceCounter;
+    }
+    
+    /**
+     * @return Returns the counterNoStateTransfered.
+     */
+    public int getCounterNoStateTransfered() {
+        return counterNoStateTransfered;
+    }
+    
+    public int getReceivedQueueSize() {
+        return receivedMessageQueue.size() ;
+    }
+    
+    /**
+     * @return Returns the stateTransferTimeout.
+     */
+    public int getStateTransferTimeout() {
+        return stateTransferTimeout;
+    }
+    /**
+     * @param timeoutAllSession The timeout
+     */
+    public void setStateTransferTimeout(int timeoutAllSession) {
+        this.stateTransferTimeout = timeoutAllSession;
+    }
+
+    /**
+     * is session state transfered complete?
+     * 
+     */
+    public boolean getStateTransfered() {
+        return stateTransfered;
+    }
+
+    /**
+     * set that state ist complete transfered  
+     * @param stateTransfered
+     */
+    public void setStateTransfered(boolean stateTransfered) {
+        this.stateTransfered = stateTransfered;
+    }
+    
+    /**
+     * @return Returns the sendAllSessionsWaitTime in msec
+     */
+    public int getSendAllSessionsWaitTime() {
+        return sendAllSessionsWaitTime;
+    }
+    
+    /**
+     * @param sendAllSessionsWaitTime The sendAllSessionsWaitTime to set at msec.
+     */
+    public void setSendAllSessionsWaitTime(int sendAllSessionsWaitTime) {
+        this.sendAllSessionsWaitTime = sendAllSessionsWaitTime;
+    }
+    
+    /**
+     * @return Returns the stateTimestampDrop.
+     */
+    public boolean isStateTimestampDrop() {
+        return stateTimestampDrop;
+    }
+    
+    /**
+     * @param isTimestampDrop The new flag value
+     */
+    public void setStateTimestampDrop(boolean isTimestampDrop) {
+        this.stateTimestampDrop = isTimestampDrop;
+    }
+    
+    /**
+     * 
+     * @return Returns the sendAllSessions.
+     */
+    public boolean isSendAllSessions() {
+        return sendAllSessions;
+    }
+    
+    /**
+     * @param sendAllSessions The sendAllSessions to set.
+     */
+    public void setSendAllSessions(boolean sendAllSessions) {
+        this.sendAllSessions = sendAllSessions;
+    }
+    
+    /**
+     * @return Returns the sendAllSessionsSize.
+     */
+    public int getSendAllSessionsSize() {
+        return sendAllSessionsSize;
+    }
+    
+    /**
+     * @param sendAllSessionsSize The sendAllSessionsSize to set.
+     */
+    public void setSendAllSessionsSize(int sendAllSessionsSize) {
+        this.sendAllSessionsSize = sendAllSessionsSize;
+    }
+    
+    /**
+     * @return Returns the notifySessionListenersOnReplication.
+     */
+    public boolean isNotifySessionListenersOnReplication() {
+        return notifySessionListenersOnReplication;
+    }
+    
+    /**
+     * @param notifyListenersCreateSessionOnReplication The notifySessionListenersOnReplication to set.
+     */
+    public void setNotifySessionListenersOnReplication(boolean notifyListenersCreateSessionOnReplication) {
+        this.notifySessionListenersOnReplication = notifyListenersCreateSessionOnReplication;
+    }
+    
+    
+    public boolean isExpireSessionsOnShutdown() {
+        return expireSessionsOnShutdown;
+    }
+
+    public void setExpireSessionsOnShutdown(boolean expireSessionsOnShutdown) {
+        this.expireSessionsOnShutdown = expireSessionsOnShutdown;
+    }
+    
+    @Override
+    public boolean isNotifyListenersOnReplication() {
+        return notifyListenersOnReplication;
+    }
+
+    public void setNotifyListenersOnReplication(boolean notifyListenersOnReplication) {
+        this.notifyListenersOnReplication = notifyListenersOnReplication;
+    }
+
+    
+   @Override
+public CatalinaCluster getCluster() {
+        return cluster;
+    }
+
+    @Override
+    public void setCluster(CatalinaCluster cluster) {
+        this.cluster = cluster;
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Construct and return a new session object, based on the default settings
+     * specified by this Manager's properties. The session id will be assigned
+     * by this method, and available via the getId() method of the returned
+     * session. If a new session cannot be created for any reason, return
+     * <code>null</code>.
+     * 
+     * @exception IllegalStateException
+     *                if a new session cannot be instantiated for any reason
+     * 
+     * Construct and return a new session object, based on the default settings
+     * specified by this Manager's properties. The session id will be assigned
+     * by this method, and available via the getId() method of the returned
+     * session. If a new session cannot be created for any reason, return
+     * <code>null</code>.
+     * 
+     * @exception IllegalStateException
+     *                if a new session cannot be instantiated for any reason
+     */
+    @Override
+    public Session createSession(String sessionId) {
+        return createSession(sessionId, true);
+    }
+
+    /**
+     * create new session with check maxActiveSessions and send session creation
+     * to other cluster nodes.
+     * 
+     * @param distribute
+     * @return The session
+     */
+    public Session createSession(String sessionId, boolean distribute) {
+        DeltaSession session = (DeltaSession) super.createSession(sessionId) ;
+        if (distribute) {
+            sendCreateSession(session.getId(), session);
+        }
+        if (log.isDebugEnabled())
+            log.debug(sm.getString("deltaManager.createSession.newSession",
+                    session.getId(), Integer.valueOf(sessions.size())));
+        return (session);
+    }
+
+    /**
+     * Send create session evt to all backup node
+     * @param sessionId
+     * @param session
+     */
+    protected void sendCreateSession(String sessionId, DeltaSession session) {
+        if(cluster.getMembers().length > 0 ) {
+            SessionMessage msg = 
+                new SessionMessageImpl(getName(),
+                                       SessionMessage.EVT_SESSION_CREATED, 
+                                       null, 
+                                       sessionId,
+                                       sessionId + "-" + System.currentTimeMillis());
+            if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.sendMessage.newSession",name, sessionId));
+            msg.setTimestamp(session.getCreationTime());
+            counterSend_EVT_SESSION_CREATED++;
+            send(msg);
+        }
+    }
+    
+    /**
+     * Send messages to other backup member (domain or all)
+     * @param msg Session message
+     */
+    protected void send(SessionMessage msg) {
+        if(cluster != null) {
+            cluster.send(msg);
+        }
+    }
+
+    /**
+     * Create DeltaSession
+     * @see org.apache.catalina.Manager#createEmptySession()
+     */
+    @Override
+    public Session createEmptySession() {
+        return getNewDeltaSession() ;
+    }
+    
+    /**
+     * Get new session class to be used in the doLoad() method.
+     */
+    protected DeltaSession getNewDeltaSession() {
+        return new DeltaSession(this);
+    }
+
+    /**
+     * Change the session ID of the current session to a new randomly generated
+     * session ID.
+     * 
+     * @param session   The session to change the session ID for
+     */
+    @Override
+    public void changeSessionId(Session session) {
+        changeSessionId(session, true);
+    }
+
+    public void changeSessionId(Session session, boolean notify) {
+        // original sessionID
+        String orgSessionID = session.getId();
+        super.changeSessionId(session);
+        if (notify) {
+            // changed sessionID
+            String newSessionID = session.getId();
+            try {
+                // serialize sessionID
+                byte[] data = serializeSessionId(newSessionID);
+                // notify change sessionID
+                SessionMessage msg = new SessionMessageImpl(getName(),
+                        SessionMessage.EVT_CHANGE_SESSION_ID, data,
+                        orgSessionID, orgSessionID + "-"
+                                + System.currentTimeMillis());
+                msg.setTimestamp(System.currentTimeMillis());
+                counterSend_EVT_CHANGE_SESSION_ID++;
+                send(msg);
+            } catch (IOException e) {
+                log.error(sm.getString("deltaManager.unableSerializeSessionID",
+                        newSessionID), e);
+            }
+        }
+    }
+
+    /**
+     * serialize sessionID
+     * @throws IOException if an input/output error occurs
+     */
+    protected byte[] serializeSessionId(String sessionId) throws IOException {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        ObjectOutputStream oos = new ObjectOutputStream(bos);
+        oos.writeUTF(sessionId);
+        oos.flush();
+        oos.close();
+        return bos.toByteArray();
+    }
+
+    /**
+     * Load sessionID
+     * @throws IOException if an input/output error occurs
+     */
+    protected String deserializeSessionId(byte[] data) throws IOException {
+        ReplicationStream ois = getReplicationStream(data);
+        String sessionId = ois.readUTF();
+        ois.close();
+        return sessionId;
+    }
+
+    /**
+     * Load Deltarequest from external node
+     * Load the Class at container classloader
+     * @see DeltaRequest#readExternal(java.io.ObjectInput)
+     * @param session
+     * @param data message data
+     * @return The request
+     * @throws ClassNotFoundException
+     * @throws IOException
+     */
+    protected DeltaRequest deserializeDeltaRequest(DeltaSession session, byte[] data) throws ClassNotFoundException, IOException {
+        try {
+            session.lock();
+            ReplicationStream ois = getReplicationStream(data);
+            session.getDeltaRequest().readExternal(ois);
+            ois.close();
+            return session.getDeltaRequest();
+        }finally {
+            session.unlock();
+        }
+    }
+
+    /**
+     * serialize DeltaRequest
+     * @see DeltaRequest#writeExternal(java.io.ObjectOutput)
+     * 
+     * @param deltaRequest
+     * @return serialized delta request
+     * @throws IOException
+     */
+    protected byte[] serializeDeltaRequest(DeltaSession session, DeltaRequest deltaRequest) throws IOException {
+        try {
+            session.lock();
+            return deltaRequest.serialize();
+        }finally {
+            session.unlock();
+        }
+    }
+
+    /**
+     * Load sessions from other cluster node.
+     * FIXME replace currently sessions with same id without notification.
+     * FIXME SSO handling is not really correct with the session replacement!
+     * @exception ClassNotFoundException
+     *                if a serialized class cannot be found during the reload
+     * @exception IOException
+     *                if an input/output error occurs
+     */
+    protected void deserializeSessions(byte[] data) throws ClassNotFoundException,IOException {
+
+        // Initialize our internal data structures
+        //sessions.clear(); //should not do this
+        // Open an input stream to the specified pathname, if any
+        ClassLoader originalLoader = Thread.currentThread().getContextClassLoader();
+        ObjectInputStream ois = null;
+        // Load the previously unloaded active sessions
+        try {
+            ois = getReplicationStream(data);
+            Integer count = (Integer) ois.readObject();
+            int n = count.intValue();
+            for (int i = 0; i < n; i++) {
+                DeltaSession session = (DeltaSession) createEmptySession();
+                session.readObjectData(ois);
+                session.setManager(this);
+                session.setValid(true);
+                session.setPrimarySession(false);
+                //in case the nodes in the cluster are out of
+                //time synch, this will make sure that we have the
+                //correct timestamp, isValid returns true, cause
+                // accessCount=1
+                session.access();
+                //make sure that the session gets ready to expire if
+                // needed
+                session.setAccessCount(0);
+                session.resetDeltaRequest();
+                // FIXME How inform other session id cache like SingleSignOn
+                // increment sessionCounter to correct stats report
+                if (findSession(session.getIdInternal()) == null ) {
+                    sessionCounter++;
+                } else {
+                    sessionReplaceCounter++;
+                    // FIXME better is to grap this sessions again !
+                    if (log.isWarnEnabled()) log.warn(sm.getString("deltaManager.loading.existing.session",session.getIdInternal()));
+                }
+                add(session);
+                if (notifySessionListenersOnReplication) {
+                    session.tellNew();
+                }
+            }
+        } catch (ClassNotFoundException e) {
+            log.error(sm.getString("deltaManager.loading.cnfe", e), e);
+            throw e;
+        } catch (IOException e) {
+            log.error(sm.getString("deltaManager.loading.ioe", e), e);
+            throw e;
+        } finally {
+            // Close the input stream
+            try {
+                if (ois != null) ois.close();
+            } catch (IOException f) {
+                // ignored
+            }
+            ois = null;
+            if (originalLoader != null) Thread.currentThread().setContextClassLoader(originalLoader);
+        }
+
+    }
+
+
+    /**
+     * Save any currently active sessions in the appropriate persistence
+     * mechanism, if any. If persistence is not supported, this method returns
+     * without doing anything.
+     * 
+     * @exception IOException
+     *                if an input/output error occurs
+     */
+    protected byte[] serializeSessions(Session[] currentSessions) throws IOException {
+
+        // Open an output stream to the specified pathname, if any
+        ByteArrayOutputStream fos = null;
+        ObjectOutputStream oos = null;
+
+        try {
+            fos = new ByteArrayOutputStream();
+            oos = new ObjectOutputStream(new BufferedOutputStream(fos));
+            oos.writeObject(Integer.valueOf(currentSessions.length));
+            for(int i=0 ; i < currentSessions.length;i++) {
+                ((DeltaSession)currentSessions[i]).writeObjectData(oos);                
+            }
+            // Flush and close the output stream
+            oos.flush();
+        } catch (IOException e) {
+            log.error(sm.getString("deltaManager.unloading.ioe", e), e);
+            throw e;
+        } finally {
+            if (oos != null) {
+                try {
+                    oos.close();
+                } catch (IOException f) {
+                    // Ignore
+                }
+                oos = null;
+            }
+        }
+        // send object data as byte[]
+        return fos.toByteArray();
+    }
+
+    /**
+     * 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 {
+
+        super.startInternal();
+
+        // Load unloaded sessions, if any
+        try {
+            //the channel is already running
+            Cluster cluster = getCluster() ;
+            // stop remove cluster binding
+            //wow, how many nested levels of if statements can we have ;)
+            if(cluster == null) {
+                Container context = getContainer() ;
+                if(context != null && context instanceof Context) {
+                     Container host = context.getParent() ;
+                     if(host != null && host instanceof Host) {
+                         cluster = host.getCluster();
+                         if(cluster != null && cluster instanceof CatalinaCluster) {
+                             setCluster((CatalinaCluster) cluster) ;
+                         } else {
+                             Container engine = host.getParent() ;
+                             if(engine != null && engine instanceof Engine) {
+                                 cluster = engine.getCluster();
+                                 if(cluster != null && cluster instanceof CatalinaCluster) {
+                                     setCluster((CatalinaCluster) cluster) ;
+                                 }
+                             } else {
+                                     cluster = null ;
+                             }
+                         }
+                     }
+                }
+            }
+            if (cluster == null) {
+                log.error(sm.getString("deltaManager.noCluster", getName()));
+                return;
+            } else {
+                if (log.isInfoEnabled()) {
+                    String type = "unknown" ;
+                    if( cluster.getContainer() instanceof Host){
+                        type = "Host" ;
+                    } else if( cluster.getContainer() instanceof Engine){
+                        type = "Engine" ;
+                    }
+                    log.info(sm.getString("deltaManager.registerCluster", getName(), type, cluster.getClusterName()));
+                }
+            }
+            if (log.isInfoEnabled()) log.info(sm.getString("deltaManager.startClustering", getName()));
+            //to survice context reloads, as only a stop/start is called, not
+            // createManager
+            cluster.registerManager(this);
+
+            getAllClusterSessions();
+
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.error(sm.getString("deltaManager.managerLoad"), t);
+        }
+        
+        setState(LifecycleState.STARTING);
+    }
+
+    /**
+     * get from first session master the backup from all clustered sessions
+     * @see #findSessionMasterMember()
+     */
+    public synchronized void getAllClusterSessions() {
+        if (cluster != null && cluster.getMembers().length > 0) {
+            long beforeSendTime = System.currentTimeMillis();
+            Member mbr = findSessionMasterMember();
+            if(mbr == null) { // No domain member found
+                 return;
+            }
+            SessionMessage msg = new SessionMessageImpl(this.getName(),SessionMessage.EVT_GET_ALL_SESSIONS, null, "GET-ALL","GET-ALL-" + getName());
+            // set reference time
+            stateTransferCreateSendTime = beforeSendTime ;
+            // request session state
+            counterSend_EVT_GET_ALL_SESSIONS++;
+            stateTransfered = false ;
+            // FIXME This send call block the deploy thread, when sender waitForAck is enabled
+            try {
+                synchronized(receivedMessageQueue) {
+                     receiverQueue = true ;
+                }
+                cluster.send(msg, mbr);
+                if (log.isInfoEnabled())
+                    log.info(sm.getString("deltaManager.waitForSessionState",getName(), mbr, Integer.valueOf(getStateTransferTimeout())));
+                // FIXME At sender ack mode this method check only the state transfer and resend is a problem!
+                waitForSendAllSessions(beforeSendTime);
+            } finally {
+                synchronized(receivedMessageQueue) {
+                    for (Iterator<SessionMessage> iter = receivedMessageQueue.iterator(); iter.hasNext();) {
+                        SessionMessage smsg = iter.next();
+                        if (!stateTimestampDrop) {
+                            messageReceived(smsg, smsg.getAddress() != null ? (Member) smsg.getAddress() : null);
+                        } else {
+                            if (smsg.getEventType() != SessionMessage.EVT_GET_ALL_SESSIONS && smsg.getTimestamp() >= stateTransferCreateSendTime) {
+                                // FIXME handle EVT_GET_ALL_SESSIONS later
+                                messageReceived(smsg,smsg.getAddress() != null ? (Member) smsg.getAddress() : null);
+                            } else {
+                                if (log.isWarnEnabled()) {
+                                    log.warn(sm.getString("deltaManager.dropMessage",getName(), smsg.getEventTypeString(),new Date(stateTransferCreateSendTime), new Date(smsg.getTimestamp())));
+                                }
+                            }
+                        }
+                    }        
+                    receivedMessageQueue.clear();
+                    receiverQueue = false ;
+                }
+           }
+        } else {
+            if (log.isInfoEnabled()) log.info(sm.getString("deltaManager.noMembers", getName()));
+        }
+    }
+
+    /**
+     * Register cross context session at replication valve thread local
+     * @param session cross context session
+     */
+    protected void registerSessionAtReplicationValve(DeltaSession session) {
+        if(replicationValve == null) {
+            if(container instanceof StandardContext && ((StandardContext)container).getCrossContext()) {
+                CatalinaCluster cluster = getCluster() ;
+                if(cluster != null) {
+                    Valve[] valves = cluster.getValves();
+                    if(valves != null && valves.length > 0) {
+                        for(int i=0; replicationValve == null && i < valves.length ; i++ ){
+                            if(valves[i] instanceof ReplicationValve) replicationValve = (ReplicationValve)valves[i] ;
+                        }//for
+
+                        if(replicationValve == null && log.isDebugEnabled()) {
+                            log.debug("no ReplicationValve found for CrossContext Support");
+                        }//endif 
+                    }//end if
+                }//endif
+            }//end if
+        }//end if
+        if(replicationValve != null) {
+            replicationValve.registerReplicationSession(session);
+        }
+    }
+    
+    /**
+     * Find the master of the session state
+     * @return master member of sessions 
+     */
+    protected Member findSessionMasterMember() {
+        Member mbr = null;
+        Member mbrs[] = cluster.getMembers();
+        if(mbrs.length != 0 ) mbr = mbrs[0];
+        if(mbr == null && log.isWarnEnabled()) log.warn(sm.getString("deltaManager.noMasterMember",getName(), ""));
+        if(mbr != null && log.isDebugEnabled()) log.warn(sm.getString("deltaManager.foundMasterMember",getName(), mbr));
+        return mbr;
+    }
+
+    /**
+     * Wait that cluster session state is transfer or timeout after 60 Sec
+     * With stateTransferTimeout == -1 wait that backup is transfered (forever mode)
+     */
+    protected void waitForSendAllSessions(long beforeSendTime) {
+        long reqStart = System.currentTimeMillis();
+        long reqNow = reqStart ;
+        boolean isTimeout = false;
+        if(getStateTransferTimeout() > 0) {
+            // wait that state is transfered with timeout check
+            do {
+                try {
+                    Thread.sleep(100);
+                } catch (Exception sleep) {
+                    //
+                }
+                reqNow = System.currentTimeMillis();
+                isTimeout = ((reqNow - reqStart) > (1000 * getStateTransferTimeout()));
+            } while ((!getStateTransfered()) && (!isTimeout));
+        } else {
+            if(getStateTransferTimeout() == -1) {
+                // wait that state is transfered
+                do {
+                    try {
+                        Thread.sleep(100);
+                    } catch (Exception sleep) {
+                    }
+                } while ((!getStateTransfered()));
+                reqNow = System.currentTimeMillis();
+            }
+        }
+        if (isTimeout || (!getStateTransfered())) {
+            counterNoStateTransfered++ ;
+            log.error(sm.getString("deltaManager.noSessionState",getName(),new Date(beforeSendTime),Long.valueOf(reqNow - beforeSendTime)));
+        } else {
+            if (log.isInfoEnabled())
+                log.info(sm.getString("deltaManager.sessionReceived",getName(), new Date(beforeSendTime), Long.valueOf(reqNow - beforeSendTime)));
+        }
+    }
+
+    /**
+     * 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 {
+
+        if (log.isDebugEnabled())
+            log.debug(sm.getString("deltaManager.stopped", getName()));
+
+        setState(LifecycleState.STOPPING);
+        
+        // Expire all active sessions
+        if (log.isInfoEnabled()) log.info(sm.getString("deltaManager.expireSessions", getName()));
+        Session sessions[] = findSessions();
+        for (int i = 0; i < sessions.length; i++) {
+            DeltaSession session = (DeltaSession) sessions[i];
+            if (!session.isValid())
+                continue;
+            try {
+                session.expire(true, isExpireSessionsOnShutdown());
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+            } 
+        }
+
+        // Require a new random number generator if we are restarted
+        getCluster().removeManager(this);
+        super.stopInternal();
+        replicationValve = null;
+    }
+
+    // -------------------------------------------------------- Replication
+    // Methods
+
+    /**
+     * A message was received from another node, this is the callback method to
+     * implement if you are interested in receiving replication messages.
+     * 
+     * @param cmsg -
+     *            the message received.
+     */
+    @Override
+    public void messageDataReceived(ClusterMessage cmsg) {
+        if (cmsg != null && cmsg instanceof SessionMessage) {
+            SessionMessage msg = (SessionMessage) cmsg;
+            switch (msg.getEventType()) {
+                case SessionMessage.EVT_GET_ALL_SESSIONS:
+                case SessionMessage.EVT_SESSION_CREATED: 
+                case SessionMessage.EVT_SESSION_EXPIRED: 
+                case SessionMessage.EVT_SESSION_ACCESSED:
+                case SessionMessage.EVT_SESSION_DELTA:
+                case SessionMessage.EVT_CHANGE_SESSION_ID: {
+                    synchronized(receivedMessageQueue) {
+                        if(receiverQueue) {
+                            receivedMessageQueue.add(msg);
+                            return ;
+                        }
+                    }
+                   break;
+                }
+                default: {
+                    //we didn't queue, do nothing
+                    break;
+                }
+            } //switch
+            
+            messageReceived(msg, msg.getAddress() != null ? (Member) msg.getAddress() : null);
+        }
+    }
+
+    /**
+     * 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,
+     */
+    @Override
+    public ClusterMessage requestCompleted(String sessionId) {
+         return requestCompleted(sessionId, false);
+     }
+
+     /**
+      * 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.
+      * 
+      * Session expiration also calls this method, but with expires == true.
+      * 
+      * @param sessionId -
+      *            the sessionId that just completed.
+      * @param expires -
+      *            whether this method has been called during session expiration
+      * @return a SessionMessage to be sent,
+      */
+     public ClusterMessage requestCompleted(String sessionId, boolean expires) {
+        DeltaSession session = null;
+        try {
+            session = (DeltaSession) findSession(sessionId);
+            if (session == null) {
+                // A parallel request has called session.invalidate() which has
+                // removed the session from the Manager.
+                return null;
+            }
+            DeltaRequest deltaRequest = session.getDeltaRequest();
+            session.lock();
+            SessionMessage msg = null;
+            boolean isDeltaRequest = false ;
+            synchronized(deltaRequest) {
+                isDeltaRequest = deltaRequest.getSize() > 0 ;
+                if (isDeltaRequest) {    
+                    counterSend_EVT_SESSION_DELTA++;
+                    byte[] data = serializeDeltaRequest(session,deltaRequest);
+                    msg = new SessionMessageImpl(getName(),
+                                                 SessionMessage.EVT_SESSION_DELTA, 
+                                                 data, 
+                                                 sessionId,
+                                                 sessionId + "-" + System.currentTimeMillis());
+                    session.resetDeltaRequest();
+                }  
+            }
+            if(!isDeltaRequest) {
+                if(!expires && !session.isPrimarySession()) {
+                    counterSend_EVT_SESSION_ACCESSED++;
+                    msg = new SessionMessageImpl(getName(),
+                                                 SessionMessage.EVT_SESSION_ACCESSED, 
+                                                 null, 
+                                                 sessionId,
+                                                 sessionId + "-" + System.currentTimeMillis());
+                    if (log.isDebugEnabled()) {
+                        log.debug(sm.getString("deltaManager.createMessage.accessChangePrimary",getName(), sessionId));
+                    }
+                }    
+            } else { // log only outside synch block!
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString("deltaManager.createMessage.delta",getName(), sessionId));
+                }
+            }
+            if (!expires)
+                session.setPrimarySession(true);
+            //check to see if we need to send out an access message
+            if (!expires && (msg == null)) {
+                long replDelta = System.currentTimeMillis() - session.getLastTimeReplicated();
+                if (session.getMaxInactiveInterval() >=0 && 
+                        replDelta > (session.getMaxInactiveInterval() * 1000)) {
+                    counterSend_EVT_SESSION_ACCESSED++;
+                    msg = new SessionMessageImpl(getName(),
+                                                 SessionMessage.EVT_SESSION_ACCESSED, 
+                                                 null,
+                                                 sessionId, 
+                                                 sessionId + "-" + System.currentTimeMillis());
+                    if (log.isDebugEnabled()) {
+                        log.debug(sm.getString("deltaManager.createMessage.access", getName(),sessionId));
+                    }
+                }
+
+            }
+
+            //update last replicated time
+            if (msg != null){
+               session.setLastTimeReplicated(System.currentTimeMillis());
+               msg.setTimestamp(session.getLastTimeReplicated());
+            }
+            return msg;
+        } catch (IOException x) {
+            log.error(sm.getString("deltaManager.createMessage.unableCreateDeltaRequest",sessionId), x);
+            return null;
+        }finally {
+            if (session!=null) session.unlock();
+        }
+
+    }
+    /**
+     * Reset manager statistics
+     */
+    public synchronized void resetStatistics() {
+        processingTime = 0 ;
+        expiredSessions.set(0);
+        synchronized (sessionCreationTiming) {
+            sessionCreationTiming.clear();
+            while (sessionCreationTiming.size() <
+                    ManagerBase.TIMING_STATS_CACHE_SIZE) {
+                sessionCreationTiming.add(null);
+            }
+        }
+        synchronized (sessionExpirationTiming) {
+            sessionExpirationTiming.clear();
+            while (sessionExpirationTiming.size() <
+                    ManagerBase.TIMING_STATS_CACHE_SIZE) {
+                sessionExpirationTiming.add(null);
+            }
+        }
+        rejectedSessions = 0 ;
+        sessionReplaceCounter = 0 ;
+        counterNoStateTransfered = 0 ;
+        setMaxActive(getActiveSessions());
+        sessionCounter = getActiveSessions() ;
+        counterReceive_EVT_ALL_SESSION_DATA = 0;
+        counterReceive_EVT_GET_ALL_SESSIONS = 0;
+        counterReceive_EVT_SESSION_ACCESSED = 0 ;
+        counterReceive_EVT_SESSION_CREATED = 0 ;
+        counterReceive_EVT_SESSION_DELTA = 0 ;
+        counterReceive_EVT_SESSION_EXPIRED = 0 ;
+        counterReceive_EVT_ALL_SESSION_TRANSFERCOMPLETE = 0;
+        counterReceive_EVT_CHANGE_SESSION_ID = 0;
+        counterSend_EVT_ALL_SESSION_DATA = 0;
+        counterSend_EVT_GET_ALL_SESSIONS = 0;
+        counterSend_EVT_SESSION_ACCESSED = 0 ;
+        counterSend_EVT_SESSION_CREATED = 0 ;
+        counterSend_EVT_SESSION_DELTA = 0 ;
+        counterSend_EVT_SESSION_EXPIRED = 0 ;
+        counterSend_EVT_ALL_SESSION_TRANSFERCOMPLETE = 0;
+        counterSend_EVT_CHANGE_SESSION_ID = 0;
+        
+    }
+   
+    //  -------------------------------------------------------- expire
+
+    /**
+     * send session expired to other cluster nodes
+     * 
+     * @param id
+     *            session id
+     */
+    protected void sessionExpired(String id) {
+        counterSend_EVT_SESSION_EXPIRED++ ;
+        SessionMessage msg = new SessionMessageImpl(getName(),SessionMessage.EVT_SESSION_EXPIRED, null, id, id+ "-EXPIRED-MSG");
+        msg.setTimestamp(System.currentTimeMillis());
+        if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.createMessage.expire",getName(), id));
+        send(msg);
+    }
+
+    /**
+     * Expire all find sessions.
+     */
+    public void expireAllLocalSessions()
+    {
+        long timeNow = System.currentTimeMillis();
+        Session sessions[] = findSessions();
+        int expireDirect  = 0 ;
+        int expireIndirect = 0 ;
+        
+        if(log.isDebugEnabled()) log.debug("Start expire all sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length);
+        for (int i = 0; i < sessions.length; i++) {
+            if (sessions[i] instanceof DeltaSession) {
+                DeltaSession session = (DeltaSession) sessions[i];
+                if (session.isPrimarySession()) {
+                    if (session.isValid()) {
+                        session.expire();
+                        expireDirect++;
+                    } else {
+                        expireIndirect++;
+                    }//end if
+                }//end if
+            }//end if
+        }//for
+        long timeEnd = System.currentTimeMillis();
+        if(log.isDebugEnabled()) log.debug("End expire sessions " + getName() + " exipre processingTime " + (timeEnd - timeNow) + " expired direct sessions: " + expireDirect + " expired direct sessions: " + expireIndirect);
+      
+    }
+    
+    /**
+     * 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 The invalidated sessions array
+     */
+    @Override
+    public String[] getInvalidatedSessions() {
+        return new String[0];
+    }
+
+    //  -------------------------------------------------------- message receive
+
+    /**
+     * Test that sender and local domain is the same
+     */
+    protected boolean checkSenderDomain(SessionMessage msg,Member sender) {
+        boolean sameDomain= true;
+        if (!sameDomain && log.isWarnEnabled()) {
+                log.warn(sm.getString("deltaManager.receiveMessage.fromWrongDomain",
+                         new Object[] {getName(), 
+                         msg.getEventTypeString(), 
+                         sender,
+                         "",
+                         "" }));
+        }
+        return sameDomain ;
+    }
+
+    /**
+     * This method is called by the received thread when a SessionMessage has
+     * been received from one of the other nodes in the cluster.
+     * 
+     * @param msg -
+     *            the message received
+     * @param sender -
+     *            the sender of the message, this is used if we receive a
+     *            EVT_GET_ALL_SESSION message, so that we only reply to the
+     *            requesting node
+     */
+    protected void messageReceived(SessionMessage msg, Member sender) {
+        if(!checkSenderDomain(msg,sender)) {
+            return;
+        }
+        ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+        try {
+            
+            ClassLoader[] loaders = getClassLoaders();
+            if ( loaders != null && loaders.length > 0) Thread.currentThread().setContextClassLoader(loaders[0]);
+            if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.eventType",getName(), msg.getEventTypeString(), sender));
+ 
+            switch (msg.getEventType()) {
+                case SessionMessage.EVT_GET_ALL_SESSIONS: {
+                    handleGET_ALL_SESSIONS(msg,sender);
+                    break;
+                }
+                case SessionMessage.EVT_ALL_SESSION_DATA: {
+                    handleALL_SESSION_DATA(msg,sender);
+                    break;
+                }
+                case SessionMessage.EVT_ALL_SESSION_TRANSFERCOMPLETE: {
+                    handleALL_SESSION_TRANSFERCOMPLETE(msg,sender);
+                    break;
+                }
+                case SessionMessage.EVT_SESSION_CREATED: {
+                    handleSESSION_CREATED(msg,sender);
+                    break;
+                }
+                case SessionMessage.EVT_SESSION_EXPIRED: {
+                    handleSESSION_EXPIRED(msg,sender);
+                    break;
+                }
+                case SessionMessage.EVT_SESSION_ACCESSED: {
+                    handleSESSION_ACCESSED(msg,sender);
+                    break;
+                }
+                case SessionMessage.EVT_SESSION_DELTA: {
+                   handleSESSION_DELTA(msg,sender);
+                   break;
+                }
+                case SessionMessage.EVT_CHANGE_SESSION_ID: {
+                    handleCHANGE_SESSION_ID(msg,sender);
+                    break;
+                 }
+                default: {
+                    //we didn't recognize the message type, do nothing
+                    break;
+                }
+            } //switch
+        } catch (Exception x) {
+            log.error(sm.getString("deltaManager.receiveMessage.error",getName()), x);
+        } finally {
+            Thread.currentThread().setContextClassLoader(contextLoader);
+        }
+    }
+
+    // -------------------------------------------------------- message receiver handler
+
+
+    /**
+     * handle receive session state is complete transfered
+     * @param msg
+     * @param sender
+     */
+    protected void handleALL_SESSION_TRANSFERCOMPLETE(SessionMessage msg, Member sender) {
+        counterReceive_EVT_ALL_SESSION_TRANSFERCOMPLETE++ ;
+        if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.transfercomplete",getName(), sender.getHost(), Integer.valueOf(sender.getPort())));
+        stateTransferCreateSendTime = msg.getTimestamp() ;
+        stateTransfered = true ;
+    }
+
+    /**
+     * handle receive session delta
+     * @param msg
+     * @param sender
+     * @throws IOException
+     * @throws ClassNotFoundException
+     */
+    protected void handleSESSION_DELTA(SessionMessage msg, Member sender) throws IOException, ClassNotFoundException {
+        counterReceive_EVT_SESSION_DELTA++;
+        byte[] delta = msg.getSession();
+        DeltaSession session = (DeltaSession) findSession(msg.getSessionID());
+        if (session != null) {
+            if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.delta",getName(), msg.getSessionID()));
+            try {
+                session.lock();
+                DeltaRequest dreq = deserializeDeltaRequest(session, delta);
+                dreq.execute(session, notifyListenersOnReplication);
+                session.setPrimarySession(false);
+            }finally {
+                session.unlock();
+            }
+        }
+    }
+
+    /**
+     * handle receive session is access at other node ( primary session is now false)
+     * @param msg
+     * @param sender
+     * @throws IOException
+     */
+    protected void handleSESSION_ACCESSED(SessionMessage msg,Member sender) throws IOException {
+        counterReceive_EVT_SESSION_ACCESSED++;
+        DeltaSession session = (DeltaSession) findSession(msg.getSessionID());
+        if (session != null) {
+            if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.accessed",getName(), msg.getSessionID()));
+            session.access();
+            session.setPrimarySession(false);
+            session.endAccess();
+        }
+    }
+
+    /**
+     * handle receive session is expire at other node ( expire session also here)
+     * @param msg
+     * @param sender
+     * @throws IOException
+     */
+    protected void handleSESSION_EXPIRED(SessionMessage msg,Member sender) throws IOException {
+        counterReceive_EVT_SESSION_EXPIRED++;
+        DeltaSession session = (DeltaSession) findSession(msg.getSessionID());
+        if (session != null) {
+            if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.expired",getName(), msg.getSessionID()));
+            session.expire(notifySessionListenersOnReplication, false);
+        }
+    }
+
+    /**
+     * handle receive new session is created at other node (create backup - primary false)
+     * @param msg
+     * @param sender
+     */
+    protected void handleSESSION_CREATED(SessionMessage msg,Member sender) {
+        counterReceive_EVT_SESSION_CREATED++;
+        if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.createNewSession",getName(), msg.getSessionID()));
+        DeltaSession session = (DeltaSession) createEmptySession();
+        session.setManager(this);
+        session.setValid(true);
+        session.setPrimarySession(false);
+        session.setCreationTime(msg.getTimestamp());
+        // use container maxInactiveInterval so that session will expire correctly in case of primary transfer
+        session.setMaxInactiveInterval(getMaxInactiveInterval());
+        session.access();
+        if(notifySessionListenersOnReplication) {
+            session.setId(msg.getSessionID());
+        } else {
+            session.setIdInternal(msg.getSessionID());
+            add(session);
+        }
+        session.resetDeltaRequest();
+        session.endAccess();
+
+    }
+
+    /**
+     * handle receive sessions from other not ( restart )
+     * @param msg
+     * @param sender
+     * @throws ClassNotFoundException
+     * @throws IOException
+     */
+    protected void handleALL_SESSION_DATA(SessionMessage msg,Member sender) throws ClassNotFoundException, IOException {
+        counterReceive_EVT_ALL_SESSION_DATA++;
+        if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.allSessionDataBegin",getName()));
+        byte[] data = msg.getSession();
+        deserializeSessions(data);
+        if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.allSessionDataAfter",getName()));
+        //stateTransferred = true;
+    }
+
+    /**
+     * handle receive that other node want all sessions ( restart )
+     * a) send all sessions with one message
+     * b) send session at blocks
+     * After sending send state is complete transfered
+     * @param msg
+     * @param sender
+     * @throws IOException
+     */
+    protected void handleGET_ALL_SESSIONS(SessionMessage msg, Member sender) throws IOException {
+        counterReceive_EVT_GET_ALL_SESSIONS++;
+        //get a list of all the session from this manager
+        if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.unloadingBegin", getName()));
+        // Write the number of active sessions, followed by the details
+        // get all sessions and serialize without sync
+        Session[] currentSessions = findSessions();
+        long findSessionTimestamp = System.currentTimeMillis() ;
+        if (isSendAllSessions()) {
+            sendSessions(sender, currentSessions, findSessionTimestamp);
+        } else {
+            // send session at blocks
+            for (int i = 0; i < currentSessions.length; i += getSendAllSessionsSize()) {
+                int len = i + getSendAllSessionsSize() > currentSessions.length ? currentSessions.length - i : getSendAllSessionsSize();
+                Session[] sendSessions = new Session[len];
+                System.arraycopy(currentSessions, i, sendSessions, 0, len);
+                sendSessions(sender, sendSessions,findSessionTimestamp);
+                if (getSendAllSessionsWaitTime() > 0) {
+                    try {
+                        Thread.sleep(getSendAllSessionsWaitTime());
+                    } catch (Exception sleep) {
+                    }
+                }//end if
+            }//for
+        }//end if
+        
+        SessionMessage newmsg = new SessionMessageImpl(name,SessionMessage.EVT_ALL_SESSION_TRANSFERCOMPLETE, null,"SESSION-STATE-TRANSFERED", "SESSION-STATE-TRANSFERED"+ getName());
+        newmsg.setTimestamp(findSessionTimestamp);
+        if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.createMessage.allSessionTransfered",getName()));
+        counterSend_EVT_ALL_SESSION_TRANSFERCOMPLETE++;
+        cluster.send(newmsg, sender);
+    }
+
+    /**
+     * handle receive change sessionID at other node
+     * @param msg
+     * @param sender
+     * @throws IOException
+     */
+    protected void handleCHANGE_SESSION_ID(SessionMessage msg,Member sender) throws IOException {
+        counterReceive_EVT_CHANGE_SESSION_ID++;
+        DeltaSession session = (DeltaSession) findSession(msg.getSessionID());
+        if (session != null) {
+            String newSessionID = deserializeSessionId(msg.getSession());
+            session.setPrimarySession(false);
+            if (notifySessionListenersOnReplication) {
+                session.setId(newSessionID);
+            } else {
+                session.setIdInternal(newSessionID);
+                add(session);
+            }
+        }
+    }
+
+    /**
+     * send a block of session to sender
+     * @param sender
+     * @param currentSessions
+     * @param sendTimestamp
+     * @throws IOException
+     */
+    protected void sendSessions(Member sender, Session[] currentSessions,long sendTimestamp) throws IOException {
+        byte[] data = serializeSessions(currentSessions);
+        if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.unloadingAfter",getName()));
+        SessionMessage newmsg = new SessionMessageImpl(name,SessionMessage.EVT_ALL_SESSION_DATA, data,"SESSION-STATE", "SESSION-STATE-" + getName());
+        newmsg.setTimestamp(sendTimestamp);
+        if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.createMessage.allSessionData",getName()));
+        counterSend_EVT_ALL_SESSION_DATA++;
+        cluster.send(newmsg, sender);
+    }
+
+    @Override
+    public ClusterManager cloneFromTemplate() {
+        DeltaManager result = new DeltaManager();
+        result.name = "Clone-from-"+name;
+        result.cluster = cluster;
+        result.replicationValve = replicationValve;
+        result.maxActiveSessions = maxActiveSessions;
+        result.expireSessionsOnShutdown = expireSessionsOnShutdown;
+        result.notifyListenersOnReplication = notifyListenersOnReplication;
+        result.notifySessionListenersOnReplication = notifySessionListenersOnReplication;
+        result.stateTransferTimeout = stateTransferTimeout;
+        result.sendAllSessions = sendAllSessions;
+        result.sendAllSessionsSize = sendAllSessionsSize;
+        result.sendAllSessionsWaitTime = sendAllSessionsWaitTime ; 
+        result.receiverQueue = receiverQueue ;
+        result.stateTimestampDrop = stateTimestampDrop ;
+        result.stateTransferCreateSendTime = stateTransferCreateSendTime; 
+        return result;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/DeltaRequest.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/DeltaRequest.java
new file mode 100644
index 0000000..6f02cdd
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/DeltaRequest.java
@@ -0,0 +1,406 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.session;
+
+/**
+ * This class is used to track the series of actions that happens when
+ * a request is executed. These actions will then translate into invocations of methods 
+ * on the actual session.
+ * This class is NOT thread safe. One DeltaRequest per session
+ * @author <a href="mailto:fhanik@apache.org">Filip Hanik</a>
+ * @version 1.0
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.security.Principal;
+import java.util.LinkedList;
+
+import org.apache.catalina.realm.GenericPrincipal;
+import org.apache.tomcat.util.res.StringManager;
+
+
+public class DeltaRequest implements Externalizable {
+
+    public static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog( DeltaRequest.class );
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm = StringManager
+            .getManager(Constants.Package);
+
+    public static final int TYPE_ATTRIBUTE = 0;
+    public static final int TYPE_PRINCIPAL = 1;
+    public static final int TYPE_ISNEW = 2;
+    public static final int TYPE_MAXINTERVAL = 3;
+    public static final int TYPE_AUTHTYPE = 4;
+
+    public static final int ACTION_SET = 0;
+    public static final int ACTION_REMOVE = 1;
+
+    public static final String NAME_PRINCIPAL = "__SET__PRINCIPAL__";
+    public static final String NAME_MAXINTERVAL = "__SET__MAXINTERVAL__";
+    public static final String NAME_ISNEW = "__SET__ISNEW__";
+    public static final String NAME_AUTHTYPE = "__SET__AUTHTYPE__";
+
+    private String sessionId;
+    private LinkedList<AttributeInfo> actions = new LinkedList<AttributeInfo>();
+    private LinkedList<AttributeInfo> actionPool =
+        new LinkedList<AttributeInfo>();
+    
+    private boolean recordAllActions = false;
+
+    public DeltaRequest() {
+        
+    }
+    
+    public DeltaRequest(String sessionId, boolean recordAllActions) {
+        this.recordAllActions=recordAllActions;
+        if(sessionId != null)
+            setSessionId(sessionId);
+    }
+
+
+    public void setAttribute(String name, Object value) {
+        int action = (value==null)?ACTION_REMOVE:ACTION_SET;
+        addAction(TYPE_ATTRIBUTE,action,name,value);
+    }
+
+    public void removeAttribute(String name) {
+        int action = ACTION_REMOVE;
+        addAction(TYPE_ATTRIBUTE,action,name,null);
+    }
+
+    public void setMaxInactiveInterval(int interval) {
+        int action = ACTION_SET;
+        addAction(TYPE_MAXINTERVAL,action,NAME_MAXINTERVAL,Integer.valueOf(interval));
+    }
+    
+    /**
+     * convert principal at SerializablePrincipal for backup nodes.
+     * Only support principals from type {@link GenericPrincipal GenericPrincipal}
+     * @param p Session principal
+     * @see GenericPrincipal
+     */
+    public void setPrincipal(Principal p) {
+        int action = (p==null)?ACTION_REMOVE:ACTION_SET;
+        SerializablePrincipal sp = null;
+        if ( p != null ) {
+            if(p instanceof GenericPrincipal) {
+                sp = SerializablePrincipal.createPrincipal((GenericPrincipal)p);
+                if(log.isDebugEnabled())
+                    log.debug(sm.getString("deltaRequest.showPrincipal", p.getName() , getSessionId()));
+            } else
+                log.error(sm.getString("deltaRequest.wrongPrincipalClass",p.getClass().getName()));
+        }
+        addAction(TYPE_PRINCIPAL,action,NAME_PRINCIPAL,sp);
+    }
+
+    public void setNew(boolean n) {
+        int action = ACTION_SET;
+        addAction(TYPE_ISNEW,action,NAME_ISNEW,Boolean.valueOf(n));
+    }
+
+    public void setAuthType(String authType) {
+        int action = (authType==null)?ACTION_REMOVE:ACTION_SET;
+        addAction(TYPE_AUTHTYPE,action,NAME_AUTHTYPE, authType);
+    }
+
+    protected void addAction(int type,
+                             int action,
+                             String name,
+                             Object value) {
+        AttributeInfo info = null;
+        if ( this.actionPool.size() > 0 ) {
+            try {
+                info = actionPool.removeFirst();
+            }catch ( Exception x ) {
+                log.error("Unable to remove element:",x);
+                info = new AttributeInfo(type, action, name, value);
+            }
+            info.init(type,action,name,value);
+        } else {
+            info = new AttributeInfo(type, action, name, value);
+        }
+        //if we have already done something to this attribute, make sure
+        //we don't send multiple actions across the wire
+        if ( !recordAllActions) {
+            try {
+                actions.remove(info);
+            } catch (java.util.NoSuchElementException x) {
+                //do nothing, we wanted to remove it anyway
+            }
+        }
+        //add the action
+        actions.addLast(info);
+    }
+    
+    public void execute(DeltaSession session, boolean notifyListeners) {
+        if ( !this.sessionId.equals( session.getId() ) )
+            throw new java.lang.IllegalArgumentException("Session id mismatch, not executing the delta request");
+        session.access();
+        for ( int i=0; i<actions.size(); i++ ) {
+            AttributeInfo info = actions.get(i);
+            switch ( info.getType() ) {
+                case TYPE_ATTRIBUTE: {
+                    if ( info.getAction() == ACTION_SET ) {
+                        if ( log.isTraceEnabled() ) log.trace("Session.setAttribute('"+info.getName()+"', '"+info.getValue()+"')");
+                        session.setAttribute(info.getName(), info.getValue(),notifyListeners,false);
+                    }  else {
+                        if ( log.isTraceEnabled() ) log.trace("Session.removeAttribute('"+info.getName()+"')");
+                        session.removeAttribute(info.getName(),notifyListeners,false);
+                    }
+                        
+                    break;
+                }//case
+                case TYPE_ISNEW: {
+                if ( log.isTraceEnabled() ) log.trace("Session.setNew('"+info.getValue()+"')");
+                    session.setNew(((Boolean)info.getValue()).booleanValue(),false);
+                    break;
+                }//case
+                case TYPE_MAXINTERVAL: {
+                    if ( log.isTraceEnabled() ) log.trace("Session.setMaxInactiveInterval('"+info.getValue()+"')");
+                    session.setMaxInactiveInterval(((Integer)info.getValue()).intValue(),false);
+                    break;
+                }//case
+                case TYPE_PRINCIPAL: {
+                    Principal p = null;
+                    if ( info.getAction() == ACTION_SET ) {
+                        SerializablePrincipal sp = (SerializablePrincipal)info.getValue();
+                        p = sp.getPrincipal();
+                    }
+                    session.setPrincipal(p,false);
+                    break;
+                }//case
+                case TYPE_AUTHTYPE: {
+                    String authType = null;
+                    if ( info.getAction() == ACTION_SET ) {
+                        authType = (String)info.getValue();
+                    }
+                    session.setAuthType(authType,false);
+                    break;
+                }//case
+                default : throw new java.lang.IllegalArgumentException("Invalid attribute info type="+info);
+            }//switch
+        }//for
+        session.endAccess();
+        reset();
+    }
+
+    public void reset() {
+        while ( actions.size() > 0 ) {
+            try {
+                AttributeInfo info = actions.removeFirst();
+                info.recycle();
+                actionPool.addLast(info);
+            }catch  ( Exception x ) {
+                log.error("Unable to remove element",x);
+            }
+        }
+        actions.clear();
+    }
+    
+    public String getSessionId() {
+        return sessionId;
+    }
+    public void setSessionId(String sessionId) {
+        this.sessionId = sessionId;
+        if ( sessionId == null ) {
+            new Exception("Session Id is null for setSessionId").fillInStackTrace().printStackTrace();
+        }
+    }
+    public int getSize() {
+        return actions.size();
+    }
+    
+    public void clear() {
+        actions.clear();
+        actionPool.clear();
+    }
+    
+    @Override
+    public void readExternal(java.io.ObjectInput in) throws IOException,ClassNotFoundException {
+        //sessionId - String
+        //recordAll - boolean
+        //size - int
+        //AttributeInfo - in an array
+        reset();
+        sessionId = in.readUTF();
+        recordAllActions = in.readBoolean();
+        int cnt = in.readInt();
+        if (actions == null)
+            actions = new LinkedList<AttributeInfo>();
+        else
+            actions.clear();
+        for (int i = 0; i < cnt; i++) {
+            AttributeInfo info = null;
+            if (this.actionPool.size() > 0) {
+                try {
+                    info = actionPool.removeFirst();
+                } catch ( Exception x )  {
+                    log.error("Unable to remove element",x);
+                    info = new AttributeInfo();
+                }
+            }
+            else {
+                info = new AttributeInfo();
+            }
+            info.readExternal(in);
+            actions.addLast(info);
+        }//for
+    }
+
+
+    @Override
+    public void writeExternal(java.io.ObjectOutput out ) throws java.io.IOException {
+        //sessionId - String
+        //recordAll - boolean
+        //size - int
+        //AttributeInfo - in an array
+        out.writeUTF(getSessionId());
+        out.writeBoolean(recordAllActions);
+        out.writeInt(getSize());
+        for ( int i=0; i<getSize(); i++ ) {
+            AttributeInfo info = actions.get(i);
+            info.writeExternal(out);
+        }
+    }
+    
+    /**
+     * serialize DeltaRequest
+     * @see DeltaRequest#writeExternal(java.io.ObjectOutput)
+     * 
+     * @return serialized delta request
+     * @throws IOException
+     */
+    protected byte[] serialize() throws IOException {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        ObjectOutputStream oos = new ObjectOutputStream(bos);
+        writeExternal(oos);
+        oos.flush();
+        oos.close();
+        return bos.toByteArray();
+    }
+    
+    private static class AttributeInfo implements java.io.Externalizable {
+        private String name = null;
+        private Object value = null;
+        private int action;
+        private int type;
+
+        public AttributeInfo() {
+            this(-1, -1, null, null);
+        }
+
+        public AttributeInfo(int type,
+                             int action,
+                             String name,
+                             Object value) {
+            super();
+            init(type,action,name,value);
+        }
+
+        public void init(int type,
+                         int action,
+                         String name,
+                         Object value) {
+            this.name = name;
+            this.value = value;
+            this.action = action;
+            this.type = type;
+        }
+
+        public int getType() {
+            return type;
+        }
+
+        public int getAction() {
+            return action;
+        }
+
+        public Object getValue() {
+            return value;
+        }
+        @Override
+        public int hashCode() {
+            return name.hashCode();
+        }
+
+        public String getName() {
+            return name;
+        }
+        
+        public void recycle() {
+            name = null;
+            value = null;
+            type=-1;
+            action=-1;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if ( ! (o instanceof AttributeInfo ) ) return false;
+            AttributeInfo other =  (AttributeInfo)o;
+            return other.getName().equals(this.getName());
+        }
+        
+        @Override
+        public void readExternal(java.io.ObjectInput in ) throws IOException,ClassNotFoundException {
+            //type - int
+            //action - int
+            //name - String
+            //hasvalue - boolean
+            //value - object
+            type = in.readInt();
+            action = in.readInt();
+            name = in.readUTF();
+            boolean hasValue = in.readBoolean();
+            if ( hasValue ) value = in.readObject();
+        }
+
+        @Override
+        public void writeExternal(java.io.ObjectOutput out) throws IOException {
+            //type - int
+            //action - int
+            //name - String
+            //hasvalue - boolean
+            //value - object
+            out.writeInt(getType());
+            out.writeInt(getAction());
+            out.writeUTF(getName());
+            out.writeBoolean(getValue()!=null);
+            if (getValue()!=null) out.writeObject(getValue());
+        }
+        
+        @Override
+        public String toString() {
+            StringBuilder buf = new StringBuilder("AttributeInfo[type=");
+            buf.append(getType()).append(", action=").append(getAction());
+            buf.append(", name=").append(getName()).append(", value=").append(getValue());
+            buf.append(", addr=").append(super.toString()).append("]");
+            return buf.toString();
+        }
+
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/DeltaSession.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/DeltaSession.java
new file mode 100644
index 0000000..3475a20
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/DeltaSession.java
@@ -0,0 +1,836 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.session;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.NotSerializableException;
+import java.io.ObjectInput;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutput;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.apache.catalina.Manager;
+import org.apache.catalina.SessionListener;
+import org.apache.catalina.ha.CatalinaCluster;
+import org.apache.catalina.ha.ClusterManager;
+import org.apache.catalina.ha.ClusterMessage;
+import org.apache.catalina.ha.ClusterSession;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.apache.catalina.session.ManagerBase;
+import org.apache.catalina.session.StandardManager;
+import org.apache.catalina.session.StandardSession;
+import org.apache.catalina.tribes.io.ReplicationStream;
+import org.apache.catalina.tribes.tipis.ReplicatedMapEntry;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ *
+ * Similar to the StandardSession except that this session will keep
+ * track of deltas during a request.
+ *
+ * @author Filip Hanik
+ * @version $Id: DeltaSession.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+public class DeltaSession extends StandardSession implements Externalizable,ClusterSession,ReplicatedMapEntry {
+
+    public static final org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(DeltaSession.class);
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm = StringManager.getManager(Constants.Package);
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * only the primary session will expire, or be able to expire due to
+     * inactivity. This is set to false as soon as I receive this session over
+     * the wire in a session message. That means that someone else has made a
+     * request on another server.
+     */
+    private transient boolean isPrimarySession = true;
+
+    /**
+     * The delta request contains all the action info
+     *
+     */
+    private transient DeltaRequest deltaRequest = null;
+
+    /**
+     * Last time the session was replicated, used for distributed expiring of
+     * session
+     */
+    private transient long lastTimeReplicated = System.currentTimeMillis();
+
+
+    protected final Lock diffLock = new ReentrantReadWriteLock().writeLock();
+
+    private long version;
+
+    // ----------------------------------------------------------- Constructors
+
+    public DeltaSession() {
+        this(null);
+    }
+
+    /**
+     * Construct a new Session associated with the specified Manager.
+     *
+     * @param manager
+     *            The manager with which this Session is associated
+     */
+    public DeltaSession(Manager manager) {
+        super(manager);
+        this.resetDeltaRequest();
+    }
+
+    // ----------------------------------------------------- ReplicatedMapEntry
+
+    /**
+         * Has the object changed since last replication
+         * and is not in a locked state
+         * @return boolean
+         */
+        @Override
+        public boolean isDirty() {
+            return getDeltaRequest().getSize()>0;
+        }
+
+        /**
+         * If this returns true, the map will extract the diff using getDiff()
+         * Otherwise it will serialize the entire object.
+         * @return boolean
+         */
+        @Override
+        public boolean isDiffable() {
+            return true;
+        }
+
+        /**
+         * Returns a diff and sets the dirty map to false
+         * @return byte[]
+         * @throws IOException
+         */
+        @Override
+        public byte[] getDiff() throws IOException {
+            try{
+                lock();
+                return getDeltaRequest().serialize();
+            }finally{
+                unlock();
+            }
+        }
+
+        public ClassLoader[] getClassLoaders() {
+            if ( manager instanceof BackupManager ) return ((BackupManager)manager).getClassLoaders();
+            else if ( manager instanceof ClusterManagerBase ) return ((ClusterManagerBase)manager).getClassLoaders();
+            else if ( manager instanceof StandardManager ) {
+                StandardManager sm = (StandardManager)manager;
+                return ClusterManagerBase.getClassLoaders(sm.getContainer());
+            } else if ( manager instanceof ManagerBase ) {
+                ManagerBase mb = (ManagerBase)manager;
+                return ClusterManagerBase.getClassLoaders(mb.getContainer());
+            }//end if
+            return null;
+        }
+
+        /**
+         * Applies a diff to an existing object.
+         * @param diff byte[]
+         * @param offset int
+         * @param length int
+         * @throws IOException
+         */
+        @Override
+        public void applyDiff(byte[] diff, int offset, int length) throws IOException, ClassNotFoundException {
+            try {
+                lock();
+                ReplicationStream stream = ( (ClusterManager) getManager()).getReplicationStream(diff, offset, length);
+                ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+                try {
+                    ClassLoader[] loaders = getClassLoaders();
+                    if (loaders != null && loaders.length > 0)
+                        Thread.currentThread().setContextClassLoader(loaders[0]);
+                    getDeltaRequest().readExternal(stream);
+                    getDeltaRequest().execute(this, ((ClusterManager)getManager()).isNotifyListenersOnReplication());
+                } finally {
+                    Thread.currentThread().setContextClassLoader(contextLoader);
+                }
+            }finally {
+                unlock();
+            }
+        }
+
+        /**
+         * Resets the current diff state and resets the dirty flag
+         */
+        @Override
+        public void resetDiff() {
+            resetDeltaRequest();
+        }
+
+        /**
+         * Lock during serialization
+         */
+        @Override
+        public void lock() {
+            diffLock.lock();
+        }
+
+        /**
+         * Unlock after serialization
+         */
+        @Override
+        public void unlock() {
+            diffLock.unlock();
+        }
+
+        @Override
+        public void setOwner(Object owner) {
+            if ( owner instanceof ClusterManager && getManager()==null) {
+                ClusterManager cm = (ClusterManager)owner;
+                this.setManager(cm);
+                this.setValid(true);
+                this.setPrimarySession(false);
+                this.access();
+                this.resetDeltaRequest();
+                this.endAccess();
+            }
+        }
+    // ----------------------------------------------------- Session Properties
+
+    /**
+     * returns true if this session is the primary session, if that is the case,
+     * the manager can expire it upon timeout.
+     */
+    @Override
+    public boolean isPrimarySession() {
+        return isPrimarySession;
+    }
+
+    /**
+     * Sets whether this is the primary session or not.
+     *
+     * @param primarySession
+     *            Flag value
+     */
+    @Override
+    public void setPrimarySession(boolean primarySession) {
+        this.isPrimarySession = primarySession;
+    }
+
+    /**
+     * Set the session identifier for this session without notify listeners.
+     *
+     * @param id
+     *            The new session identifier
+     */
+    public void setIdInternal(String id) {
+        this.id = id;
+        resetDeltaRequest();
+    }
+
+    /**
+     * Set the session identifier for this session.
+     *
+     * @param id
+     *            The new session identifier
+     */
+    @Override
+    public void setId(String id) {
+        super.setId(id);
+        resetDeltaRequest();
+    }
+
+
+    @Override
+    public void setMaxInactiveInterval(int interval) {
+        this.setMaxInactiveInterval(interval,true);
+    }
+
+
+    public void setMaxInactiveInterval(int interval, boolean addDeltaRequest) {
+        super.maxInactiveInterval = interval;
+        if (isValid && interval == 0) {
+            expire();
+        } else {
+            if (addDeltaRequest && (deltaRequest != null)) {
+                try {
+                    lock();
+                    deltaRequest.setMaxInactiveInterval(interval);
+                }finally{
+                    unlock();
+                }
+            }
+        }
+    }
+
+    /**
+     * Set the <code>isNew</code> flag for this session.
+     *
+     * @param isNew
+     *            The new value for the <code>isNew</code> flag
+     */
+    @Override
+    public void setNew(boolean isNew) {
+        setNew(isNew, true);
+    }
+
+    public void setNew(boolean isNew, boolean addDeltaRequest) {
+        super.setNew(isNew);
+        if (addDeltaRequest && (deltaRequest != null)){
+            try {
+                lock();
+                deltaRequest.setNew(isNew);
+            }finally{
+                unlock();
+            }
+        }
+    }
+
+    /**
+     * Set the authenticated Principal that is associated with this Session.
+     * This provides an <code>Authenticator</code> with a means to cache a
+     * previously authenticated Principal, and avoid potentially expensive
+     * <code>Realm.authenticate()</code> calls on every request.
+     *
+     * @param principal
+     *            The new Principal, or <code>null</code> if none
+     */
+    @Override
+    public void setPrincipal(Principal principal) {
+        setPrincipal(principal, true);
+    }
+
+    public void setPrincipal(Principal principal, boolean addDeltaRequest) {
+        try { 
+            lock();
+            super.setPrincipal(principal);
+            if (addDeltaRequest && (deltaRequest != null))
+                deltaRequest.setPrincipal(principal);
+        } finally {
+            unlock();
+        }
+    }
+
+    /**
+     * Set the authentication type used to authenticate our cached
+     * Principal, if any.
+     *
+     * @param authType The new cached authentication type
+     */
+    @Override
+    public void setAuthType(String authType) {
+        setAuthType(authType, true);
+    }
+
+    public void setAuthType(String authType, boolean addDeltaRequest) {
+        try { 
+            lock();
+            super.setAuthType(authType);
+            if (addDeltaRequest && (deltaRequest != null))
+                deltaRequest.setAuthType(authType);
+        } finally {
+            unlock();
+        }
+    }
+
+    /**
+     * Return the <code>isValid</code> flag for this session.
+     */
+    @Override
+    public boolean isValid() {
+        if (this.expiring) {
+            return true;
+        }
+        if (!this.isValid) {
+            return false;
+        }
+        if (ACTIVITY_CHECK && accessCount.get() > 0) {
+            return true;
+        }
+        if (maxInactiveInterval >= 0) {
+            long timeNow = System.currentTimeMillis();
+            int timeIdle;
+            if (LAST_ACCESS_AT_START) {
+                timeIdle = (int) ((timeNow - lastAccessedTime) / 1000L);
+            } else {
+                timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L);
+            }
+            if (isPrimarySession()) {
+                if (timeIdle >= maxInactiveInterval) {
+                    expire(true);
+                }
+            } else {
+                if (timeIdle >= (2 * maxInactiveInterval)) {
+                    //if the session has been idle twice as long as allowed,
+                    //the primary session has probably crashed, and no other
+                    //requests are coming in. that is why we do this. otherwise
+                    //we would have a memory leak
+                    expire(true, false);
+                }
+            }
+        }
+        return (this.isValid);
+    }
+
+    /**
+     * End the access and register to ReplicationValve (crossContext support)
+     */
+    @Override
+    public void endAccess() {
+        super.endAccess() ;
+        if(manager instanceof DeltaManager) {
+            ((DeltaManager)manager).registerSessionAtReplicationValve(this);
+        }
+    }
+    
+    // ------------------------------------------------- Session Public Methods
+
+    /**
+     * Perform the internal processing required to invalidate this session,
+     * without triggering an exception if the session has already expired.
+     *
+     * @param notify
+     *            Should we notify listeners about the demise of this session?
+     */
+    @Override
+    public void expire(boolean notify) {
+        expire(notify, true);
+    }
+
+    public void expire(boolean notify, boolean notifyCluster) {
+        if (expiring)
+            return;
+        String expiredId = getIdInternal();
+
+        if(expiredId != null && manager != null &&
+           manager instanceof DeltaManager) {
+            DeltaManager dmanager = (DeltaManager)manager;
+            CatalinaCluster cluster = dmanager.getCluster();
+            ClusterMessage msg = dmanager.requestCompleted(expiredId, true);
+            if (msg != null) {
+                cluster.send(msg);
+            }
+        }
+
+        super.expire(notify);
+
+        if (notifyCluster) {
+            if (log.isDebugEnabled())
+                log.debug(sm.getString("deltaSession.notifying",
+                                       ((ClusterManager)manager).getName(), 
+                                       Boolean.valueOf(isPrimarySession()), 
+                                       expiredId));
+            if ( manager instanceof DeltaManager ) {
+                ( (DeltaManager) manager).sessionExpired(expiredId);
+            }
+        }
+    }
+
+    /**
+     * Release all object references, and initialize instance variables, in
+     * preparation for reuse of this object.
+     */
+    @Override
+    public void recycle() {
+        try {
+            lock();
+            super.recycle();
+            deltaRequest.clear();
+        }finally{
+            unlock();
+        }
+    }
+
+
+    /**
+     * Return a string representation of this object.
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("DeltaSession[");
+        sb.append(id);
+        sb.append("]");
+        return (sb.toString());
+    }
+
+    // ------------------------------------------------ Session Package Methods
+
+    @Override
+    public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {
+        try {
+            lock();
+            readObjectData(in);
+        }finally{
+            unlock();
+        }
+    }
+
+
+    /**
+     * Read a serialized version of the contents of this session object from the
+     * specified object input stream, without requiring that the StandardSession
+     * itself have been serialized.
+     *
+     * @param stream
+     *            The object input stream to read from
+     *
+     * @exception ClassNotFoundException
+     *                if an unknown class is specified
+     * @exception IOException
+     *                if an input/output error occurs
+     */
+    @Override
+    public void readObjectData(ObjectInputStream stream) throws ClassNotFoundException, IOException {
+        readObject((ObjectInput)stream);
+    }
+    public void readObjectData(ObjectInput stream) throws ClassNotFoundException, IOException {
+        readObject(stream);
+    }
+
+    /**
+     * Write a serialized version of the contents of this session object to the
+     * specified object output stream, without requiring that the
+     * StandardSession itself have been serialized.
+     *
+     * @param stream
+     *            The object output stream to write to
+     *
+     * @exception IOException
+     *                if an input/output error occurs
+     */
+    @Override
+    public void writeObjectData(ObjectOutputStream stream) throws IOException {
+        writeObjectData((ObjectOutput)stream);
+    }
+    public void writeObjectData(ObjectOutput stream) throws IOException {
+        writeObject(stream);
+    }
+
+    public void resetDeltaRequest() {
+        try {
+            lock();
+            if (deltaRequest == null) {
+                deltaRequest = new DeltaRequest(getIdInternal(), false);
+            } else {
+                deltaRequest.reset();
+                deltaRequest.setSessionId(getIdInternal());
+            }
+        }finally{
+            unlock();
+        }
+    }
+
+    public DeltaRequest getDeltaRequest() {
+        if (deltaRequest == null) resetDeltaRequest();
+        return deltaRequest;
+    }
+
+    // ------------------------------------------------- HttpSession Properties
+
+    // ----------------------------------------------HttpSession Public Methods
+
+
+    /**
+     * Remove the object bound with the specified name from this session. If the
+     * session does not have an object bound with this name, this method does
+     * nothing.
+     * <p>
+     * After this method executes, and if the object implements
+     * <code>HttpSessionBindingListener</code>, the container calls
+     * <code>valueUnbound()</code> on the object.
+     *
+     * @param name
+     *            Name of the object to remove from this session.
+     * @param notify
+     *            Should we notify interested listeners that this attribute is
+     *            being removed?
+     *
+     * @exception IllegalStateException
+     *                if this method is called on an invalidated session
+     */
+    @Override
+    public void removeAttribute(String name, boolean notify) {
+        removeAttribute(name, notify, true);
+    }
+
+    public void removeAttribute(String name, boolean notify,boolean addDeltaRequest) {
+        // Validate our current state
+        if (!isValid()) throw new IllegalStateException(sm.getString("standardSession.removeAttribute.ise"));
+        removeAttributeInternal(name, notify, addDeltaRequest);
+    }
+
+    /**
+     * Bind an object to this session, using the specified name. If an object of
+     * the same name is already bound to this session, the object is replaced.
+     * <p>
+     * After this method executes, and if the object implements
+     * <code>HttpSessionBindingListener</code>, the container calls
+     * <code>valueBound()</code> on the object.
+     *
+     * @param name
+     *            Name to which the object is bound, cannot be null
+     * @param value
+     *            Object to be bound, cannot be null
+     *
+     * @exception IllegalArgumentException
+     *                if an attempt is made to add a non-serializable object in
+     *                an environment marked distributable.
+     * @exception IllegalStateException
+     *                if this method is called on an invalidated session
+     */
+    @Override
+    public void setAttribute(String name, Object value) {
+        setAttribute(name, value, true, true);
+    }
+
+    public void setAttribute(String name, Object value, boolean notify,boolean addDeltaRequest) {
+
+        // Name cannot be null
+        if (name == null) throw new IllegalArgumentException(sm.getString("standardSession.setAttribute.namenull"));
+
+        // Null value is the same as removeAttribute()
+        if (value == null) {
+            removeAttribute(name);
+            return;
+        }
+
+        try {
+            lock();
+            super.setAttribute(name,value, notify);
+            if (addDeltaRequest && (deltaRequest != null)) deltaRequest.setAttribute(name, value);
+        } finally {
+            unlock();
+        }
+    }
+
+    // -------------------------------------------- HttpSession Private Methods
+
+    /**
+     * Read a serialized version of this session object from the specified
+     * object input stream.
+     * <p>
+     * <b>IMPLEMENTATION NOTE </b>: The reference to the owning Manager is not
+     * restored by this method, and must be set explicitly.
+     *
+     * @param stream
+     *            The input stream to read from
+     *
+     * @exception ClassNotFoundException
+     *                if an unknown class is specified
+     * @exception IOException
+     *                if an input/output error occurs
+     */
+    @Override
+    protected void readObject(ObjectInputStream stream) throws ClassNotFoundException, IOException {
+        readObject((ObjectInput)stream);
+    }
+
+    private void readObject(ObjectInput stream) throws ClassNotFoundException, IOException {
+
+        // Deserialize the scalar instance variables (except Manager)
+        authType = null; // Transient only
+        creationTime = ( (Long) stream.readObject()).longValue();
+        lastAccessedTime = ( (Long) stream.readObject()).longValue();
+        maxInactiveInterval = ( (Integer) stream.readObject()).intValue();
+        isNew = ( (Boolean) stream.readObject()).booleanValue();
+        isValid = ( (Boolean) stream.readObject()).booleanValue();
+        thisAccessedTime = ( (Long) stream.readObject()).longValue();
+        version = ( (Long) stream.readObject()).longValue();
+        boolean hasPrincipal = stream.readBoolean();
+        principal = null;
+        if (hasPrincipal) {
+            principal = SerializablePrincipal.readPrincipal(stream);
+        }
+
+        //        setId((String) stream.readObject());
+        id = (String) stream.readObject();
+        if (log.isDebugEnabled()) log.debug(sm.getString("deltaSession.readSession", id));
+
+        // Deserialize the attribute count and attribute values
+        if (attributes == null) attributes = new ConcurrentHashMap<String, Object>();
+        int n = ( (Integer) stream.readObject()).intValue();
+        boolean isValidSave = isValid;
+        isValid = true;
+        for (int i = 0; i < n; i++) {
+            String name = (String) stream.readObject();
+            Object value = stream.readObject();
+            if ( (value instanceof String) && (value.equals(NOT_SERIALIZED)))
+                continue;
+            attributes.put(name, value);
+        }
+        isValid = isValidSave;
+
+        if (listeners == null) {
+            ArrayList<SessionListener> arrayList =
+                new ArrayList<SessionListener>();
+            listeners = arrayList;
+        }
+
+        if (notes == null) {
+            notes = new Hashtable<String,Object>();
+        }
+        activate();
+    }
+
+    @Override
+    public void writeExternal(ObjectOutput out ) throws java.io.IOException {
+        try {
+            lock();
+            writeObject(out);
+        }finally {
+            unlock();
+        }
+    }
+
+
+    /**
+     * Write a serialized version of this session object to the specified object
+     * output stream.
+     * <p>
+     * <b>IMPLEMENTATION NOTE </b>: The owning Manager will not be stored in the
+     * serialized representation of this Session. After calling
+     * <code>readObject()</code>, you must set the associated Manager
+     * explicitly.
+     * <p>
+     * <b>IMPLEMENTATION NOTE </b>: Any attribute that is not Serializable will
+     * be unbound from the session, with appropriate actions if it implements
+     * HttpSessionBindingListener. If you do not want any such attributes, be
+     * sure the <code>distributable</code> property of the associated Manager
+     * is set to <code>true</code>.
+     *
+     * @param stream
+     *            The output stream to write to
+     *
+     * @exception IOException
+     *                if an input/output error occurs
+     */
+    @Override
+    protected void writeObject(ObjectOutputStream stream) throws IOException {
+        writeObject((ObjectOutput)stream);
+    }
+    
+    private void writeObject(ObjectOutput stream) throws IOException {
+        // Write the scalar instance variables (except Manager)
+        stream.writeObject(Long.valueOf(creationTime));
+        stream.writeObject(Long.valueOf(lastAccessedTime));
+        stream.writeObject(Integer.valueOf(maxInactiveInterval));
+        stream.writeObject(Boolean.valueOf(isNew));
+        stream.writeObject(Boolean.valueOf(isValid));
+        stream.writeObject(Long.valueOf(thisAccessedTime));
+        stream.writeObject(Long.valueOf(version));
+        stream.writeBoolean(getPrincipal() != null);
+        if (getPrincipal() != null) {
+            SerializablePrincipal.writePrincipal((GenericPrincipal) principal,stream);
+        }
+
+        stream.writeObject(id);
+        if (log.isDebugEnabled()) log.debug(sm.getString("deltaSession.writeSession", id));
+
+        // Accumulate the names of serializable and non-serializable attributes
+        String keys[] = keys();
+        ArrayList<String> saveNames = new ArrayList<String>();
+        ArrayList<Object> saveValues = new ArrayList<Object>();
+        for (int i = 0; i < keys.length; i++) {
+            Object value = null;
+            value = attributes.get(keys[i]);
+            if (value == null || exclude(keys[i]))
+                continue;
+            else if (value instanceof Serializable) {
+                saveNames.add(keys[i]);
+                saveValues.add(value);
+            }
+        }
+
+        // Serialize the attribute count and the Serializable attributes
+        int n = saveNames.size();
+        stream.writeObject(Integer.valueOf(n));
+        for (int i = 0; i < n; i++) {
+            stream.writeObject( saveNames.get(i));
+            try {
+                stream.writeObject(saveValues.get(i));
+            } catch (NotSerializableException e) {
+                log.error(sm.getString("standardSession.notSerializable",saveNames.get(i), id), e);
+                stream.writeObject(NOT_SERIALIZED);
+                log.error("  storing attribute '" + saveNames.get(i)+ "' with value NOT_SERIALIZED");
+            }
+        }
+
+    }
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Return the value of an attribute without a check for validity.
+     */
+    protected Object getAttributeInternal(String name) {
+        return (attributes.get(name));
+    }
+
+    protected void removeAttributeInternal(String name, boolean notify,
+                                           boolean addDeltaRequest) {
+        try {
+            lock();
+            // Remove this attribute from our collection
+            Object value = attributes.get(name);
+            if (value == null) return;
+
+            super.removeAttributeInternal(name,notify);
+            if (addDeltaRequest && (deltaRequest != null)) deltaRequest.removeAttribute(name);
+
+        }finally {
+            unlock();
+        }
+    }
+
+    protected long getLastTimeReplicated() {
+        return lastTimeReplicated;
+    }
+
+    @Override
+    public long getVersion() {
+        return version;
+    }
+
+    protected void setLastTimeReplicated(long lastTimeReplicated) {
+        this.lastTimeReplicated = lastTimeReplicated;
+    }
+
+    @Override
+    public void setVersion(long version) {
+        this.version = version;
+    }
+
+    protected void setAccessCount(int count) {
+        if ( accessCount == null && ACTIVITY_CHECK ) accessCount = new AtomicInteger();
+        if ( accessCount != null ) super.accessCount.set(count);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/JvmRouteBinderValve.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/JvmRouteBinderValve.java
new file mode 100644
index 0000000..d7cb30e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/JvmRouteBinderValve.java
@@ -0,0 +1,485 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.session;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Context;
+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.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.ha.CatalinaCluster;
+import org.apache.catalina.ha.ClusterManager;
+import org.apache.catalina.ha.ClusterValve;
+import org.apache.catalina.session.ManagerBase;
+import org.apache.catalina.session.PersistentManager;
+import org.apache.catalina.valves.ValveBase;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Valve to handle Tomcat jvmRoute takeover using mod_jk module after node
+ * failure. After a node crashes, subsequent requests go to other cluster nodes.
+ * That incurs a drop in performance. When this Valve is enabled on a backup
+ * node and sees a request, which was intended for another (thus failed) node,
+ * it will rewrite the cookie jsessionid information to use the route to this
+ * backup cluster node, that answered the request. After the response is
+ * delivered to the client, all subsequent client requests will go directly to
+ * the backup node. The change of sessionid is also sent to all other cluster
+ * nodes. After all that, the session stickiness will work directly to the
+ * backup node and the traffic will not go back to the failed node after it is
+ * restarted!
+ * 
+ * <p>
+ * For this valve to function correctly, so that all nodes of the cluster
+ * receive the sessionid change notifications that it generates, the following
+ * ClusterListener MUST be configured at all nodes of the cluster:
+ * {@link org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener
+ * JvmRouteSessionIDBinderListener} since Tomcat 5.5.10, and both
+ * JvmRouteSessionIDBinderListener and JvmRouteSessionIDBinderLifecycleListener
+ * for earlier versions of Tomcat.
+ * 
+ * <p>
+ * Add this Valve to your host definition at conf/server.xml .
+ * 
+ * Since 5.5.10 as direct cluster valve:<br/>
+ * 
+ * <pre>
+ *  &lt;Cluster&gt;
+ *  &lt;Valve className=&quot;org.apache.catalina.ha.session.JvmRouteBinderValve&quot; /&gt;  
+ *  &lt;/Cluster&gt;
+ * </pre>
+ * 
+ * <br />
+ * Before 5.5.10 as Host element:<br/>
+ * 
+ * <pre>
+ *  &lt;Host&gt;
+ *  &lt;Valve className=&quot;org.apache.catalina.ha.session.JvmRouteBinderValve&quot; /&gt;  
+ *  &lt;/Host&gt;
+ * </pre>
+ * 
+ * <em>A Trick:</em><br/>
+ * You can enable this mod_jk turnover mode via JMX before you drop a node to
+ * all backup nodes! Set enable true on all JvmRouteBinderValve backups, disable
+ * worker at mod_jk and then drop node and restart it! Then enable mod_jk worker
+ * and disable JvmRouteBinderValves again. This use case means that only
+ * requested sessions are migrated.
+ * 
+ * @author Peter Rossbach
+ * @version $Id: JvmRouteBinderValve.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+public class JvmRouteBinderValve extends ValveBase implements ClusterValve {
+
+    /*--Static Variables----------------------------------------*/
+    public static final org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory
+            .getLog(JvmRouteBinderValve.class);
+
+    /**
+     * The descriptive information about this implementation.
+     */
+    protected static final String info = "org.apache.catalina.ha.session.JvmRouteBinderValve/1.2";
+
+    //------------------------------------------------------ Constructor
+    public JvmRouteBinderValve() {
+        super(false);
+    }
+
+    /*--Instance Variables--------------------------------------*/
+
+    /**
+     * the cluster
+     */
+    protected CatalinaCluster cluster;
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm = StringManager.getManager(Constants.Package);
+
+    /**
+     * enabled this component
+     */
+    protected boolean enabled = true;
+
+    /**
+     * number of session that no at this tomcat instanz hosted
+     */
+    protected long numberOfSessions = 0;
+
+    protected String sessionIdAttribute = "org.apache.catalina.ha.session.JvmRouteOrignalSessionID";
+
+
+    /*--Logic---------------------------------------------------*/
+
+    /**
+     * Return descriptive information about this implementation.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+    /**
+     * set session id attribute to failed node for request.
+     * 
+     * @return Returns the sessionIdAttribute.
+     */
+    public String getSessionIdAttribute() {
+        return sessionIdAttribute;
+    }
+
+    /**
+     * get name of failed request session attribute
+     * 
+     * @param sessionIdAttribute
+     *            The sessionIdAttribute to set.
+     */
+    public void setSessionIdAttribute(String sessionIdAttribute) {
+        this.sessionIdAttribute = sessionIdAttribute;
+    }
+
+    /**
+     * @return Returns the number of migrated sessions.
+     */
+    public long getNumberOfSessions() {
+        return numberOfSessions;
+    }
+
+    /**
+     * @return Returns the enabled.
+     */
+    public boolean getEnabled() {
+        return enabled;
+    }
+
+    /**
+     * @param enabled
+     *            The enabled to set.
+     */
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    /**
+     * Detect possible the JVMRoute change at cluster backup node..
+     * 
+     * @param request
+     *            tomcat request being processed
+     * @param response
+     *            tomcat response being processed
+     * @exception IOException
+     *                if an input/output error has occurred
+     * @exception ServletException
+     *                if a servlet error has occurred
+     */
+    @Override
+    public void invoke(Request request, Response response) throws IOException,
+            ServletException {
+
+         if (getEnabled() 
+             && request.getContext() != null
+             && request.getContext().getDistributable() ) {
+             // valve cluster can access manager - other cluster handle turnover 
+             // at host level - hopefully!
+             Manager manager = request.getContext().getManager();
+
+             if (manager != null && (
+                     (manager instanceof ClusterManager
+                       && getCluster() != null
+                       && getCluster().getManager(((ClusterManager)manager).getName()) != null)
+                     ||
+                     (manager instanceof PersistentManager)))
+                 handlePossibleTurnover(request);
+        }
+        // Pass this request on to the next valve in our pipeline
+        getNext().invoke(request, response);
+    }
+
+    /**
+     * handle possible session turn over.
+     * 
+     * @see JvmRouteBinderValve#handleJvmRoute(Request, String, String)
+     * @param request current request
+     */
+    protected void handlePossibleTurnover(Request request) {
+        String sessionID = request.getRequestedSessionId() ;
+        if (sessionID != null) {
+            long t1 = System.currentTimeMillis();
+            String jvmRoute = getLocalJvmRoute(request);
+            if (jvmRoute == null) {
+                if (log.isDebugEnabled())
+                    log.debug(sm.getString("jvmRoute.missingJvmRouteAttribute"));
+                return;
+            }
+            handleJvmRoute( request, sessionID, jvmRoute);
+            if (log.isDebugEnabled()) {
+                long t2 = System.currentTimeMillis();
+                long time = t2 - t1;
+                log.debug(sm.getString("jvmRoute.turnoverInfo", Long.valueOf(time)));
+            }
+        }
+    }
+
+    /**
+     * get jvmroute from engine
+     * 
+     * @param request current request
+     * @return return jvmRoute from ManagerBase or null
+     */
+    protected String getLocalJvmRoute(Request request) {
+        Manager manager = getManager(request);
+        if(manager instanceof ManagerBase)
+            return ((ManagerBase) manager).getJvmRoute();
+        return null ;
+    }
+
+    /**
+     * get Cluster DeltaManager
+     * 
+     * @param request current request
+     * @return manager or null
+     */
+    protected Manager getManager(Request request) {
+        Manager manager = request.getContext().getManager();
+        if (log.isDebugEnabled()) {
+            if(manager != null)
+                log.debug(sm.getString("jvmRoute.foundManager", manager,  request.getContext().getName()));
+            else 
+                log.debug(sm.getString("jvmRoute.notFoundManager", request.getContext().getName()));
+        }
+        return manager;
+    }
+
+    /**
+     * @return Returns the cluster.
+     */
+    @Override
+    public CatalinaCluster getCluster() {
+        return cluster;
+    }
+    
+    /**
+     * @param cluster The cluster to set.
+     */
+    @Override
+    public void setCluster(CatalinaCluster cluster) {
+        this.cluster = cluster;
+    }
+    
+    /**
+     * Handle jvmRoute stickiness after tomcat instance failed. After this
+     * correction a new Cookie send to client with new jvmRoute and the
+     * SessionID change propagate to the other cluster nodes.
+     * 
+     * @param request current request
+     * @param sessionId
+     *            request SessionID from Cookie
+     * @param localJvmRoute
+     *            local jvmRoute
+     */
+    protected void handleJvmRoute(
+            Request request, String sessionId, String localJvmRoute) {
+        // get requested jvmRoute.
+        String requestJvmRoute = null;
+        int index = sessionId.indexOf(".");
+        if (index > 0) {
+            requestJvmRoute = sessionId
+                    .substring(index + 1, sessionId.length());
+        }
+        if (requestJvmRoute != null && !requestJvmRoute.equals(localJvmRoute)) {
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString("jvmRoute.failover", requestJvmRoute,
+                        localJvmRoute, sessionId));
+            }
+            Session catalinaSession = null;
+            try {
+                catalinaSession = getManager(request).findSession(sessionId);
+            } catch (IOException e) {
+                // Hups!
+            }
+            String id = sessionId.substring(0, index);
+            String newSessionID = id + "." + localJvmRoute;
+            // OK - turnover the session and inform other cluster nodes
+            if (catalinaSession != null) {
+                changeSessionID(request, sessionId, newSessionID,
+                        catalinaSession);
+                numberOfSessions++;
+            } else {
+                try {
+                    catalinaSession = getManager(request).findSession(newSessionID);
+                } catch (IOException e) {
+                    // Hups!
+                }
+                if (catalinaSession != null) {
+                    // session is rewrite at other request, rewrite this also
+                    changeRequestSessionID(request, sessionId, newSessionID);
+                } else {
+                    if (log.isDebugEnabled()) {
+                        log.debug(sm.getString("jvmRoute.cannotFindSession",sessionId));
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * change session id and send to all cluster nodes
+     * 
+     * @param request current request
+     * @param sessionId
+     *            original session id
+     * @param newSessionID
+     *            new session id for node migration
+     * @param catalinaSession
+     *            current session with original session id
+     */
+    protected void changeSessionID(Request request, String sessionId,
+            String newSessionID, Session catalinaSession) {
+        fireLifecycleEvent("Before session migration", catalinaSession);
+        // FIXME: setId trigger session Listener, but only chance to register manager with correct id!
+        catalinaSession.setId(newSessionID);
+        // FIXME: Why we remove change data from other running request?
+        // setId also trigger resetDeltaRequest!!
+        if (catalinaSession instanceof DeltaSession)
+            ((DeltaSession) catalinaSession).resetDeltaRequest();
+        changeRequestSessionID(request, sessionId, newSessionID);
+
+        // now sending the change to all other clusternodes!
+        sendSessionIDClusterBackup(request,sessionId, newSessionID);
+
+        fireLifecycleEvent("After session migration", catalinaSession);
+        if (log.isDebugEnabled()) {
+            log.debug(sm.getString("jvmRoute.changeSession", sessionId,
+                    newSessionID));
+        }   
+    }
+
+    /**
+     * Change Request Session id
+     * @param request current request
+     * @param sessionId
+     *            original session id
+     * @param newSessionID
+     *            new session id for node migration
+     */
+    protected void changeRequestSessionID(Request request, String sessionId, String newSessionID) {
+        request.changeSessionId(newSessionID);
+
+        // set original sessionid at request, to allow application detect the
+        // change
+        if (sessionIdAttribute != null && !"".equals(sessionIdAttribute)) {
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString("jvmRoute.set.orignalsessionid",sessionIdAttribute,sessionId));
+            }
+            request.setAttribute(sessionIdAttribute, sessionId);
+        }
+    }
+    
+    /**
+     * Send the changed Sessionid to all clusternodes.
+     * 
+     * @see JvmRouteSessionIDBinderListener#messageReceived(
+     *            org.apache.catalina.ha.ClusterMessage)
+     * @param sessionId
+     *            current failed sessionid
+     * @param newSessionID
+     *            new session id, bind to the new cluster node
+     */
+    protected void sendSessionIDClusterBackup(Request request, String sessionId,
+            String newSessionID) {
+        CatalinaCluster c = getCluster();
+        if (c != null) {
+            SessionIDMessage msg = new SessionIDMessage();
+            msg.setOrignalSessionID(sessionId);
+            msg.setBackupSessionID(newSessionID);
+            Context context = request.getContext();
+            msg.setContextName(context.getName());
+            msg.setHost(context.getParent().getName());
+            c.send(msg);
+        }
+    }
+
+    /**
+     * 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 {
+        
+        if (cluster == null) {
+            Container hostContainer = getContainer();
+            // compatibility with JvmRouteBinderValve version 1.1
+            // ( setup at context.xml or context.xml.default )
+            if (!(hostContainer instanceof Host)) {
+                if (log.isWarnEnabled())
+                    log.warn(sm.getString("jvmRoute.configure.warn"));
+                hostContainer = hostContainer.getParent();
+            }
+            if (hostContainer instanceof Host
+                    && ((Host) hostContainer).getCluster() != null) {
+                cluster = (CatalinaCluster) ((Host) hostContainer).getCluster();
+            } else {
+                Container engine = hostContainer.getParent() ;
+                if (engine instanceof Engine
+                        && ((Engine) engine).getCluster() != null) {
+                    cluster = (CatalinaCluster) ((Engine) engine).getCluster();
+                }
+            }
+        }
+        
+        if (log.isInfoEnabled()) {
+            log.info(sm.getString("jvmRoute.valve.started"));
+            if (cluster == null)
+                log.info(sm.getString("jvmRoute.noCluster"));
+        }
+
+        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();
+
+        cluster = null;
+        numberOfSessions = 0;
+        if (log.isInfoEnabled())
+            log.info(sm.getString("jvmRoute.valve.stopped"));
+
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/JvmRouteSessionIDBinderListener.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/JvmRouteSessionIDBinderListener.java
new file mode 100644
index 0000000..0cf352c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/JvmRouteSessionIDBinderListener.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.ha.session;
+
+import java.io.IOException;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.Session;
+import org.apache.catalina.core.StandardEngine;
+import org.apache.catalina.ha.ClusterListener;
+import org.apache.catalina.ha.ClusterMessage;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Receive SessionID cluster change from other backup node after primary session
+ * node is failed.
+ * 
+ * @author Peter Rossbach
+ * @version $Id: JvmRouteSessionIDBinderListener.java,v 1.1 2011/06/28 21:08:17 rherrmann Exp $
+ */
+public class JvmRouteSessionIDBinderListener extends ClusterListener {
+
+    private static final Log log =
+        LogFactory.getLog(JvmRouteSessionIDBinderListener.class);
+    
+    private static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    /**
+     * The descriptive information about this implementation.
+     */
+    protected static final String info =
+        "org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener/1.1";
+
+    //--Instance Variables--------------------------------------
+
+
+    protected boolean started = false;
+
+    /**
+     * number of session that goes to this cluster node
+     */
+    private long numberOfSessions = 0;
+
+    //--Constructor---------------------------------------------
+
+    public JvmRouteSessionIDBinderListener() {
+        // NO-OP
+    }
+
+    //--Logic---------------------------------------------------
+
+    /**
+     * Return descriptive information about this implementation.
+     */
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+    /**
+     * @return Returns the numberOfSessions.
+     */
+    public long getNumberOfSessions() {
+        return numberOfSessions;
+    }
+
+    /**
+     * Add this Mover as Cluster Listener ( receiver)
+     * 
+     * @throws LifecycleException
+     */
+    public void start() throws LifecycleException {
+        if (started)
+            return;
+        getCluster().addClusterListener(this);
+        started = true;
+        if (log.isInfoEnabled())
+            log.info(sm.getString("jvmRoute.clusterListener.started"));
+    }
+
+    /**
+     * Remove this from Cluster Listener
+     * 
+     * @throws LifecycleException
+     */
+    public void stop() throws LifecycleException {
+        started = false;
+        getCluster().removeClusterListener(this);
+        if (log.isInfoEnabled())
+            log.info(sm.getString("jvmRoute.clusterListener.stopped"));
+    }
+
+    /**
+     * 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) {
+        if (msg instanceof SessionIDMessage) {
+            SessionIDMessage sessionmsg = (SessionIDMessage) msg;
+            if (log.isDebugEnabled())
+                log.debug(sm.getString(
+                        "jvmRoute.receiveMessage.sessionIDChanged", sessionmsg
+                                .getOrignalSessionID(), sessionmsg
+                                .getBackupSessionID(), sessionmsg
+                                .getContextName()));
+            Container container = getCluster().getContainer();
+            Container host = null ;
+            if(container instanceof Engine) {
+                host = container.findChild(sessionmsg.getHost());
+            } else {
+                host = container ;
+            }
+            if (host != null) {
+                Context context = (Context) host.findChild(sessionmsg
+                        .getContextName());
+                if (context != null) {
+                    try {
+                        Session session = context.getManager().findSession(
+                                sessionmsg.getOrignalSessionID());
+                        if (session != null) {
+                            session.setId(sessionmsg.getBackupSessionID());
+                        } else if (log.isInfoEnabled())
+                            log.info(sm.getString("jvmRoute.lostSession",
+                                    sessionmsg.getOrignalSessionID(),
+                                    sessionmsg.getContextName()));
+                    } catch (IOException e) {
+                        log.error(e);
+                    }
+
+                } else if (log.isErrorEnabled())
+                    log.error(sm.getString("jvmRoute.contextNotFound",
+                            sessionmsg.getContextName(), ((StandardEngine) host
+                                    .getParent()).getJvmRoute()));
+            } else if (log.isErrorEnabled())
+                log.error(sm.getString("jvmRoute.hostNotFound", sessionmsg.getContextName()));
+        }
+        return;
+    }
+
+    /**
+     * 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.
+     */
+    @Override
+    public boolean accept(ClusterMessage msg) {
+        return (msg instanceof SessionIDMessage);
+    }
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/LocalStrings.properties
new file mode 100644
index 0000000..465fcbd
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/LocalStrings.properties
@@ -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.
+
+deltaManager.createSession.newSession=Created a DeltaSession with Id [{0}] Total count={1}
+deltaManager.createMessage.access=Manager [{0}]: create session message [{1}] access.
+deltaManager.createMessage.accessChangePrimary=Manager [{0}]: create session message [{1}] access to change primary.
+deltaManager.createMessage.allSessionData=Manager [{0}] send all session data.
+deltaManager.createMessage.allSessionTransfered=Manager [{0}] send all session data transfered
+deltaManager.createMessage.delta=Manager [{0}]: create session message [{1}] delta request.
+deltaManager.createMessage.expire=Manager [{0}]: create session message [{1}] expire.
+deltaManager.createMessage.unableCreateDeltaRequest=Unable to serialize delta request for sessionid [{0}]
+deltaManager.dropMessage=Manager [{0}]: Drop message {1} inside GET_ALL_SESSIONS sync phase start date {2} message date {3}
+deltaManager.foundMasterMember=Found for context [{0}] the replication master member [{1}]
+deltaManager.loading.cnfe=ClassNotFoundException while loading persisted sessions: {0}
+deltaManager.loading.existing.session=overload existing session {0} 
+deltaManager.loading.ioe=IOException while loading persisted sessions: {0}
+deltaManager.loading.withContextClassLoader=Manager [{0}]: Loading the object data with a context class loader.
+deltaManager.loading.withoutClassLoader=Manager [{0}]: Loading the object data without a context class loader.
+deltaManager.managerLoad=Exception loading sessions from persistent storage
+deltaManager.noCluster=Starting... no cluster associated with this context: [{0}]
+deltaManager.noMasterMember=Starting... with no other member for context [{0}] at domain [{1}]
+deltaManager.noMembers=Manager [{0}]: skipping state transfer. No members active in cluster group.
+deltaManager.noSessionState=Manager [{0}]: No session state send at {1} received, timing out after {2} ms.
+deltaManager.sendMessage.newSession=Manager [{0}] send new session ({1})
+deltaManager.expireSessions=Manager [{0}] expiring sessions upon shutdown
+deltaManager.receiveMessage.accessed=Manager [{0}]: received session [{1}] accessed.
+deltaManager.receiveMessage.createNewSession=Manager [{0}]: received session [{1}] created.
+deltaManager.receiveMessage.delta=Manager [{0}]: received session [{1}] delta.
+deltaManager.receiveMessage.error=Manager [{0}]: Unable to receive message through TCP channel
+deltaManager.receiveMessage.eventType=Manager [{0}]: Received SessionMessage of type=({1}) from [{2}]
+deltaManager.receiveMessage.expired=Manager [{0}]: received session [{1}] expired.
+deltaManager.receiveMessage.transfercomplete=Manager [{0}] received from node [{1}:{2}] session state transfered.
+deltaManager.receiveMessage.unloadingAfter=Manager [{0}]: unloading sessions complete
+deltaManager.receiveMessage.unloadingBegin=Manager [{0}]: start unloading sessions
+deltaManager.receiveMessage.allSessionDataAfter=Manager [{0}]: session state deserialized
+deltaManager.receiveMessage.allSessionDataBegin=Manager [{0}]: received session state data
+deltaManager.receiveMessage.fromWrongDomain=Manager [{0}]: Received wrong SessionMessage of type=({1}) from [{2}] with domain [{3}] (localdomain [{4}] 
+deltaManager.registerCluster=Register manager {0} to cluster element {1} with name {2}
+deltaManager.sessionReceived=Manager [{0}]; session state send at {1} received in {2} ms.
+deltaManager.startClustering=Starting clustering manager at {0}
+deltaManager.stopped=Manager [{0}] is stopping
+deltaManager.unloading.ioe=IOException while saving persisted sessions: {0}
+deltaManager.waitForSessionState=Manager [{0}], requesting session state from {1}. This operation will timeout if no session state has been received within {2} seconds.
+deltaManager.unableSerializeSessionID =Unable to serialize sessionID [{0}]
+deltaRequest.showPrincipal=Principal [{0}] is set to session {1}
+deltaRequest.wrongPrincipalClass=DeltaManager only support GenericPrincipal. Your realm used principal class {0}.
+deltaSession.notifying=Notifying cluster of expiration primary={0} sessionId [{1}]
+deltaSession.valueBound.ex=Session bound listener throw an exception
+deltaSession.valueBinding.ex=Session binding listener throw an exception
+deltaSession.valueUnbound.ex=Session unbound listener throw an exception
+deltaSession.readSession=readObject() loading session [{0}]
+deltaSession.readAttribute=session [{0}] loading attribute '{1}' with value '{2}'
+deltaSession.writeSession=writeObject() storing session [{0}]
+jvmRoute.cannotFindSession=Can't find session [{0}]
+jvmRoute.changeSession=Changed session from [{0}] to [{1}]
+jvmRoute.clusterListener.started=Cluster JvmRouteSessionIDBinderListener started
+jvmRoute.clusterListener.stopped=Cluster JvmRouteSessionIDBinderListener stopped
+jvmRoute.configure.warn=Please, setup your JvmRouteBinderValve at host valve, not at context valve!
+jvmRoute.contextNotFound=Context [{0}] not found at node [{1}]!
+jvmRoute.failover=Detected a failover with different jvmRoute - orginal route: [{0}] new one: [{1}] at session id [{2}]
+jvmRoute.foundManager=Found Cluster DeltaManager {0} at {1}
+jvmRoute.hostNotFound=No host found [{0}]
+jvmRoute.listener.started=SessionID Binder Listener started
+jvmRoute.listener.stopped=SessionID Binder Listener stopped
+jvmRoute.lostSession=Lost Session [{0}] at path [{1}]
+jvmRoute.missingJvmRouteAttribute=No engine jvmRoute attribute configured!
+jvmRoute.newSessionCookie=Setting cookie with session id [{0}] name: [{1}] path: [{2}] secure: [{3}] httpOnly: [{4}]
+jvmRoute.noCluster=The JvmRouterBinderValve is configured, but clustering is not being used. Fail over will still work, providing a PersistentManager is used.
+jvmRoute.notFoundManager=Not found Cluster DeltaManager at {0}
+jvmRoute.receiveMessage.sessionIDChanged=Cluster JvmRouteSessionIDBinderListener received orginal session ID [{0}] set to new id [{1}] for context path [{2}]
+jvmRoute.run.already=jvmRoute SessionID receiver run already
+jvmRoute.skipURLSessionIDs=Skip reassign jvm route check, sessionid comes from URL!
+jvmRoute.turnoverInfo=Turnover Check time {0} msec
+jvmRoute.valve.started=JvmRouteBinderValve started
+jvmRoute.valve.stopped=JvmRouteBinderValve stopped
+jvmRoute.set.orignalsessionid=Set Orginal Session id at request attriute {0} value: {1}
+standardSession.notSerializable=Cannot serialize session attribute {0} for session {1}
+standardSession.removeAttribute.ise=removeAttribute: Session already invalidated
+standardSession.setAttribute.namenull=setAttribute: name parameter cannot be null
+serializablePrincipal.readPrincipal.cnfe=readPrincipal: Failed to recreate user Principal
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/LocalStrings_es.properties
new file mode 100644
index 0000000..4539731
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/LocalStrings_es.properties
@@ -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.
+
+deltaManager.createSession.newSession = Creada una DeltaSession con Id [{0}] Total contador\={1}
+deltaManager.createMessage.access = Gestor [{0}]\: creado mensaje de sesi\u00F3n [{1}] acceso.
+deltaManager.createMessage.accessChangePrimary = Gestor [{0}]\: creado mensaje de sesi\u00F3n [{1}] acceso para cambiar el primario.
+deltaManager.createMessage.allSessionData = Gestor [{0}] env\u00EDa todos los datos de sesi\u00F3n.
+deltaManager.createMessage.allSessionTransfered = Gestor [{0}] env\u00EDa todos los datos de sesi\u00F3n transferidos
+deltaManager.createMessage.delta = Gestor [{0}]\: crea mensaje de sesi\u00F3n [{1}] de requerimiento delta.
+deltaManager.createMessage.expire = Gestor [{0}]\: crea mensaje de sesi\u00F3n [{1}] de expiraci\u00F3n.
+deltaManager.createMessage.unableCreateDeltaRequest = No puedo serializar requerimiento delta para la id de sesi\u00F3n [{0}]
+deltaManager.dropMessage = Gestor [{0}]\: Quita mensaje {1} dentro de fase sincronizada GET_ALL_SESSIONS fecha inicio {2} fecha mensaje {3}
+deltaManager.foundMasterMember = Hallado para contexto [{0}] el miembro maestro de r\u00E9plica [{1}]
+deltaManager.loading.cnfe = ClassNotFoundException al cargar sesiones persistentes\: {0}
+deltaManager.loading.existing.session = sobrecarga en sesi\u00F3n existente {0}
+deltaManager.loading.ioe = IOException al cargar sesiones persistentes\: {0}
+deltaManager.loading.withContextClassLoader = Gestor [{0}]\: Cargando los datos de objeto con un cargador de clase de contexto.
+deltaManager.loading.withoutClassLoader = Gestor [{0}]\: Cargando los datos de objeto sin un cargador de clase de contexto.
+deltaManager.managerLoad = Excepci\u00F3n cargando sesiones desde almacenaje persistente
+deltaManager.noCluster = Arrancando... no hay cl\u00FAster asociado con este contexto\: [{0}]
+deltaManager.noMasterMember = Arrancando... sin otro miembro para el contexto [{0}] en dominio [{1}]
+deltaManager.noMembers = Gestor [{0}]\: saltando estado de transferencia. No hay miembros activos en grupo de cl\u00FAster.
+deltaManager.noSessionState = Gestor [{0}]\: No se ha recibido estado de sesi\u00F3n a las {1}, agotando tiempo tras {2} ms.
+deltaManager.sendMessage.newSession = El gestor [{0}] env\u00EDa nueva sesi\u00F3n ({1})
+deltaManager.expireSessions = Gestor [{0}] expirando sesiones al apagar
+deltaManager.receiveMessage.accessed = Gestor [{0}]\: accedida sesi\u00F3n [{1}] recibida.
+deltaManager.receiveMessage.createNewSession = Gestor [{0}]\: creada sesi\u00F3n [{1}] recibida.
+deltaManager.receiveMessage.delta = Gestor [{0}]\: delta sesi\u00F3n [{1}] recibida.
+deltaManager.receiveMessage.error = Gestor [{0}]\: No puedo recibir mensaje a trav\u00E9s del canal TCP
+deltaManager.receiveMessage.eventType = Gestor [{0}]\: recibido SessionMessage de tipo\=({1}) desde [{2}]
+deltaManager.receiveMessage.expired = Gestor [{0}]\: expirada sesi\u00F3n [{1}] recibida.
+deltaManager.receiveMessage.transfercomplete = Gestor [{0}] recibido desde nodo [{1}\:{2}] estado de sesi\u00F3n transferido.
+deltaManager.receiveMessage.unloadingAfter = Gestor [{0}]\: completada la descarga de sesiones
+deltaManager.receiveMessage.unloadingBegin = Gestor [{0}]\: iniciada descarga de sesiones
+deltaManager.receiveMessage.allSessionDataAfter = Gestor [{0}]\: estado de sesi\u00F3n deserializado
+deltaManager.receiveMessage.allSessionDataBegin = Gestor [{0}]\: recibidos datos de estado de sesi\u00F3n
+deltaManager.receiveMessage.fromWrongDomain = Gestor [{0}]\: Recibido SessionMessage equivocado de tipo\=({1}) desde [{2}] con dominio [{3}] (dominio local [{4}] 
+deltaManager.registerCluster = Registrar gestor {0} a elemento de cl\u00FAster {1} con nombre {2}
+deltaManager.sessionReceived = Gestor [{0}]; estado de sesi\u00F3n enviado a las {1} recibido en {2} ms.
+deltaManager.startClustering = Iniciando gestor de cl\u00FAster a las {0}
+deltaManager.stopped = El gestor [{0}] se est\u00E1 parando
+deltaManager.unloading.ioe = IOException al grabar sesiones persistentes\: {0}
+deltaManager.waitForSessionState = Gestor [{0}], requiriendo estado de sesi\u00F3n desde {1}. Esta operaci\u00F3n se agotar\u00E1 si no se recibe estado de sesi\u00F3n dentro de {2} segundos.
+deltaRequest.showPrincipal = El Principal [{0}] est\u00E1 puesto a sesi\u00F3n {1}
+deltaRequest.wrongPrincipalClass = DeltaManager s\u00F3lo soporta GenericPrincipal. Tu reino utiliz\u00F3 clase principal {0}.
+deltaSession.notifying = Notificando cl\u00FAster de expiraci\u00F3n primaria\={0} sessionId [{1}]
+deltaSession.valueBound.ex = Oyente ligado a sesi\u00F3n lanz\u00F3 una excepci\u00F3n
+deltaSession.valueBinding.ex = Oyente lig\u00E1ndose a sesi\u00F3n lanz\u00F3 una excepci\u00F3n
+deltaSession.valueUnbound.ex = Oyente desligado de sesi\u00F3n lanz\u00F3 una excepci\u00F3n
+deltaSession.readSession = readObject() cargando sesi\u00F3n [{0}]
+deltaSession.readAttribute = sesi\u00F3n [{0}] cargando atributo '{1}' con valor '{2}'
+deltaSession.writeSession = writeObject() guardando sesi\u00F3n [{0}]
+jvmRoute.cannotFindSession = No puedo hallar sesi\u00F3n [{0}]
+jvmRoute.changeSession = Cambiada sesi\u00F3n desde [{0}] a [{1}]
+jvmRoute.clusterListener.started = Cl\u00FAster JvmRouteSessionIDBinderListener arrancado
+jvmRoute.clusterListener.stopped = Cl\u00FAster JvmRouteSessionIDBinderListener parado
+jvmRoute.configure.warn = Por favor, \u00A1configura tu JvmRouteBinderValve en la v\u00E1lvula de m\u00E1quina, no en la v\u00E1lvula del contexto\!
+jvmRoute.contextNotFound = \u00A1Contexto [{0}] no hallado en el nodo [{1}]\!
+jvmRoute.failover = Detectada una ca\u00EDda con diferente jvmRoute - ruta original\: [{0}] nueva\: [{1}] en id de sesi\u00F3n [{2}]
+jvmRoute.foundManager = Hallado Cl\u00FAster DeltaManager {0} en {1}
+jvmRoute.hostNotFound = No hallada m\u00E1quina [{0}]
+jvmRoute.listener.started = Arrancado Oyente Ligador de SessionID
+jvmRoute.listener.stopped = Parado Oyente Ligador de SessionID
+jvmRoute.lostSession = Perdida Sesi\u00F3n [{0}] en ruta [{1}]
+jvmRoute.missingJvmRouteAttribute = \u00A1No se ha configurado atributo de motor jvmRoute\!
+jvmRoute.newSessionCookie = Poniendo cookie con id de sesi\u00F3n [{0}] nombre\: [{1}] ruta\: [{2}] seguro\: [{3}] httpOnly\: [{4}]
+jvmRoute.notFoundManager = No hallado Cl\u00FAster DeltaManager {0} en {1}
+jvmRoute.receiveMessage.sessionIDChanged = Cl\u00FAster JvmRouteSessionIDBinderListener recibi\u00F3 ID original de sesi\u00F3n [{0}] puesto a nuevo id [{1}] para la ruta de contexto [{2}]
+jvmRoute.run.already = receptor jvmRoute SessionID ya ejecutado
+jvmRoute.skipURLSessionIDs = \u00A1Saltado chequeo de reasignaci\u00F3n de ruta jvm, la sessionid viene desde URL\!
+jvmRoute.turnoverInfo = Ajustado tiempo de Chequeo a {0} mseg
+jvmRoute.valve.started = JvmRouteBinderValve arrancada
+jvmRoute.valve.stopped = JvmRouteBinderValve parada
+jvmRoute.set.orignalsessionid = Puesta id Orginal de Sesi\u00F3n en atributo de requerimiento {0} valor\: {1}
+standardSession.notSerializable = No puedo serializar atributo de sesi\u00F3n {0} para sesi\u00F3n {1}
+standardSession.removeAttribute.ise = removeAttribute\: Sesi\u00F3n ya invalidada
+standardSession.setAttribute.namenull = setAttribute\: par\u00E1metro de nombre no puede ser nulo
+serializablePrincipal.readPrincipal.cnfe = readPrincipal\: No pude volver a crea el usuario Principal
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/SerializablePrincipal.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/SerializablePrincipal.java
new file mode 100644
index 0000000..549a1bc
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/SerializablePrincipal.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.session;
+
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.catalina.Realm;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Generic implementation of <strong>java.security.Principal</strong> that
+ * is available for use by <code>Realm</code> implementations.
+ * The GenericPrincipal does NOT implement serializable and I didn't want to
+ * change that implementation hence I implemented this one instead.
+ * @author Filip Hanik
+ * @version $Id: SerializablePrincipal.java,v 1.1 2011/06/28 21:08:17 rherrmann Exp $
+ */
+public class SerializablePrincipal  implements java.io.Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog(SerializablePrincipal.class);
+    
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    // ----------------------------------------------------------- Constructors
+
+    public SerializablePrincipal() {
+        super();
+    }
+    
+    
+    /**
+     * Construct a new Principal, associated with the specified Realm, for the
+     * specified username and password.
+     *
+     * @param name The username of the user represented by this Principal
+     * @param password Credentials used to authenticate this user
+     */
+    public SerializablePrincipal(String name, String password) {
+
+        this(name, password, null);
+
+    }
+
+
+    /**
+     * Construct a new Principal, associated with the specified Realm, for the
+     * specified username and password, with the specified role names
+     * (as Strings).
+     *
+     * @param name The username of the user represented by this Principal
+     * @param password Credentials used to authenticate this user
+     * @param roles List of roles (must be Strings) possessed by this user
+     */
+    public SerializablePrincipal(String name, String password,
+                            List<String> roles) {
+        this(name, password, roles, null);
+    }
+
+    
+    /**
+     * Construct a new Principal, associated with the specified Realm, for the
+     * specified username and password, with the specified role names
+     * (as Strings).
+     *
+     * @param name The username of the user represented by this Principal
+     * @param password Credentials used to authenticate this user
+     * @param roles List of roles (must be Strings) possessed by this user
+     * @param userPrincipal The user principal to be exposed to applications
+     */
+    public SerializablePrincipal(String name, String password,
+                            List<String> roles, Principal userPrincipal) {
+
+        super();
+        this.name = name;
+        this.password = password;
+        if (roles != null) {
+            this.roles = new String[roles.size()];
+            this.roles = roles.toArray(this.roles);
+            if (this.roles.length > 0)
+                Arrays.sort(this.roles);
+        }
+        if (userPrincipal instanceof Serializable) {
+            this.userPrincipal = userPrincipal;
+        }
+    }
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * The username of the user represented by this Principal.
+     */
+    protected String name = null;
+
+    public String getName() {
+        return (this.name);
+    }
+
+
+    /**
+     * The authentication credentials for the user represented by
+     * this Principal.
+     */
+    protected String password = null;
+
+    public String getPassword() {
+        return (this.password);
+    }
+
+
+    /**
+     * The Realm with which this Principal is associated.
+     */
+    protected transient Realm realm = null;
+
+    public Realm getRealm() {
+        return (this.realm);
+    }
+
+    public void setRealm(Realm realm) {
+        this.realm = realm;
+    }
+
+
+    /**
+     * The set of roles associated with this user.
+     */
+    protected String roles[] = new String[0];
+
+    public String[] getRoles() {
+        return (this.roles);
+    }
+
+
+    /**
+     * The user principal, if present.
+     */
+    protected Principal userPrincipal = null;
+    
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return a String representation of this object, which exposes only
+     * information that should be public.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("SerializablePrincipal[");
+        sb.append(this.name);
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+    public static SerializablePrincipal createPrincipal(GenericPrincipal principal)
+    {
+        if ( principal==null) return null;
+        return new SerializablePrincipal(principal.getName(),
+                                         principal.getPassword(),
+                                         principal.getRoles()!=null?Arrays.asList(principal.getRoles()):null,
+                                         principal.getUserPrincipal()!=principal?principal.getUserPrincipal():null);
+    }
+
+    public GenericPrincipal getPrincipal()
+    {
+        return new GenericPrincipal(name, password,
+                getRoles()!=null?Arrays.asList(getRoles()):null,
+                userPrincipal);
+    }
+    
+    public static GenericPrincipal readPrincipal(ObjectInput in)
+            throws IOException, ClassNotFoundException {
+        String name = in.readUTF();
+        boolean hasPwd = in.readBoolean();
+        String pwd = null;
+        if ( hasPwd ) pwd = in.readUTF();
+        int size = in.readInt();
+        String[] roles = new String[size];
+        for ( int i=0; i<size; i++ ) roles[i] = in.readUTF();
+        Principal userPrincipal = null;
+        boolean hasUserPrincipal = in.readBoolean();
+        if (hasUserPrincipal) {
+            try {
+                userPrincipal = (Principal) in.readObject();
+            } catch (ClassNotFoundException e) {
+                log.error(sm.getString(
+                        "serializablePrincipal.readPrincipal.cnfe"), e);
+                throw e;
+            }
+        }
+        return new GenericPrincipal(name,pwd,Arrays.asList(roles),
+                userPrincipal);
+    }
+    
+    public static void writePrincipal(GenericPrincipal p, ObjectOutput out)
+            throws IOException {
+        out.writeUTF(p.getName());
+        out.writeBoolean(p.getPassword()!=null);
+        if ( p.getPassword()!= null ) out.writeUTF(p.getPassword());
+        String[] roles = p.getRoles();
+        if ( roles == null ) roles = new String[0];
+        out.writeInt(roles.length);
+        for ( int i=0; i<roles.length; i++ ) out.writeUTF(roles[i]);
+        boolean hasUserPrincipal = (p != p.getUserPrincipal() &&
+                p.getUserPrincipal() instanceof Serializable);
+        out.writeBoolean(hasUserPrincipal);
+        if (hasUserPrincipal) out.writeObject(p.getUserPrincipal());
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/SessionIDMessage.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/SessionIDMessage.java
new file mode 100644
index 0000000..5ab5b94
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/SessionIDMessage.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.catalina.ha.session;
+
+import org.apache.catalina.ha.ClusterMessageBase;
+
+/**
+ * Session id change cluster message
+ * 
+ * @author Peter Rossbach
+ * 
+ * @version $Id: SessionIDMessage.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+public class SessionIDMessage extends ClusterMessageBase {
+
+    private static final long serialVersionUID = 1L;
+
+    private int messageNumber;
+
+    private String orignalSessionID;
+
+    private String backupSessionID;
+
+    private String host ;
+    private String contextName;
+
+    @Override
+    public String getUniqueId() {
+        StringBuilder result = new StringBuilder(getOrignalSessionID());
+        result.append("#-#");
+        result.append(getHost());
+                result.append("#-#");
+                result.append(getContextName());
+        result.append("#-#");
+        result.append(getMessageNumber());
+        result.append("#-#");
+        result.append(System.currentTimeMillis());
+        return result.toString();
+    }
+
+    /**
+     * @return Returns the host.
+     */
+    public String getHost() {
+        return host;
+    }
+
+    /**
+     * @param host The host to set.
+     */
+    public void setHost(String host) {
+        this.host = host;
+    }
+    
+    /**
+     * @return Returns the context name.
+     */
+    public String getContextName() {
+        return contextName;
+    }
+    /**
+     * @param contextName The context name to set.
+     */
+    public void setContextName(String contextName) {
+        this.contextName = contextName;
+    }
+    /**
+     * @return Returns the messageNumber.
+     */
+    public int getMessageNumber() {
+        return messageNumber;
+    }
+
+    /**
+     * @param messageNumber
+     *            The messageNumber to set.
+     */
+    public void setMessageNumber(int messageNumber) {
+        this.messageNumber = messageNumber;
+    }
+
+    
+    /**
+     * @return Returns the backupSessionID.
+     */
+    public String getBackupSessionID() {
+        return backupSessionID;
+    }
+
+    /**
+     * @param backupSessionID
+     *            The backupSessionID to set.
+     */
+    public void setBackupSessionID(String backupSessionID) {
+        this.backupSessionID = backupSessionID;
+    }
+
+    /**
+     * @return Returns the orignalSessionID.
+     */
+    public String getOrignalSessionID() {
+        return orignalSessionID;
+    }
+
+    /**
+     * @param orignalSessionID
+     *            The orignalSessionID to set.
+     */
+    public void setOrignalSessionID(String orignalSessionID) {
+        this.orignalSessionID = orignalSessionID;
+    }
+
+    @Override
+    public String toString() {
+        return "SESSIONID-UPDATE#" + getHost() + "." + getContextName() + "#" + getOrignalSessionID() + ":" + getBackupSessionID();
+    }
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/SessionMessage.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/SessionMessage.java
new file mode 100644
index 0000000..7c09309
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/SessionMessage.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ha.session;
+import org.apache.catalina.ha.ClusterMessage;
+
+/**
+ *
+ * <B>Class Description:</B><BR>
+ * The SessionMessage class is a class that is used when a session has been
+ * created, modified, expired in a Tomcat cluster node.<BR>
+ *
+ * The following events are currently available:
+ * <ul>
+ *   <li><pre>public static final int EVT_SESSION_CREATED</pre><li>
+ *   <li><pre>public static final int EVT_SESSION_EXPIRED</pre><li>
+ *   <li><pre>public static final int EVT_SESSION_ACCESSED</pre><li>
+ *   <li><pre>public static final int EVT_GET_ALL_SESSIONS</pre><li>
+ *   <li><pre>public static final int EVT_SESSION_DELTA</pre><li>
+ *   <li><pre>public static final int EVT_ALL_SESSION_DATA</pre><li>
+ *   <li><pre>public static final int EVT_ALL_SESSION_TRANSFERCOMPLETE</pre><li>
+ *   <li><pre>public static final int EVT_CHANGE_SESSION_ID</pre><li>
+ * </ul>
+ *
+ */
+
+public interface SessionMessage extends ClusterMessage {
+
+    /**
+     * Event type used when a session has been created on a node
+     */
+    public static final int EVT_SESSION_CREATED = 1;
+    /**
+     * Event type used when a session has expired
+     */
+    public static final int EVT_SESSION_EXPIRED = 2;
+
+    /**
+     * Event type used when a session has been accessed (ie, last access time
+     * has been updated. This is used so that the replicated sessions will not expire
+     * on the network
+     */
+    public static final int EVT_SESSION_ACCESSED = 3;
+    /**
+     * Event type used when a server comes online for the first time.
+     * The first thing the newly started server wants to do is to grab the
+     * all the sessions from one of the nodes and keep the same state in there
+     */
+    public static final int EVT_GET_ALL_SESSIONS = 4;
+    /**
+     * Event type used when an attribute has been added to a session,
+     * the attribute will be sent to all the other nodes in the cluster
+     */
+    public static final int EVT_SESSION_DELTA  = 13;
+
+    /**
+     * When a session state is transferred, this is the event.
+     */
+    public static final int EVT_ALL_SESSION_DATA = 12;
+    
+    /**
+     * When a session state is complete transferred, this is the event.
+     */
+    public static final int EVT_ALL_SESSION_TRANSFERCOMPLETE = 14;
+
+    /**
+     * Event type used when a sessionID has been changed.
+     */
+    public static final int EVT_CHANGE_SESSION_ID = 15;
+
+    
+    public String getContextName();
+    
+    public String getEventTypeString();
+    
+    /**
+     * returns the event type
+     * @return one of the event types EVT_XXXX
+     */
+    public int getEventType(); 
+    /**
+     * @return the serialized data for the session
+     */
+    public byte[] getSession();
+    /**
+     * @return the session ID for the session
+     */
+    public String getSessionID();
+
+
+}//SessionMessage
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/SessionMessageImpl.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/SessionMessageImpl.java
new file mode 100644
index 0000000..857fb4e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/SessionMessageImpl.java
@@ -0,0 +1,175 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.session;
+
+
+import org.apache.catalina.ha.ClusterMessageBase;
+
+/**
+ * Session cluster message
+ * 
+ * @author Filip Hanik
+ * @author Peter Rossbach
+ * 
+ * @version $Id: SessionMessageImpl.java,v 1.1 2011/06/28 21:08:17 rherrmann Exp $
+ */
+public class SessionMessageImpl extends ClusterMessageBase implements SessionMessage {
+    
+    private static final long serialVersionUID = 1L;
+
+
+    public SessionMessageImpl() {
+    }
+    
+    
+    /*
+
+     * Private serializable variables to keep the messages state
+     */
+    private int mEvtType = -1;
+    private byte[] mSession;
+    private String mSessionID;
+
+    private String mContextName;
+    private long serializationTimestamp;
+    private boolean timestampSet = false ;
+    private String uniqueId;
+
+
+    private SessionMessageImpl( String contextName,
+                           int eventtype,
+                           byte[] session,
+                           String sessionID)
+    {
+        mEvtType = eventtype;
+        mSession = session;
+        mSessionID = sessionID;
+        mContextName = contextName;
+        uniqueId = sessionID;
+    }
+
+    /**
+     * Creates a session message. Depending on what event type you want this
+     * message to represent, you populate the different parameters in the constructor<BR>
+      * The following rules apply dependent on what event type argument you use:<BR>
+     * <B>EVT_SESSION_CREATED</B><BR>
+     *    The parameters: session, sessionID must be set.<BR>
+     * <B>EVT_SESSION_EXPIRED</B><BR>
+     *    The parameters: sessionID must be set.<BR>
+     * <B>EVT_SESSION_ACCESSED</B><BR>
+     *    The parameters: sessionID must be set.<BR>
+     * <B>EVT_GET_ALL_SESSIONS</B><BR>
+     *    get all sessions from from one of the nodes.<BR>
+     * <B>EVT_SESSION_DELTA</B><BR>
+     *    Send attribute delta (add,update,remove attribute or principal, ...).<BR>
+     * <B>EVT_ALL_SESSION_DATA</B><BR>
+     *    Send complete serializes session list<BR>
+     * <B>EVT_ALL_SESSION_TRANSFERCOMPLETE</B><BR>
+     *    send that all session state information are transfered
+     *    after GET_ALL_SESSION received from this sender.<BR>
+     * <B>EVT_CHANGE_SESSION_ID</B><BR>
+     *    send original sessionID and new sessionID.<BR>
+     * @param contextName - the name of the context (application
+     * @param eventtype - one of the 8 event type defined in this class
+     * @param session - the serialized byte array of the session itself
+     * @param sessionID - the id that identifies this session
+     * @param uniqueID - the id that identifies this message
+     */
+    public SessionMessageImpl( String contextName,
+                           int eventtype,
+                           byte[] session,
+                           String sessionID,
+                           String uniqueID)
+    {
+        this(contextName,eventtype,session,sessionID);
+        uniqueId = uniqueID;
+    }
+
+    /**
+     * returns the event type
+     * @return one of the event types EVT_XXXX
+     */
+    @Override
+    public int getEventType() { return mEvtType; }
+
+    /**
+     * @return the serialized data for the session
+     */
+    @Override
+    public byte[] getSession() { return mSession;}
+
+    /**
+     * @return the session ID for the session
+     */
+    @Override
+    public String getSessionID(){ return mSessionID; }
+    
+    /**
+     * set message send time but only the first setting works (one shot)
+     */
+    @Override
+    public void setTimestamp(long time) {
+        synchronized(this) {
+            if(!timestampSet) {
+                serializationTimestamp=time;
+                timestampSet = true ;
+            }
+        }
+    }
+    
+    @Override
+    public long getTimestamp() { return serializationTimestamp;}
+    
+    /**
+     * clear text event type name (for logging purpose only) 
+     * @return the event type in a string representation, useful for debugging
+     */
+    @Override
+    public String getEventTypeString()
+    {
+        switch (mEvtType)
+        {
+            case EVT_SESSION_CREATED : return "SESSION-MODIFIED";
+            case EVT_SESSION_EXPIRED : return "SESSION-EXPIRED";
+            case EVT_SESSION_ACCESSED : return "SESSION-ACCESSED";
+            case EVT_GET_ALL_SESSIONS : return "SESSION-GET-ALL";
+            case EVT_SESSION_DELTA : return "SESSION-DELTA";
+            case EVT_ALL_SESSION_DATA : return "ALL-SESSION-DATA";
+            case EVT_ALL_SESSION_TRANSFERCOMPLETE : return "SESSION-STATE-TRANSFERED";
+            case EVT_CHANGE_SESSION_ID : return "SESSION-ID-CHANGED";
+            default : return "UNKNOWN-EVENT-TYPE";
+        }
+    }
+
+    @Override
+    public String getContextName() {
+       return mContextName;
+    }
+    @Override
+    public String getUniqueId() {
+        return uniqueId;
+    }
+    @Override
+    public void setUniqueId(String uniqueId) {
+        this.uniqueId = uniqueId;
+    }
+    
+    @Override
+    public String toString() {
+        return getEventTypeString() + "#" + getContextName() + "#" + getSessionID() ;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/mbeans-descriptors.xml b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/mbeans-descriptors.xml
new file mode 100644
index 0000000..afb2922
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/session/mbeans-descriptors.xml
@@ -0,0 +1,622 @@
+<?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="JvmRouteBinderValve"
+    description="mod_jk jvmRoute jsessionid cookie backup correction"
+    domain="Catalina"
+    group="Valve"
+    type="org.apache.catalina.ha.session.JvmRouteBinderValve">
+    <attribute
+      name="asyncSupported"
+      description="Does this valve support async reporting? "
+      type="boolean"/>
+    <attribute
+      name="className"
+      description="Fully qualified class name of the managed object"
+      type="java.lang.String"
+      writeable="false"/>
+    <attribute
+      name="enabled"
+      description="enable a jvm Route check"
+      type="boolean"/>
+    <attribute
+      name="info"
+      description="describe version"
+      type="java.lang.String"
+      writeable="false"/>
+    <attribute
+      name="numberOfSessions"
+      description="number of jvmRoute session corrections"
+      type="long"
+      writeable="false"/>
+    <attribute
+      name="sessionIdAttribute"
+      description="Name of attribute with sessionid value before turnover a session"
+      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"/>
+    <operation
+      name="start"
+      description="Stops the Cluster JvmRouteBinderValve"
+      impact="ACTION"
+      returnType="void"/>
+    <operation
+      name="stop"
+      description="Stops the Cluster JvmRouteBinderValve"
+      impact="ACTION"
+      returnType="void"/>
+  </mbean>
+  <mbean
+    name="JvmRouteSessionIDBinderListener"
+    description="Monitors the jvmRoute activity"
+    domain="Catalina"
+    group="Listener"
+    type="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener">
+    <attribute
+      name="info"
+      description="describe version"
+      type="java.lang.String"
+      writeable="false"/>
+    <attribute
+      name="numberOfSessions"
+      description="number of jvmRoute session corrections"
+      type="long"
+      writeable="false"/>
+  </mbean>
+  <mbean
+    name="DeltaManager"
+    description="Cluster Manager implementation of the Manager interface"
+    domain="Catalina"
+    group="Manager"
+    type="org.apache.catalina.ha.session.DeltaManager">
+    <attribute
+      name="activeSessions"
+      description="Number of active sessions at this moment"
+      type="int"
+      writeable="false"/>
+    <attribute
+      name="algorithm"
+      description="The message digest algorithm to be used when generating session identifiers"
+      type="java.lang.String"/>
+    <attribute
+      name="className"
+      description="Fully qualified class name of the managed object"
+      type="java.lang.String"
+      writeable="false"/>
+    <attribute
+      name="counterNoStateTransfered"
+      description="Count the failed session transfers noStateTransfered"
+      type="int"
+      writeable="false"/>
+    <attribute
+      name="counterReceive_EVT_GET_ALL_SESSIONS"
+      description="Count receive EVT_GET_ALL_SESSIONS messages"
+      type="long"
+      writeable="false"/>
+    <attribute
+      name="counterReceive_EVT_ALL_SESSION_DATA"
+      description="Count receive EVT_ALL_SESSION_DATA messages"
+      type="long"
+      writeable="false"/>
+    <attribute
+      name="counterReceive_EVT_SESSION_CREATED"
+      description="Count receive EVT_SESSION_CREATED messages"
+      type="long"
+      writeable="false"/>
+    <attribute
+      name="counterReceive_EVT_SESSION_DELTA"
+      description="Count receive EVT_SESSION_DELTA messages"
+      type="long"
+      writeable="false"/>
+    <attribute
+      name="counterReceive_EVT_SESSION_ACCESSED"
+      description="Count receive EVT_SESSION_ACCESSED messages"
+      type="long"
+      writeable="false"/>
+    <attribute
+      name="counterReceive_EVT_SESSION_EXPIRED"
+      description="Count receive EVT_SESSION_EXPIRED messages"
+      type="long"
+      writeable="false"/>
+    <attribute
+      name="counterReceive_EVT_ALL_SESSION_TRANSFERCOMPLETE"
+      description="Count receive EVT_ALL_SESSION_TRANSFERCOMPLETE messages"
+      type="long"
+      writeable="false"/>
+    <attribute
+      name="counterReceive_EVT_CHANGE_SESSION_ID"
+      description="Count receive EVT_CHANGE_SESSION_ID messages"
+      type="long"
+      writeable="false"/>
+    <attribute
+      name="counterSend_EVT_GET_ALL_SESSIONS"
+      description="Count send EVT_GET_ALL_SESSIONS messages"
+      type="long"
+      writeable="false"/>
+    <attribute
+      name="counterSend_EVT_ALL_SESSION_DATA"
+      description="Count send EVT_ALL_SESSION_DATA messages"
+      type="long"
+      writeable="false"/>
+    <attribute
+      name="counterSend_EVT_SESSION_CREATED"
+      description="Count send EVT_SESSION_CREATED messages"
+      type="long"
+      writeable="false"/>
+    <attribute
+      name="counterSend_EVT_SESSION_DELTA"
+      description="Count send EVT_SESSION_DELTA messages"
+      type="long"
+      writeable="false"/>
+    <attribute
+      name="counterSend_EVT_SESSION_ACCESSED"
+      description="Count send EVT_SESSION_ACCESSED messages"
+      type="long"
+      writeable="false"/>
+    <attribute
+      name="counterSend_EVT_SESSION_EXPIRED"
+      description="Count send EVT_SESSION_EXPIRED messages"
+      type="long"
+      writeable="false"/>
+    <attribute
+      name="counterSend_EVT_ALL_SESSION_TRANSFERCOMPLETE"
+      description="Count send EVT_ALL_SESSION_TRANSFERCOMPLETE messages"
+      type="long"
+      writeable="false"/>
+    <attribute
+      name="counterSend_EVT_CHANGE_SESSION_ID"
+      description="Count send EVT_CHANGE_SESSION_ID messages"
+      type="long"
+      writeable="false"/>
+    <attribute
+      name="distributable"
+      description="The distributable flag for Sessions created by this Manager"
+      type="boolean"/>
+    <attribute
+      name="duplicates"
+      description="Number of duplicated session ids generated"
+      type="int"/>
+    <attribute
+      name="entropy"
+      description="A String initialization parameter used to increase the entropy of the initialization of our random number generator"
+      type="java.lang.String"/>
+    <attribute
+      name="expiredSessions"
+      description="Number of sessions that expired ( doesn't include explicit invalidations )"
+      type="long"/>
+    <attribute
+      name="expireSessionsOnShutdown"
+      is="true"
+      description="exipre all sessions cluster wide as one node goes down"
+      type="boolean"/>
+    <attribute
+      name="info"
+      description="describe version"
+      type="java.lang.String"
+      writeable="false"/>
+    <attribute
+      name="invalidatedSessions"
+      description="describe version"
+      type="[Ljava.lang.String;"
+      writeable="false"/>
+    <attribute
+      name="maxActive"
+      description="Maximum number of active sessions so far"
+      type="int"/>
+    <attribute
+      name="maxActiveSessions"
+      description="The maximum number of active Sessions allowed, or -1 for no limit"
+      type="int"/>
+    <attribute
+      name="maxInactiveInterval"
+      description="The default maximum inactive interval for Sessions created by this Manager"
+      type="int"/>
+    <attribute
+      name="name"
+      description="The descriptive name of this Manager implementation (for logging)"
+      type="java.lang.String"
+      writeable="false"/>
+    <attribute
+      name="notifyListenersOnReplication"
+      is="true"
+      description="Send session attribute change events on backup nodes"
+      type="boolean"/>
+    <attribute
+      name="notifySessionListenersOnReplication"
+      is="true"
+      description="Send session start/stop events on backup nodes"
+      type="boolean"/>
+    <attribute
+      name="processExpiresFrequency"
+      description="The frequency of the manager checks (expiration and passivation)"
+      type="int"/>
+    <attribute
+      name="processingTime"
+      description="Time spent doing housekeeping and expiration"
+      type="long"/>
+    <attribute
+      name="randomFile"
+      description="File source of random - /dev/urandom or a pipe"
+      type="java.lang.String"/>
+    <attribute
+      name="sendAllSessions"
+      is="true"
+      description="Send all sessions at one big block"
+      type="boolean"/>
+    <attribute
+      name="sendAllSessionsSize"
+      description="session block size when sendAllSessions=false (default=1000)"
+      type="int"/>
+    <attribute
+      name="sendAllSessionsWaitTime"
+      description="wait time between send session block (default 2 sec)"
+      type="int"/>
+    <attribute
+      name="sessionAverageAliveTime"
+      description="Average time an expired session had been alive"
+      type="int"/>
+    <attribute
+      name="sessionCounter"
+      description="Total number of sessions created by this manager"
+      type="long"/>
+    <attribute
+      name="sessionIdLength"
+      description="The session id length (in bytes) of Sessions created by this Manager"
+      type="int"/>
+    <attribute
+      name="sessionMaxAliveTime"
+      description="Longest time an expired session had been alive"
+      type="int"/>
+    <attribute
+      name="sessionReplaceCounter"
+      description="Total number of replaced sessions that load from external nodes"
+      type="long"
+      writeable="false"/>
+    <attribute name="stateName"
+      description="The name of the LifecycleState that this component is currently in"
+      type="java.lang.String"
+      writeable="false"/>
+    <attribute
+      name="stateTransfered"
+      is="true"
+      description="Is session state transfered complete? "
+      type="boolean"/>  
+    <attribute
+      name="stateTransferTimeout"
+      description="state transfer timeout in sec"
+      type="int"/>
+    <attribute
+      name="receivedQueueSize"
+      description="length of receive queue size when session received from other node"
+      type="int"
+      writeable="false"/>
+    <attribute
+      name="rejectedSessions"
+      description="Number of sessions we rejected due to maxActive beeing reached"
+      type="int"
+      writeable="false"/>
+    <operation
+      name="expireSession"
+      description="Expired the given session"
+      impact="ACTION"
+      returnType="void">
+      <parameter
+        name="sessionId"
+        description="The session id for the session to be expired"
+        type="java.lang.String"/>
+    </operation>
+    <operation
+      name="expireAllLocalSessions"
+      description="Exipre all active local sessions and replicate the invalid sessions"
+      impact="ACTION"
+      returnType="void"/>
+    <operation
+      name="findSession"
+      description="Return the active Session, associated with this Manager, with the specified session id (if any)"
+      impact="ACTION"
+      returnType="org.apache.catalina.Session">
+      <parameter
+        name="id"
+        description="The session id for the session to be returned"
+        type="java.lang.String"/>
+    </operation>
+    <operation
+      name="findSessions"
+      description="Return the set of active Sessions associated with this Manager."
+      impact="ACTION"
+      returnType="[Lorg.apache.catalina.Session;">
+    </operation>  
+    <operation
+      name="getAllClusterSessions"
+      description="send to oldest cluster member that this node need all cluster sessions (resync member)"
+      impact="ACTION"
+      returnType="void"/>
+    <operation
+      name="getCreationTime"
+      description="Return the creatio time for this session"
+      impact="ACTION"
+      returnType="java.lang.String">
+      <parameter
+        name="sessionId"
+        description="The session id for the session "
+        type="java.lang.String"/>
+    </operation>   
+    <operation
+      name="getLastAccessedTime"
+      description="Get the last access time. This one gets updated whenever a request finishes. "
+      impact="ACTION"
+      returnType="java.lang.String">
+      <parameter
+        name="sessionId"
+        description="Id of the session"
+        type="java.lang.String"/>
+    </operation> 
+    <operation
+      name="getSessionAttribute"
+      description="Return a session attribute"
+      impact="ACTION"
+      returnType="java.lang.String">
+      <parameter
+        name="sessionId"
+        description="Id of the session"
+        type="java.lang.String"/>
+      <parameter
+        name="key"
+        description="key of the attribute"
+        type="java.lang.String"/>
+    </operation>
+    <operation
+      name="getThisAccessedTime"
+      description="Get the last access time. This one gets updated whenever a request starts. "
+      impact="ACTION"
+      returnType="java.lang.String">
+      <parameter
+        name="sessionId"
+        description="Id of the session"
+        type="java.lang.String"/>
+    </operation> 
+    <operation
+      name="listSessionIds"
+      description="Return the list of active primary session ids"
+      impact="ACTION"
+      returnType="java.lang.String"/>
+    <operation
+      name="processExpires"
+      description="Invalidate all sessions that have expired.s"
+      impact="ACTION"
+      returnType="void"/>
+    <operation
+      name="resetStatistics"
+      description="Reset all statistics"
+      impact="ACTION"
+      returnType="void"/>
+  </mbean>
+  <mbean
+    name="BackupManager"
+    description="Cluster Manager implementation of the Manager interface"
+    domain="Catalina"
+    group="Manager"
+    type="org.apache.catalina.ha.session.BackupManager">
+    <attribute
+      name="activeSessions"
+      description="Number of active primary sessions at this moment"
+      type="int"
+      writeable="false"/>
+    <attribute
+      name="activeSessionsFull"
+      description="Number of active sessions at this moment"
+      type="int"
+      writeable="false"/>
+    <attribute
+      name="algorithm"
+      description="The message digest algorithm to be used when generating session identifiers"
+      type="java.lang.String"/>
+    <attribute
+      name="className"
+      description="Fully qualified class name of the managed object"
+      type="java.lang.String"
+      writeable="false"/>
+    <attribute
+      name="distributable"
+      description="The distributable flag for Sessions created by this Manager"
+      type="boolean"/>
+    <attribute
+      name="duplicates"
+      description="Number of duplicated session ids generated"
+      type="int"/>
+    <attribute
+      name="entropy"
+      description="A String initialization parameter used to increase the entropy of the initialization of our random number generator"
+      type="java.lang.String"/>
+    <attribute
+      name="expiredSessions"
+      description="Number of sessions that expired ( doesn't include explicit invalidations )"
+      type="long"/>
+    <attribute
+      name="expireSessionsOnShutdown"
+      is="true"
+      description="exipre all sessions cluster wide as one node goes down"
+      type="boolean"/>
+    <attribute
+      name="invalidatedSessions"
+      description="Get the list of invalidated session."
+      type="[Ljava.lang.String;"/>
+    <attribute
+      name="mapName"
+      description="mapName"
+      type="java.lang.String"
+      writeable="false"/>
+    <attribute
+      name="mapSendOptions"
+      description="mapSendOptions"
+      type="int"
+      writeable="false"/>
+    <attribute
+      name="maxActive"
+      description="Maximum number of active sessions so far"
+      type="int"/>
+    <attribute
+      name="maxActiveSessions"
+      description="The maximum number of active Sessions allowed, or -1 for no limit"
+      type="int"/>
+    <attribute
+      name="maxInactiveInterval"
+      description="The default maximum inactive interval for Sessions created by this Manager"
+      type="int"/>
+    <attribute
+      name="name"
+      description="The name of component. "
+      type="java.lang.String"/>
+    <attribute
+      name="notifyListenersOnReplication"
+      is="true"
+      description="Send session attribute change events on backup nodes"
+      type="boolean"/>
+    <attribute
+      name="pathname"
+      description="Path name of the disk file in which active sessions"
+      type="java.lang.String"/>
+    <attribute
+      name="processExpiresFrequency"
+      description="The frequency of the manager checks (expiration and passivation)"
+      type="int"/>
+    <attribute
+      name="processingTime"
+      description="Time spent doing housekeeping and expiration"
+      type="long"/>
+    <attribute
+      name="sessionAverageAliveTime"
+      description="Average time an expired session had been alive"
+      type="int"/>
+    <attribute
+      name="sessionCounter"
+      description="Total number of sessions created by this manager"
+      type="long"/>
+    <attribute
+      name="sessionIdLength"
+      description="The session id length (in bytes) of Sessions created by this Manager"
+      type="int"/>
+    <attribute
+      name="sessionMaxAliveTime"
+      description="Longest time an expired session had been alive"
+      type="int"/>
+    <attribute name="stateName"
+      description="The name of the LifecycleState that this component is currently in"
+      type="java.lang.String"
+      writeable="false"/>
+    <attribute
+      name="randomFile"
+      description="File source of random - /dev/urandom or a pipe"
+      type="java.lang.String"/>
+    <attribute
+      name="rejectedSessions"
+      description="Number of sessions we rejected due to maxActive beeing reached"
+      type="int"/>
+    <operation
+      name="expireSession"
+      description="Expired the given session"
+      impact="ACTION"
+      returnType="void">
+      <parameter
+        name="sessionId"
+        description="The session id for the session to be expired"
+        type="java.lang.String"/>
+    </operation>
+    <operation
+      name="findSession"
+      description="Return the active Session, associated with this Manager, with the specified session id (if any)"
+      impact="ACTION"
+      returnType="org.apache.catalina.Session">
+      <parameter
+        name="id"
+        description="The session id for the session to be returned"
+        type="java.lang.String"/>
+    </operation>
+    <operation
+      name="findSessions"
+      description="Return the set of active Sessions associated with this Manager."
+      impact="ACTION"
+      returnType="[Lorg.apache.catalina.Session;">
+    </operation>  
+    <operation
+      name="getCreationTime"
+      description="Return the creatio time for this session"
+      impact="ACTION"
+      returnType="java.lang.String">
+      <parameter
+        name="sessionId"
+        description="The session id for the session "
+        type="java.lang.String"/>
+    </operation>   
+    <operation
+      name="getLastAccessedTime"
+      description="Get the last access time. This one gets updated whenever a request finishes. "
+      impact="ACTION"
+      returnType="java.lang.String">
+      <parameter
+        name="sessionId"
+        description="Id of the session"
+        type="java.lang.String"/>
+    </operation> 
+    <operation
+      name="getSessionAttribute"
+      description="Return a session attribute"
+      impact="ACTION"
+      returnType="java.lang.String">
+      <parameter
+        name="sessionId"
+        description="Id of the session"
+        type="java.lang.String"/>
+      <parameter
+        name="key"
+        description="key of the attribute"
+        type="java.lang.String"/>
+    </operation>
+    <operation
+      name="getThisAccessedTime"
+      description="Get the last access time. This one gets updated whenever a request starts. "
+      impact="ACTION"
+      returnType="java.lang.String">
+      <parameter
+        name="sessionId"
+        description="Id of the session"
+        type="java.lang.String"/>
+    </operation> 
+    <operation
+      name="listSessionIds"
+      description="Return the list of active primary session ids"
+      impact="ACTION"
+      returnType="java.lang.String"/>
+    <operation
+      name="listSessionIdsFull"
+      description="Return the list of active session ids"
+      impact="ACTION"
+      returnType="java.lang.String"/>
+    <operation
+      name="processExpires"
+      description="Invalidate all sessions that have expired.s"
+      impact="ACTION"
+      returnType="void"/>
+  </mbean>
+</mbeans-descriptors>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/tcp/Constants.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/tcp/Constants.java
new file mode 100644
index 0000000..34e8a7d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/tcp/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.catalina.ha.tcp;
+
+/**
+ * Manifest constants for the <code>org.apache.catalina.ha.tcp</code>
+ * package.
+ *
+ * @author Peter Rossbach
+ * @version $Id: Constants.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public class Constants {
+
+    public static final String Package = "org.apache.catalina.ha.tcp";
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/tcp/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/tcp/LocalStrings.properties
new file mode 100644
index 0000000..8746fba
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/tcp/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.
+
+AsyncSocketSender.create.thread=Create sender [{0}:{1,number,integer}] queue thread to tcp background replication
+AsyncSocketSender.queue.message=Queue message to [{0}:{1,number,integer}] id=[{2}] size={3}
+AsyncSocketSender.send.error=Unable to asynchronously send session with id=[{0}] - message will be ignored.
+AsyncSocketSender.queue.empty=Queue in sender [{0}:{1,number,integer}] returned null element!
+cluster.mbean.register.already=MBean {0} already registered!
+FastAsyncSocketSender.setThreadPriority=[{0}:{1,number,integer}] set priority to {2}
+FastAsyncSocketSender.min.exception=[{0}:{1,number,integer}] new priority {2} < MIN_PRIORITY
+FastAsyncSocketSender.max.exception=[{0}:{1,number,integer}] new priority {2} > MAX_PRIORITY
+IDataSender.ack.eof=EOF reached at local port [{0}:{1,number,integer}]
+IDataSender.ack.receive=Got ACK at local port [{0}:{1,number,integer}]
+IDataSender.ack.missing=Unable to read acknowledgement from [{0}:{1,number,integer}] in {2,number,integer} ms. Disconnecting socket, and trying again.
+IDataSender.ack.read=Read wait ack char '{2}' [{0}:{1,number,integer}]
+IDataSender.ack.start=Waiting for ACK message [{0}:{1,number,integer}]
+IDataSender.ack.wrong=Missing correct ACK after 10 bytes read at local port [{0}:{1,number,integer}]
+IDataSender.closeSocket=Sender close socket to [{0}:{1,number,integer}] (close count {2,number,integer})
+IDataSender.connect=Sender connect to [{0}:{1,number,integer}] (connect count {2,number,integer})
+IDataSender.create=Create sender [{0}:{1,number,integer}]
+IDataSender.disconnect=Sender disconnect from [{0}:{1,number,integer}] (disconnect count {2,number,integer})
+IDataSender.message.disconnect=Message transfered: Sender can't disconnect from [{0}:{1,number,integer}]
+IDataSender.message.create=Message transfered: Sender can't create current socket [{0}:{1,number,integer}]
+IDataSender.openSocket=Sender open socket to [{0}:{1,number,integer}] (open count {2,number,integer})
+IDataSender.openSocket.failure=Open sender socket [{0}:{1,number,integer}] failure! (open failure count {2,number,integer})
+IDataSender.send.again=Send data again to [{0}:{1,number,integer}]
+IDataSender.send.crash=Send message crashed [{0}:{1,number,integer}] type=[{2}], id=[{3}]
+IDataSender.send.message=Send message to [{0}:{1,number,integer}] id=[{2}] size={3,number,integer}
+IDataSender.send.lost=Message lost: [{0}:{1,number,integer}] type=[{2}], id=[{3}]
+IDataSender.senderModes.Configured=Configured a data replication sender for mode {0}
+IDataSender.senderModes.Instantiate=Can't instantiate a data replication sender of class {0}
+IDataSender.senderModes.Missing=Can't configure a data replication sender for mode {0}
+IDataSender.senderModes.Resources=Can't load data replication sender mapping list
+IDataSender.stats=Send stats from [{0}:{1,number,integer}], Nr of bytes sent={2,number,integer} over {3} = {4,number,integer} bytes/request, processing time {5,number,integer} msec, avg processing time {6,number,integer} msec
+PoolSocketSender.senderQueue.sender.failed=PoolSocketSender create new sender to [{0}:{1,number,integer}] failed
+PoolSocketSender.noMoreSender=No socket sender available for client [{0}:{1,number,integer}] did it disappeared?
+ReplicationTransmitter.getProperty=get property {0}
+ReplicationTransmitter.setProperty=set property {0}: {1} old value {2}
+ReplicationTransmitter.started=Start ClusterSender at cluster {0} with name {1}
+ReplicationTransmitter.stopped=Stopped ClusterSender at cluster {0} with name {1}
+ReplicationValve.crossContext.add=add Cross Context session replication container to replicationValve threadlocal
+ReplicationValve.crossContext.registerSession=register Cross context session id={0} from context {1}
+ReplicationValve.crossContext.remove=remove Cross Context session replication container from replicationValve threadlocal
+ReplicationValve.crossContext.sendDelta=send Cross Context session delta from context {0}.
+ReplicationValve.filter.loading=Loading request filter={0}
+ReplicationValve.filter.failure=Unable to compile filter={0}
+ReplicationValve.invoke.uri=Invoking replication request on {0}
+ReplicationValve.nocluster=No cluster configured for this request.
+ReplicationValve.resetDeltaRequest=Cluster is standalone: reset Session Request Delta at context {0}
+ReplicationValve.send.failure=Unable to perform replication request.
+ReplicationValve.send.invalid.failure=Unable to send session [id={0}] invalid message over cluster.
+ReplicationValve.session.found=Context {0}: Found session {1} but it isn't a ClusterSession.
+ReplicationValve.session.indicator=Context {0}: Primarity of session {0} in request attribute {1} is {2}.
+ReplicationValve.session.invalid=Context {0}: Requested session {1} is invalid, removed or not replicated at this node.
+ReplicationValve.stats=Average request time= {0} ms for Cluster overhead time={1} ms for {2} requests {3} filter requests {4} send requests {5} cross context requests (Request={6} ms Cluster={7} ms).
+SimpleTcpCluster.event.log=Cluster receive listener event {0} with data {1}
+SimpleTcpCluster.getProperty=get property {0}
+SimpleTcpCluster.setProperty=set property {0}: {1} old value {2}
+SimpleTcpCluster.default.addClusterListener=Add Default ClusterListener at cluster {0}
+SimpleTcpCluster.default.addClusterValves=Add Default ClusterValves at cluster {0}
+SimpleTcpCluster.default.addClusterReceiver=Add Default ClusterReceiver at cluster {0}
+SimpleTcpCluster.default.addClusterSender=Add Default ClusterSender at cluster {0}
+SimpleTcpCluster.default.addMembershipService=Add Default Membership Service at cluster {0}
+SimpleTcpCluster.log.receive=RECEIVE {0,date}:{0,time} {1,number} {2}:{3,number,integer} {4} {5}
+SimpleTcpCluster.log.send=SEND {0,date}:{0,time} {1,number} {2}:{3,number,integer} {4} 
+SimpleTcpCluster.log.send.all=SEND {0,date}:{0,time} {1,number} - {2}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/tcp/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/tcp/LocalStrings_es.properties
new file mode 100644
index 0000000..823c366
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/tcp/LocalStrings_es.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.
+
+AsyncSocketSender.create.thread = Crear remitente [{0}\:{1,number,integer}] poner hilo en cola a r\u00E9plica tcp en segundo plano
+AsyncSocketSender.queue.message = Poner mensaje en cola para [{0}\:{1,number,integer}] id\=[{2}] medida\={3}
+AsyncSocketSender.send.error = No puedo enviar de forma as\u00EDncrona sesi\u00F3n con id\=[{0}] - el mensaje ser\u00E1 ignorado.
+AsyncSocketSender.queue.empty = Poner en cola en remitente [{0}\:{1,number,integer}] \u00A1devolvi\u00F3 elemento nulo\!
+cluster.mbean.register.already = \u00A1MBean {0} ya registrado\!
+FastAsyncSocketSender.setThreadPriority = [{0}\:{1,number,integer}] puesta prioridad a {2}
+FastAsyncSocketSender.min.exception = [{0}\:{1,number,integer}]}] nueva prioridad {2} < MIN_PRIORITY
+FastAsyncSocketSender.max.exception = [{0}\:{1,number,integer}]}] nueva prioridad {2} > MAX_PRIORITY
+IDataSender.ack.eof = EOF alcanzado en puerto local [{0}\:{1,number,integer}]
+IDataSender.ack.receive = Obtenido un ACK en puerto local [{0}\:{1,number,integer}]
+IDataSender.ack.missing = No puedo leer reconocimiento desde [{0}\:{1,number,integer}] en {2,number,integer} ms. Desconectadno conector y probando otra vez.
+IDataSender.ack.read = Le\u00EDdo car\u00E1cter ack de espera '{2}' [{0}\:{1,number,integer}]
+IDataSender.ack.start = Eperando por mensaje ACK [{0}\:{1,number,integer}]
+IDataSender.ack.wrong = Falta ACK correcto tras leer 10 bytes en puerto local [{0}\:{1,number,integer}]
+IDataSender.closeSocket = El remitente cerr\u00F3 conector a [{0}\:{1,number,integer}] (contador de cierre {2,number,integer})
+IDataSender.connect = El remitente se conect\u00F3 a [{0}\:{1,number,integer}] (contador de conexi\u00F3n {2,number,integer})
+IDataSender.create = Crear remitente [{0}\:{1,number,integer}]
+IDataSender.disconnect = El remitente se desconect\u00F3 de [{0}\:{1,number,integer}] (contador de desconexi\u00F3n {2,number,integer})
+IDataSender.message.disconnect = Mensaje transferido\: El remitente no se puede desconectar de [{0}\:{1,number,integer}]
+IDataSender.message.create = Mensaje transferido\: El remitente no puede crear conector actual [{0}\:{1,number,integer}]
+IDataSender.openSocket = El remitente abri\u00F3 conector con [{0}\:{1,number,integer}] (contador de apertura {2,number,integer})
+IDataSender.openSocket.failure = \u00A1Fallo al abrir conector de remitente [{0}\:{1,number,integer}]\! (contador de fallos de apertura {2,number,integer})
+IDataSender.send.again = Enviar datos de nuevo a [{0}\:{1,number,integer}]
+IDataSender.send.crash = Enviar mensaje destrozado [{0}\:{1,number,integer}] tipo\=[{2}], id\=[{3}]
+IDataSender.send.message = Enviar mensaje a [{0}\:{1,number,integer}] id\=[{2}] medida\={3,number,integer}
+IDataSender.send.lost = Mensaje perdido\: [{0}\:{1,number,integer}] tipo\=[{2}], id\=[{3}]
+IDataSender.senderModes.Configured = Configurado un remitente de r\u00E9plica de datos para modo {0}
+IDataSender.senderModes.Instantiate = No puedo instanciar un remitente de r\u00E9plica de datos de clase {0}
+IDataSender.senderModes.Missing = No puedo configurar un remitente de r\u00E9plica de datos para modo {0}
+IDataSender.senderModes.Resources = No puedo cargar lista de mapeo de remitente de r\u00E9plica de datos
+IDataSender.stats = Estado de env\u00EDo desde [{0}\:{1,number,integer}], Nr de bytes enviado\={2,number,integer} sobre {3} \= {4,number,integer} bytes/requerimiento, tiempo de proceso {5,number,integer} mseg, tiempo medio de proceso {6,number,integer} mseg
+PoolSocketSender.senderQueue.sender.failed = PoolSocketSender fall\u00F3 el crear nuevo remitente para [{0}\:{1,number,integer}]
+PoolSocketSender.noMoreSender = No hay remitente de conector disponible para cliente [{0}\:{1,number,integer}] \u00BFdesapareci\u00F3?
+ReplicationTransmitter.getProperty = obtener propiedad {0}
+ReplicationTransmitter.setProperty = poner propiedad {0}\: {1} viejo valor {2}
+ReplicationTransmitter.started = Iniciar ClusterSender en cl\u00FAster {0} con nombre {1}
+ReplicationTransmitter.stopped = Parado ClusterSender en cl\u00FAster {0} con nombre {1}
+ReplicationValve.crossContext.add = a\u00F1adir contenedor de r\u00E9plica de sesi\u00F3n de Contexto Cruzado a replicationValve threadlocal
+ReplicationValve.crossContext.registerSession = retistrar id de sesi\u00F3n de Contexto Cruzado\={0} desde contexto {1}
+ReplicationValve.crossContext.remove = quitar contenedor de r\u00E9plica de sesi\u00F3n de Contexto Cruzado a replicationValve threadlocal
+ReplicationValve.crossContext.sendDelta = enviar delta de sesi\u00F3n de Contexto Cruzado desde contexto {0}.
+ReplicationValve.filter.loading = Cargando filtros de requerimiento\={0}
+ReplicationValve.filter.failure = No puedo compilar filtror\={0}
+ReplicationValve.invoke.uri = Invocando requerimiento de r\u00E9plica en {0}
+ReplicationValve.nocluster = No cluster configured for this request.
+ReplicationValve.resetDeltaRequest = Cluster is standalone\: reset Session Request Delta at context {0}
+ReplicationValve.send.failure = Unable to perform replication request.
+ReplicationValve.send.invalid.failure = Unable to send session [id\={0}] invalid message over cluster.
+ReplicationValve.session.found = Context {0}\: Found session {1} but it isn't a ClusterSession.
+ReplicationValve.session.indicator = Context {0}\: Primarity of session {0} in request attribute {1} is {2}.
+ReplicationValve.session.invalid = Context {0}\: Requested session {1} is invalid, removed or not replicated at this node.
+ReplicationValve.stats = Average request time\= {0} ms for Cluster overhead time\={1} ms for {2} requests {3} filter requests {4} send requests {5} cross context requests (Request\={6} ms Cluster\={7} ms).
+SimpleTcpCluster.event.log = Cluster receive listener event {0} with data {1}
+SimpleTcpCluster.getProperty = get property {0}
+SimpleTcpCluster.setProperty = set property {0}\: {1} old value {2}
+SimpleTcpCluster.default.addClusterListener = Add Default ClusterListener at cluster {0}
+SimpleTcpCluster.default.addClusterValves = Add Default ClusterValves at cluster {0}
+SimpleTcpCluster.default.addClusterReceiver = Add Default ClusterReceiver at cluster {0}
+SimpleTcpCluster.default.addClusterSender = Add Default ClusterSender at cluster {0}
+SimpleTcpCluster.default.addMembershipService = Add Default Membership Service at cluster {0}
+SimpleTcpCluster.log.receive = RECEIVE {0,date}\:{0,time} {1,number} {2}\:{3,number,integer} {4} {5}
+SimpleTcpCluster.log.send = SEND {0,date}\:{0,time} {1,number} {2}\:{3,number,integer} {4} 
+SimpleTcpCluster.log.send.all = SEND {0,date}\:{0,time} {1,number} - {2}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/tcp/ReplicationValve.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/tcp/ReplicationValve.java
new file mode 100644
index 0000000..716b3ed
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/tcp/ReplicationValve.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.catalina.ha.tcp;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import javax.servlet.ServletException;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Manager;
+import org.apache.catalina.Session;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.ha.CatalinaCluster;
+import org.apache.catalina.ha.ClusterManager;
+import org.apache.catalina.ha.ClusterMessage;
+import org.apache.catalina.ha.ClusterSession;
+import org.apache.catalina.ha.ClusterValve;
+import org.apache.catalina.ha.session.DeltaManager;
+import org.apache.catalina.ha.session.DeltaSession;
+import org.apache.catalina.valves.ValveBase;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * <p>Implementation of a Valve that logs interesting contents from the
+ * specified Request (before processing) and the corresponding Response
+ * (after processing).  It is especially useful in debugging problems
+ * related to headers and cookies.</p>
+ *
+ * <p>This Valve may be attached to any Container, depending on the granularity
+ * of the logging you wish to perform.</p>
+ *
+ * <p>primaryIndicator=true, then the request attribute <i>org.apache.catalina.ha.tcp.isPrimarySession.</i>
+ * is set true, when request processing is at sessions primary node.
+ * </p>
+ *
+ * @author Craig R. McClanahan
+ * @author Filip Hanik
+ * @author Peter Rossbach
+ * @version $Id: ReplicationValve.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public class ReplicationValve
+    extends ValveBase implements ClusterValve {
+    
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog( ReplicationValve.class );
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * The descriptive information related to this implementation.
+     */
+    private static final String info =
+        "org.apache.catalina.ha.tcp.ReplicationValve/2.0";
+
+
+    /**
+     * The StringManager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    private CatalinaCluster cluster = null ;
+
+    /**
+     * Filter expression 
+     */
+    protected Pattern filter = null;
+    
+    /**
+     * crossContext session container 
+     */
+    protected ThreadLocal<ArrayList<DeltaSession>> crossContextSessions =
+        new ThreadLocal<ArrayList<DeltaSession>>() ;
+    
+    /**
+     * doProcessingStats (default = off)
+     */
+    protected boolean doProcessingStats = false;
+    
+    protected long totalRequestTime = 0;
+    protected long totalSendTime = 0;
+    protected long nrOfRequests = 0;
+    protected long lastSendTime = 0;
+    protected long nrOfFilterRequests = 0;
+    protected long nrOfSendRequests = 0;
+    protected long nrOfCrossContextSendRequests = 0;
+    
+    /**
+     * must primary change indicator set 
+     */
+    protected boolean primaryIndicator = false ;
+    
+    /**
+     * Name of primary change indicator as request attribute
+     */
+    protected String primaryIndicatorName = "org.apache.catalina.ha.tcp.isPrimarySession";
+   
+    // ------------------------------------------------------------- Properties
+
+    public ReplicationValve() {
+        super(false);
+    }
+    
+    /**
+     * Return descriptive information about this Valve implementation.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+    
+    /**
+     * @return Returns the cluster.
+     */
+    @Override
+    public CatalinaCluster getCluster() {
+        return cluster;
+    }
+    
+    /**
+     * @param cluster The cluster to set.
+     */
+    @Override
+    public void setCluster(CatalinaCluster cluster) {
+        this.cluster = cluster;
+    }
+ 
+    /**
+     * @return Returns the filter
+     */
+    public String getFilter() {
+       if (filter == null) {
+           return null;
+       }
+       return filter.toString();
+    }
+
+    /**
+     * compile filter string to regular expression
+     * @see Pattern#compile(java.lang.String)
+     * @param filter
+     *            The filter to set.
+     */
+    public void setFilter(String filter) {
+        if (log.isDebugEnabled())
+            log.debug(sm.getString("ReplicationValve.filter.loading", filter));
+        
+        if (filter == null || filter.length() == 0) {
+            this.filter = null;
+        } else {
+            try {
+                this.filter = Pattern.compile(filter);
+            } catch (PatternSyntaxException pse) {
+                log.error(sm.getString("ReplicationValve.filter.failure",
+                        filter), pse);
+            }
+        }
+    }
+
+    /**
+     * @return Returns the primaryIndicator.
+     */
+    public boolean isPrimaryIndicator() {
+        return primaryIndicator;
+    }
+
+    /**
+     * @param primaryIndicator The primaryIndicator to set.
+     */
+    public void setPrimaryIndicator(boolean primaryIndicator) {
+        this.primaryIndicator = primaryIndicator;
+    }
+    
+    /**
+     * @return Returns the primaryIndicatorName.
+     */
+    public String getPrimaryIndicatorName() {
+        return primaryIndicatorName;
+    }
+    
+    /**
+     * @param primaryIndicatorName The primaryIndicatorName to set.
+     */
+    public void setPrimaryIndicatorName(String primaryIndicatorName) {
+        this.primaryIndicatorName = primaryIndicatorName;
+    }
+    
+    /**
+     * Calc processing stats
+     */
+    public boolean doStatistics() {
+        return doProcessingStats;
+    }
+
+    /**
+     * Set Calc processing stats
+     * @see #resetStatistics()
+     */
+    public void setStatistics(boolean doProcessingStats) {
+        this.doProcessingStats = doProcessingStats;
+    }
+
+    /**
+     * @return Returns the lastSendTime.
+     */
+    public long getLastSendTime() {
+        return lastSendTime;
+    }
+    
+    /**
+     * @return Returns the nrOfRequests.
+     */
+    public long getNrOfRequests() {
+        return nrOfRequests;
+    }
+    
+    /**
+     * @return Returns the nrOfFilterRequests.
+     */
+    public long getNrOfFilterRequests() {
+        return nrOfFilterRequests;
+    }
+
+    /**
+     * @return Returns the nrOfCrossContextSendRequests.
+     */
+    public long getNrOfCrossContextSendRequests() {
+        return nrOfCrossContextSendRequests;
+    }
+
+    /**
+     * @return Returns the nrOfSendRequests.
+     */
+    public long getNrOfSendRequests() {
+        return nrOfSendRequests;
+    }
+
+    /**
+     * @return Returns the totalRequestTime.
+     */
+    public long getTotalRequestTime() {
+        return totalRequestTime;
+    }
+    
+    /**
+     * @return Returns the totalSendTime.
+     */
+    public long getTotalSendTime() {
+        return totalSendTime;
+    }
+
+    // --------------------------------------------------------- Public Methods
+    
+    /**
+     * Register all cross context sessions inside endAccess.
+     * Use a list with contains check, that the Portlet API can include a lot of fragments from same or
+     * different applications with session changes.
+     *
+     * @param session cross context session
+     */
+    public void registerReplicationSession(DeltaSession session) {
+        List<DeltaSession> sessions = crossContextSessions.get();
+        if(sessions != null) {
+            if(!sessions.contains(session)) {
+                if(log.isDebugEnabled())
+                    log.debug(sm.getString("ReplicationValve.crossContext.registerSession",
+                        session.getIdInternal(),
+                        session.getManager().getContainer().getName()));
+                sessions.add(session);
+            }
+        }
+    }
+
+    /**
+     * Log the interesting request parameters, invoke the next Valve in the
+     * sequence, and log the interesting response parameters.
+     *
+     * @param request The servlet request to be processed
+     * @param response The servlet response to be created
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    @Override
+    public void invoke(Request request, Response response)
+        throws IOException, ServletException
+    {
+        long totalstart = 0;
+
+        //this happens before the request
+        if(doStatistics()) {
+            totalstart = System.currentTimeMillis();
+        }
+        if (primaryIndicator) {
+            createPrimaryIndicator(request) ;
+        }
+        Context context = request.getContext();
+        boolean isCrossContext = context != null
+                && context instanceof StandardContext
+                && ((StandardContext) context).getCrossContext();
+        try {
+            if(isCrossContext) {
+                if(log.isDebugEnabled())
+                    log.debug(sm.getString("ReplicationValve.crossContext.add"));
+                //FIXME add Pool of Arraylists
+                crossContextSessions.set(new ArrayList<DeltaSession>());
+            }
+            getNext().invoke(request, response);
+            if(context != null) {
+                Manager manager = context.getManager();            
+                if (manager != null && manager instanceof ClusterManager) {
+                    ClusterManager clusterManager = (ClusterManager) manager;
+                    CatalinaCluster containerCluster = (CatalinaCluster) getContainer().getCluster();
+                    if (containerCluster == null) {
+                        if (log.isWarnEnabled())
+                            log.warn(sm.getString("ReplicationValve.nocluster"));
+                        return;
+                    }
+                    // valve cluster can access manager - other cluster handle replication 
+                    // at host level - hopefully!
+                    if(containerCluster.getManager(clusterManager.getName()) == null)
+                        return ;
+                    if(containerCluster.hasMembers()) {
+                        sendReplicationMessage(request, totalstart, isCrossContext, clusterManager, containerCluster);
+                    } else {
+                        resetReplicationRequest(request,isCrossContext);
+                    }        
+                }
+            }
+        } finally {
+            // Array must be remove: Current master request send endAccess at recycle. 
+            // Don't register this request session again!
+            if(isCrossContext) {
+                if(log.isDebugEnabled())
+                    log.debug(sm.getString("ReplicationValve.crossContext.remove"));
+                // crossContextSessions.remove() only exist at Java 5
+                // register ArrayList at a pool
+                crossContextSessions.set(null);
+            }
+        }
+    }
+
+    
+    /**
+     * reset the active statistics 
+     */
+    public void resetStatistics() {
+        totalRequestTime = 0 ;
+        totalSendTime = 0 ;
+        lastSendTime = 0 ;
+        nrOfFilterRequests = 0 ;
+        nrOfRequests = 0 ;
+        nrOfSendRequests = 0;
+        nrOfCrossContextSendRequests = 0;
+    }
+    
+
+    // --------------------------------------------------------- Protected Methods
+
+    /**
+     * @param request
+     * @param totalstart
+     * @param isCrossContext
+     * @param clusterManager
+     * @param containerCluster
+     */
+    protected void sendReplicationMessage(Request request, long totalstart, boolean isCrossContext, ClusterManager clusterManager, CatalinaCluster containerCluster) {
+        //this happens after the request
+        long start = 0;
+        if(doStatistics()) {
+            start = System.currentTimeMillis();
+        }
+        try {
+            // send invalid sessions
+            // DeltaManager returns String[0]
+            if (!(clusterManager instanceof DeltaManager))
+                sendInvalidSessions(clusterManager, containerCluster);
+            // send replication
+            sendSessionReplicationMessage(request, clusterManager, containerCluster);
+            if(isCrossContext)
+                sendCrossContextSession(containerCluster);
+        } catch (Exception x) {
+            // FIXME we have a lot of sends, but the trouble with one node stops the correct replication to other nodes!
+            log.error(sm.getString("ReplicationValve.send.failure"), x);
+        } finally {
+            // FIXME this stats update are not cheap!!
+            if(doStatistics()) {
+                updateStats(totalstart,start);
+            }
+        }
+    }
+
+    /**
+     * Send all changed cross context sessions to backups
+     * @param containerCluster
+     */
+    protected void sendCrossContextSession(CatalinaCluster containerCluster) {
+        List<DeltaSession> sessions = crossContextSessions.get();
+        if(sessions != null && sessions.size() >0) {
+            for(Iterator<DeltaSession> iter = sessions.iterator(); iter.hasNext() ;) {          
+                Session session = iter.next();
+                if(log.isDebugEnabled())
+                    log.debug(sm.getString("ReplicationValve.crossContext.sendDelta",  
+                            session.getManager().getContainer().getName() ));
+                sendMessage(session,(ClusterManager)session.getManager(),containerCluster);
+                if(doStatistics()) {
+                    nrOfCrossContextSendRequests++;
+                }
+            }
+        }
+    }
+  
+    /**
+     * Fix memory leak for long sessions with many changes, when no backup member exists!
+     * @param request current request after response is generated
+     * @param isCrossContext check crosscontext threadlocal
+     */
+    protected void resetReplicationRequest(Request request, boolean isCrossContext) {
+        Session contextSession = request.getSessionInternal(false);
+        if(contextSession instanceof DeltaSession){
+            resetDeltaRequest(contextSession);
+            ((DeltaSession)contextSession).setPrimarySession(true);
+        }
+        if(isCrossContext) {
+            List<DeltaSession> sessions = crossContextSessions.get();
+            if(sessions != null && sessions.size() >0) {
+                Iterator<DeltaSession> iter = sessions.iterator();
+                for(; iter.hasNext() ;) {          
+                    Session session = iter.next();
+                    resetDeltaRequest(session);
+                    if(session instanceof DeltaSession)
+                        ((DeltaSession)contextSession).setPrimarySession(true);
+
+                }
+            }
+        }                     
+    }
+
+    /**
+     * Reset DeltaRequest from session
+     * @param session HttpSession from current request or cross context session
+     */
+    protected void resetDeltaRequest(Session session) {
+        if(log.isDebugEnabled()) {
+            log.debug(sm.getString("ReplicationValve.resetDeltaRequest" , 
+                session.getManager().getContainer().getName() ));
+        }
+        ((DeltaSession)session).resetDeltaRequest();
+    }
+
+    /**
+     * Send Cluster Replication Request
+     * @param request current request
+     * @param manager session manager
+     * @param cluster replication cluster
+     */
+    protected void sendSessionReplicationMessage(Request request,
+            ClusterManager manager, CatalinaCluster cluster) {
+        Session session = request.getSessionInternal(false);
+        if (session != null) {
+            String uri = request.getDecodedRequestURI();
+            // request without session change
+            if (!isRequestWithoutSessionChange(uri)) {
+                if (log.isDebugEnabled())
+                    log.debug(sm.getString("ReplicationValve.invoke.uri", uri));
+                sendMessage(session,manager,cluster);
+            } else
+                if(doStatistics())
+                    nrOfFilterRequests++;
+        }
+
+    }
+
+   /**
+    * Send message delta message from request session 
+    * @param session current session
+    * @param manager session manager
+    * @param cluster replication cluster
+    */
+    protected void sendMessage(Session session,
+             ClusterManager manager, CatalinaCluster cluster) {
+        String id = session.getIdInternal();
+        if (id != null) {
+            send(manager, cluster, id);
+        }
+    }
+
+    /**
+     * send manager requestCompleted message to cluster
+     * @param manager SessionManager
+     * @param cluster replication cluster
+     * @param sessionId sessionid from the manager
+     * @see DeltaManager#requestCompleted(String)
+     * @see SimpleTcpCluster#send(ClusterMessage)
+     */
+    protected void send(ClusterManager manager, CatalinaCluster cluster, String sessionId) {
+        ClusterMessage msg = manager.requestCompleted(sessionId);
+        if (msg != null) {
+            cluster.send(msg);
+            if(doStatistics())
+                nrOfSendRequests++;
+        }
+    }
+    
+    /**
+     * check for session invalidations
+     * @param manager
+     * @param cluster
+     */
+    protected void sendInvalidSessions(ClusterManager manager, CatalinaCluster cluster) {
+        String[] invalidIds=manager.getInvalidatedSessions();
+        if ( invalidIds.length > 0 ) {
+            for ( int i=0;i<invalidIds.length; i++ ) {
+                try {
+                    send(manager,cluster,invalidIds[i]);
+                } catch ( Exception x ) {
+                    log.error(sm.getString("ReplicationValve.send.invalid.failure",invalidIds[i]),x);
+                }
+            }
+        }
+    }
+    
+    /**
+     * is request without possible session change
+     * @param uri The request uri
+     * @return True if no session change
+     */
+    protected boolean isRequestWithoutSessionChange(String uri) {
+        Pattern f = filter;
+        return f != null && f.matcher(uri).matches();
+    }
+
+    /**
+     * protocol cluster replications stats
+     * @param requestTime
+     * @param clusterTime
+     */
+    protected  void updateStats(long requestTime, long clusterTime) {
+        synchronized(this) {
+            lastSendTime=System.currentTimeMillis();
+            totalSendTime+=lastSendTime - clusterTime;
+            totalRequestTime+=lastSendTime - requestTime;
+            nrOfRequests++;
+        }
+        if(log.isInfoEnabled()) {
+            if ( (nrOfRequests % 100) == 0 ) {
+                 log.info(sm.getString("ReplicationValve.stats",
+                     new Object[]{
+                         Long.valueOf(totalRequestTime/nrOfRequests),
+                         Long.valueOf(totalSendTime/nrOfRequests),
+                         Long.valueOf(nrOfRequests),
+                         Long.valueOf(nrOfSendRequests),
+                         Long.valueOf(nrOfCrossContextSendRequests),
+                         Long.valueOf(nrOfFilterRequests),
+                         Long.valueOf(totalRequestTime),
+                         Long.valueOf(totalSendTime)}));
+             }
+        }
+    }
+
+
+    /**
+     * Mark Request that processed at primary node with attribute
+     * primaryIndicatorName
+     * 
+     * @param request
+     * @throws IOException
+     */
+    protected void createPrimaryIndicator(Request request) throws IOException {
+        String id = request.getRequestedSessionId();
+        if ((id != null) && (id.length() > 0)) {
+            Manager manager = request.getContext().getManager();
+            Session session = manager.findSession(id);
+            if (session instanceof ClusterSession) {
+                ClusterSession cses = (ClusterSession) session;
+                if (log.isDebugEnabled())
+                    log.debug(sm.getString(
+                            "ReplicationValve.session.indicator", request.getContext().getName(),id,
+                            primaryIndicatorName,
+                            Boolean.valueOf(cses.isPrimarySession())));
+                request.setAttribute(primaryIndicatorName, cses.isPrimarySession()?Boolean.TRUE:Boolean.FALSE);
+            } else {
+                if (log.isDebugEnabled()) {
+                    if (session != null) {
+                        log.debug(sm.getString(
+                                "ReplicationValve.session.found", request.getContext().getName(),id));
+                    } else {
+                        log.debug(sm.getString(
+                                "ReplicationValve.session.invalid", request.getContext().getName(),id));
+                    }
+                }
+            }
+        }
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/tcp/SendMessageData.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/tcp/SendMessageData.java
new file mode 100644
index 0000000..617c858
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/tcp/SendMessageData.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.tcp;
+
+import org.apache.catalina.tribes.Member;
+
+/**
+ * @author Peter Rossbach
+ * @version $Id: SendMessageData.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+public class SendMessageData {
+
+    private Object message ;
+    private Member destination ;
+    private Exception exception ;
+    
+    
+    /**
+     * @param message
+     * @param destination
+     * @param exception
+     */
+    public SendMessageData(Object message, Member destination,
+            Exception exception) {
+        super();
+        this.message = message;
+        this.destination = destination;
+        this.exception = exception;
+    }
+    
+    /**
+     * @return Returns the destination.
+     */
+    public Member getDestination() {
+        return destination;
+    }
+    /**
+     * @param destination The destination to set.
+     */
+    public void setDestination(Member destination) {
+        this.destination = destination;
+    }
+    /**
+     * @return Returns the exception.
+     */
+    public Exception getException() {
+        return exception;
+    }
+    /**
+     * @param exception The exception to set.
+     */
+    public void setException(Exception exception) {
+        this.exception = exception;
+    }
+    /**
+     * @return Returns the message.
+     */
+    public Object getMessage() {
+        return message;
+    }
+    /**
+     * @param message The message to set.
+     */
+    public void setMessage(Object message) {
+        this.message = message;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/tcp/SimpleTcpCluster.java b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/tcp/SimpleTcpCluster.java
new file mode 100644
index 0000000..f04a3a1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/tcp/SimpleTcpCluster.java
@@ -0,0 +1,949 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.ha.tcp;
+
+import java.beans.PropertyChangeSupport;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Host;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.Manager;
+import org.apache.catalina.Valve;
+import org.apache.catalina.ha.CatalinaCluster;
+import org.apache.catalina.ha.ClusterListener;
+import org.apache.catalina.ha.ClusterManager;
+import org.apache.catalina.ha.ClusterMessage;
+import org.apache.catalina.ha.ClusterValve;
+import org.apache.catalina.ha.jmx.ClusterJmxHelper;
+import org.apache.catalina.ha.session.ClusterSessionListener;
+import org.apache.catalina.ha.session.DeltaManager;
+import org.apache.catalina.ha.session.JvmRouteBinderValve;
+import org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener;
+import org.apache.catalina.ha.util.IDynamicProperty;
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.ChannelListener;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.MembershipListener;
+import org.apache.catalina.tribes.group.GroupChannel;
+import org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor;
+import org.apache.catalina.tribes.group.interceptors.TcpFailureDetector;
+import org.apache.catalina.util.LifecycleBase;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.IntrospectionUtils;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * A <b>Cluster </b> implementation using simple multicast. Responsible for
+ * setting up a cluster and provides callers with a valid multicast
+ * receiver/sender.
+ * 
+ * FIXME remove install/remove/start/stop context dummys
+ * FIXME wrote testcases 
+ * 
+ * @author Filip Hanik
+ * @author Remy Maucherat
+ * @author Peter Rossbach
+ * @version $Id: SimpleTcpCluster.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+public class SimpleTcpCluster extends LifecycleBase
+    implements CatalinaCluster, LifecycleListener, IDynamicProperty,
+               MembershipListener, ChannelListener{
+
+    public static final Log log = LogFactory.getLog(SimpleTcpCluster.class);
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * Descriptive information about this component implementation.
+     */
+    protected static final String info = "SimpleTcpCluster/2.2";
+
+    public static final String BEFORE_MEMBERREGISTER_EVENT = "before_member_register";
+
+    public static final String AFTER_MEMBERREGISTER_EVENT = "after_member_register";
+
+    public static final String BEFORE_MANAGERREGISTER_EVENT = "before_manager_register";
+
+    public static final String AFTER_MANAGERREGISTER_EVENT = "after_manager_register";
+
+    public static final String BEFORE_MANAGERUNREGISTER_EVENT = "before_manager_unregister";
+
+    public static final String AFTER_MANAGERUNREGISTER_EVENT = "after_manager_unregister";
+
+    public static final String BEFORE_MEMBERUNREGISTER_EVENT = "before_member_unregister";
+
+    public static final String AFTER_MEMBERUNREGISTER_EVENT = "after_member_unregister";
+
+    public static final String SEND_MESSAGE_FAILURE_EVENT = "send_message_failure";
+
+    public static final String RECEIVE_MESSAGE_FAILURE_EVENT = "receive_message_failure";
+    
+    /**
+     * Group channel.
+     */
+    protected Channel channel = new GroupChannel();
+
+
+    /**
+     * Name for logging purpose
+     */
+    protected String clusterImpName = "SimpleTcpCluster";
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm = StringManager.getManager(Constants.Package);
+
+    /**
+     * The cluster name to join
+     */
+    protected String clusterName ;
+
+    /**
+     * call Channel.heartbeat() at container background thread
+     * @see org.apache.catalina.tribes.group.GroupChannel#heartbeat()
+     */
+    protected boolean heartbeatBackgroundEnabled =false ;
+    
+    /**
+     * The Container associated with this Cluster.
+     */
+    protected Container container = null;
+
+    /**
+     * The property change support for this component.
+     */
+    protected PropertyChangeSupport support = new PropertyChangeSupport(this);
+
+    /**
+     * The context name <->manager association for distributed contexts.
+     */
+    protected Map<String, ClusterManager> managers =
+        new HashMap<String, ClusterManager>();
+
+    protected ClusterManager managerTemplate = new DeltaManager();
+
+    private List<Valve> valves = new ArrayList<Valve>();
+
+    private org.apache.catalina.ha.ClusterDeployer clusterDeployer;
+
+    /**
+     * Listeners of messages
+     */
+    protected List<ClusterListener> clusterListeners = new ArrayList<ClusterListener>();
+
+    /**
+     * Comment for <code>notifyLifecycleListenerOnFailure</code>
+     */
+    private boolean notifyLifecycleListenerOnFailure = false;
+
+    /**
+     * dynamic sender <code>properties</code>
+     */
+    private Map<String, Object> properties = new HashMap<String, Object>();
+    
+    private int channelSendOptions = Channel.SEND_OPTIONS_ASYNCHRONOUS;
+    
+    private int channelStartOptions = Channel.DEFAULT;
+
+    // ------------------------------------------------------------- Properties
+
+    public SimpleTcpCluster() {
+        // NO-OP
+    }
+
+    /**
+     * Return descriptive information about this Cluster implementation and the
+     * corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    @Override
+    public String getInfo() {
+        return (info);
+    }
+
+    /**
+     * Return heartbeat enable flag (default false)
+     * @return the heartbeatBackgroundEnabled
+     */
+    public boolean isHeartbeatBackgroundEnabled() {
+        return heartbeatBackgroundEnabled;
+    }
+
+    /**
+     * enabled that container backgroundThread call heartbeat at channel
+     * @param heartbeatBackgroundEnabled the heartbeatBackgroundEnabled to set
+     */
+    public void setHeartbeatBackgroundEnabled(boolean heartbeatBackgroundEnabled) {
+        this.heartbeatBackgroundEnabled = heartbeatBackgroundEnabled;
+    }
+
+    /**
+     * Set the name of the cluster to join, if no cluster with this name is
+     * present create one.
+     * 
+     * @param clusterName
+     *            The clustername to join
+     */
+    @Override
+    public void setClusterName(String clusterName) {
+        this.clusterName = clusterName;
+    }
+
+    /**
+     * Return the name of the cluster that this Server is currently configured
+     * to operate within.
+     * 
+     * @return The name of the cluster associated with this server
+     */
+    @Override
+    public String getClusterName() {
+        if(clusterName == null && container != null)
+            return container.getName() ;
+        return clusterName;
+    }
+
+    /**
+     * Set the Container associated with our Cluster
+     * 
+     * @param container
+     *            The Container to use
+     */
+    @Override
+    public void setContainer(Container container) {
+        Container oldContainer = this.container;
+        this.container = container;
+        support.firePropertyChange("container", oldContainer, this.container);
+    }
+
+    /**
+     * Get the Container associated with our Cluster
+     * 
+     * @return The Container associated with our Cluster
+     */
+    @Override
+    public Container getContainer() {
+        return (this.container);
+    }
+
+    /**
+     * @return Returns the notifyLifecycleListenerOnFailure.
+     */
+    public boolean isNotifyLifecycleListenerOnFailure() {
+        return notifyLifecycleListenerOnFailure;
+    }
+
+    /**
+     * @param notifyListenerOnFailure
+     *            The notifyLifecycleListenerOnFailure to set.
+     */
+    public void setNotifyLifecycleListenerOnFailure(
+            boolean notifyListenerOnFailure) {
+        boolean oldNotifyListenerOnFailure = this.notifyLifecycleListenerOnFailure;
+        this.notifyLifecycleListenerOnFailure = notifyListenerOnFailure;
+        support.firePropertyChange("notifyLifecycleListenerOnFailure",
+                oldNotifyListenerOnFailure,
+                this.notifyLifecycleListenerOnFailure);
+    }
+
+    /**
+     * Add cluster valve 
+     * Cluster Valves are only add to container when cluster is started!
+     * @param valve The new cluster Valve.
+     */
+    @Override
+    public void addValve(Valve valve) {
+        if (valve instanceof ClusterValve && (!valves.contains(valve)))
+            valves.add(valve);
+    }
+
+    /**
+     * get all cluster valves
+     * @return current cluster valves
+     */
+    @Override
+    public Valve[] getValves() {
+        return valves.toArray(new Valve[valves.size()]);
+    }
+
+    /**
+     * Get the cluster listeners associated with this cluster. If this Array has
+     * no listeners registered, a zero-length array is returned.
+     */
+    public ClusterListener[] findClusterListeners() {
+        if (clusterListeners.size() > 0) {
+            ClusterListener[] listener = new ClusterListener[clusterListeners.size()];
+            clusterListeners.toArray(listener);
+            return listener;
+        } else
+            return new ClusterListener[0];
+
+    }
+
+    /**
+     * Add cluster message listener and register cluster to this listener.
+     * 
+     * @see org.apache.catalina.ha.CatalinaCluster#addClusterListener(org.apache.catalina.ha.ClusterListener)
+     */
+    @Override
+    public void addClusterListener(ClusterListener listener) {
+        if (listener != null && !clusterListeners.contains(listener)) {
+            clusterListeners.add(listener);
+            listener.setCluster(this);
+        }
+    }
+
+    /**
+     * Remove message listener and deregister Cluster from listener.
+     * 
+     * @see org.apache.catalina.ha.CatalinaCluster#removeClusterListener(org.apache.catalina.ha.ClusterListener)
+     */
+    @Override
+    public void removeClusterListener(ClusterListener listener) {
+        if (listener != null) {
+            clusterListeners.remove(listener);
+            listener.setCluster(null);
+        }
+    }
+
+    /**
+     * get current Deployer
+     */
+    @Override
+    public org.apache.catalina.ha.ClusterDeployer getClusterDeployer() {
+        return clusterDeployer;
+    }
+
+    /**
+     * set a new Deployer, must be set before cluster started!
+     */
+    @Override
+    public void setClusterDeployer(
+            org.apache.catalina.ha.ClusterDeployer clusterDeployer) {
+        this.clusterDeployer = clusterDeployer;
+    }
+
+    @Override
+    public void setChannel(Channel channel) {
+        this.channel = channel;
+    }
+
+    public void setManagerTemplate(ClusterManager managerTemplate) {
+        this.managerTemplate = managerTemplate;
+    }
+
+    public void setChannelSendOptions(int channelSendOptions) {
+        this.channelSendOptions = channelSendOptions;
+    }
+
+    /**
+     * has members
+     */
+    protected boolean hasMembers = false;
+    @Override
+    public boolean hasMembers() {
+        return hasMembers;
+    }
+    
+    /**
+     * Get all current cluster members
+     * @return all members or empty array 
+     */
+    @Override
+    public Member[] getMembers() {
+        return channel.getMembers();
+    }
+
+    /**
+     * Return the member that represents this node.
+     * 
+     * @return Member
+     */
+    @Override
+    public Member getLocalMember() {
+        return channel.getLocalMember(true);
+    }
+
+    // ------------------------------------------------------------- dynamic
+    // manager property handling
+
+    /**
+     * JMX hack to direct use at jconsole
+     * 
+     * @param name
+     * @param value
+     */
+    public boolean setProperty(String name, String value) {
+        return setProperty(name, (Object) value);
+    }
+
+    /**
+     * set config attributes with reflect and propagate to all managers
+     * 
+     * @param name
+     * @param value
+     */
+    @Override
+    public boolean setProperty(String name, Object value) {
+        if (log.isTraceEnabled())
+            log.trace(sm.getString("SimpleTcpCluster.setProperty", name, value,properties.get(name)));
+        properties.put(name, value);
+        //using a dynamic way of setting properties is nice, but a security risk
+        //if exposed through JMX. This way you can sit and try to guess property names,
+        //we will only allow explicit property names
+        log.warn("Dynamic setProperty("+name+",value) has been disabled, please use explicit properties for the element you are trying to identify");
+        return false;
+    }
+
+    /**
+     * get current config
+     * 
+     * @param key
+     * @return The property
+     */
+    @Override
+    public Object getProperty(String key) {
+        if (log.isTraceEnabled())
+            log.trace(sm.getString("SimpleTcpCluster.getProperty", key));
+        return properties.get(key);
+    }
+
+    /**
+     * Get all properties keys
+     * 
+     * @return An iterator over the property names.
+     */
+    @Override
+    public Iterator<String> getPropertyNames() {
+        return properties.keySet().iterator();
+    }
+
+    /**
+     * remove a configured property.
+     * 
+     * @param key
+     */
+    @Override
+    public void removeProperty(String key) {
+        properties.remove(key);
+    }
+
+    /**
+     * transfer properties from cluster configuration to subelement bean.
+     * @param prefix
+     * @param bean
+     */
+    protected void transferProperty(String prefix, Object bean) {
+        if (prefix != null) {
+            for (Iterator<String> iter = getPropertyNames(); iter.hasNext();) {
+                String pkey = iter.next();
+                if (pkey.startsWith(prefix)) {
+                    String key = pkey.substring(prefix.length() + 1);
+                    Object value = getProperty(pkey);
+                    IntrospectionUtils.setProperty(bean, key, value.toString());
+                }
+            }
+        }
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * @return Returns the managers.
+     */
+    @Override
+    public Map<String, ClusterManager> getManagers() {
+        return managers;
+    }
+
+    @Override
+    public Channel getChannel() {
+        return channel;
+    }
+
+    public ClusterManager getManagerTemplate() {
+        return managerTemplate;
+    }
+
+    public int getChannelSendOptions() {
+        return channelSendOptions;
+    }
+
+    /**
+     * Create new Manager without add to cluster (comes with start the manager)
+     * 
+     * @param name
+     *            Context Name of this manager
+     * @see org.apache.catalina.Cluster#createManager(java.lang.String)
+     * @see DeltaManager#start()
+     */
+    @Override
+    public synchronized Manager createManager(String name) {
+        if (log.isDebugEnabled()) {
+            log.debug("Creating ClusterManager for context " + name +
+                    " using class " + getManagerTemplate().getClass().getName());
+        }
+        Manager manager = null;
+        try {
+            manager = managerTemplate.cloneFromTemplate();
+            ((ClusterManager)manager).setName(name);
+        } catch (Exception x) {
+            log.error("Unable to clone cluster manager, defaulting to org.apache.catalina.ha.session.DeltaManager", x);
+            manager = new org.apache.catalina.ha.session.DeltaManager();
+        } finally {
+            if ( manager != null && (manager instanceof ClusterManager)) ((ClusterManager)manager).setCluster(this);
+        }
+        return manager;
+    }
+    
+    @Override
+    public void registerManager(Manager manager) {
+    
+        if (! (manager instanceof ClusterManager)) {
+            log.warn("Manager [ " + manager + "] does not implement ClusterManager, addition to cluster has been aborted.");
+            return;
+        }
+        ClusterManager cmanager = (ClusterManager) manager ;
+        cmanager.setDistributable(true);
+        // Notify our interested LifecycleListeners
+        fireLifecycleEvent(BEFORE_MANAGERREGISTER_EVENT, manager);
+        String clusterName = getManagerName(cmanager.getName(), manager);
+        cmanager.setName(clusterName);
+        cmanager.setCluster(this);
+    
+        managers.put(clusterName, cmanager);
+        // Notify our interested LifecycleListeners
+        fireLifecycleEvent(AFTER_MANAGERREGISTER_EVENT, manager);    
+    }
+
+    /**
+     * Remove an application from cluster replication bus.
+     * 
+     * @see org.apache.catalina.Cluster#removeManager(Manager)
+     */
+    @Override
+    public void removeManager(Manager manager) {
+        if (manager != null && manager instanceof ClusterManager ) {
+            ClusterManager cmgr = (ClusterManager) manager;
+            // Notify our interested LifecycleListeners
+            fireLifecycleEvent(BEFORE_MANAGERUNREGISTER_EVENT,manager);
+            managers.remove(getManagerName(cmgr.getName(),manager));
+            cmgr.setCluster(null);
+            // Notify our interested LifecycleListeners
+            fireLifecycleEvent(AFTER_MANAGERUNREGISTER_EVENT, manager);
+        }
+    }
+
+    /**
+     * @param name
+     * @param manager
+     * @return TODO
+     */
+    @Override
+    public String getManagerName(String name, Manager manager) {
+        String clusterName = name ;
+        if ( clusterName == null ) clusterName = manager.getContainer().getName();
+        if(getContainer() instanceof Engine) {
+            Container context = manager.getContainer() ;
+            if(context != null && context instanceof Context) {
+                Container host = ((Context)context).getParent();
+                if(host != null && host instanceof Host && clusterName!=null && 
+                        !(clusterName.startsWith(host.getName() +"#"))) {
+                    clusterName = host.getName() +"#" + clusterName ;
+                }
+            }
+        }
+        return clusterName;
+    }
+
+    /*
+     * Get Manager
+     * 
+     * @see org.apache.catalina.ha.CatalinaCluster#getManager(java.lang.String)
+     */
+    @Override
+    public Manager getManager(String name) {
+        return managers.get(name);
+    }
+    
+    // ------------------------------------------------------ Lifecycle Methods
+
+    /**
+     * 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.
+     * @see org.apache.catalina.ha.deploy.FarmWarDeployer#backgroundProcess()
+     * @see org.apache.catalina.tribes.group.GroupChannel#heartbeat()
+     * @see org.apache.catalina.tribes.group.GroupChannel.HeartbeatThread#run()
+     * 
+     */
+    @Override
+    public void backgroundProcess() {
+        if (clusterDeployer != null) clusterDeployer.backgroundProcess();
+       
+        //send a heartbeat through the channel        
+        if ( isHeartbeatBackgroundEnabled() && channel !=null ) channel.heartbeat();
+    }
+
+
+    /**
+     * Use as base to handle start/stop/periodic Events from host. Currently
+     * only log the messages as trace level.
+     * 
+     * @see org.apache.catalina.LifecycleListener#lifecycleEvent(org.apache.catalina.LifecycleEvent)
+     */
+    @Override
+    public void lifecycleEvent(LifecycleEvent lifecycleEvent) {
+        if (log.isTraceEnabled())
+            log.trace(sm.getString("SimpleTcpCluster.event.log", lifecycleEvent.getType(), lifecycleEvent.getData()));
+    }
+
+    // ------------------------------------------------------ public
+
+    @Override
+    protected void initInternal() {
+        // NOOP
+    }
+    
+    
+    /**
+     * Start Cluster and implement the requirements
+     * of {@link 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.isInfoEnabled()) log.info("Cluster is about to start");
+
+        try {
+            checkDefaults();
+            registerClusterValve();
+            channel.addMembershipListener(this);
+            channel.addChannelListener(this);
+            channel.start(channelStartOptions);
+            if (clusterDeployer != null) clusterDeployer.start();
+            //register JMX objects
+            ClusterJmxHelper.registerDefaultCluster(this);
+            // Notify our interested LifecycleListeners
+        } catch (Exception x) {
+            log.error("Unable to start cluster.", x);
+            throw new LifecycleException(x);
+        }
+        
+        setState(LifecycleState.STARTING);
+    }
+
+    protected void checkDefaults() {
+        if ( clusterListeners.size() == 0 ) {
+            addClusterListener(new JvmRouteSessionIDBinderListener()); 
+            addClusterListener(new ClusterSessionListener());
+        }
+        if ( valves.size() == 0 ) {
+            addValve(new JvmRouteBinderValve());
+            addValve(new ReplicationValve());
+        }
+        if ( clusterDeployer != null ) clusterDeployer.setCluster(this);
+        if ( channel == null ) channel = new GroupChannel();
+        if ( channel instanceof GroupChannel && !((GroupChannel)channel).getInterceptors().hasNext()) {
+            channel.addInterceptor(new MessageDispatch15Interceptor());
+            channel.addInterceptor(new TcpFailureDetector());
+        }
+    }
+
+    /**
+     * register all cluster valve to host or engine
+     * @throws Exception
+     * @throws ClassNotFoundException
+     */
+    protected void registerClusterValve() throws Exception {
+        if(container != null ) {
+            for (Iterator<Valve> iter = valves.iterator(); iter.hasNext();) {
+                ClusterValve valve = (ClusterValve) iter.next();
+                if (log.isDebugEnabled())
+                    log.debug("Invoking addValve on " + getContainer()
+                            + " with class=" + valve.getClass().getName());
+                if (valve != null) {
+                    IntrospectionUtils.callMethodN(getContainer(), "addValve",
+                            new Object[] { valve },
+                            new Class[] { org.apache.catalina.Valve.class });
+
+                    valve.setCluster(this);
+                }
+            }
+        }
+    }
+
+    /**
+     * unregister all cluster valve to host or engine
+     * @throws Exception
+     * @throws ClassNotFoundException
+     */
+    protected void unregisterClusterValve() throws Exception {
+        for (Iterator<Valve> iter = valves.iterator(); iter.hasNext();) {
+            ClusterValve valve = (ClusterValve) iter.next();
+            if (log.isDebugEnabled())
+                log.debug("Invoking removeValve on " + getContainer()
+                        + " with class=" + valve.getClass().getName());
+            if (valve != null) {
+                IntrospectionUtils.callMethodN(getContainer(), "removeValve",
+                    new Object[] { valve },
+                    new Class[] { org.apache.catalina.Valve.class });
+                valve.setCluster(this);
+            }
+        }
+    }
+
+    
+    /**
+     * Stop Cluster and implement the requirements
+     * of {@link LifecycleBase#startInternal()}.
+     *
+     * @exception LifecycleException if this component detects a fatal error
+     *  that prevents this component from being used
+     */
+    @Override
+    protected void stopInternal() throws LifecycleException {
+
+        setState(LifecycleState.STOPPING);
+
+        if (clusterDeployer != null) clusterDeployer.stop();
+        this.managers.clear();
+        try {
+            if ( clusterDeployer != null ) clusterDeployer.setCluster(null);
+            channel.stop(channelStartOptions);
+            channel.removeChannelListener(this);
+            channel.removeMembershipListener(this);
+            this.unregisterClusterValve();
+            //unregister JMX objects
+            ClusterJmxHelper.unregisterDefaultCluster(this);
+
+        } catch (Exception x) {
+            log.error("Unable to stop cluster valve.", x);
+        }
+    }
+
+    
+    @Override
+    protected void destroyInternal() {
+        // NOOP
+    }
+
+    
+    /**
+     * Return a String rendering of this object.
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(this.getClass().getName());
+        sb.append('[');
+        if (container == null) {
+            sb.append("Container is null");
+        } else {
+            sb.append(container.getName());
+        }
+        sb.append(']');
+        return sb.toString();
+    }
+    
+
+    /**
+     * send message to all cluster members
+     * @param msg message to transfer
+     * 
+     * @see org.apache.catalina.ha.CatalinaCluster#send(org.apache.catalina.ha.ClusterMessage)
+     */
+    @Override
+    public void send(ClusterMessage msg) {
+        send(msg, null);
+    }
+    
+    /**
+     * send a cluster message to one member
+     * 
+     * @param msg message to transfer
+     * @param dest Receiver member
+     * @see org.apache.catalina.ha.CatalinaCluster#send(org.apache.catalina.ha.ClusterMessage,
+     *      org.apache.catalina.tribes.Member)
+     */
+    @Override
+    public void send(ClusterMessage msg, Member dest) {
+        try {
+            msg.setAddress(getLocalMember());
+            if (dest != null) {
+                if (!getLocalMember().equals(dest)) {
+                    channel.send(new Member[] {dest}, msg,channelSendOptions);
+                } else
+                    log.error("Unable to send message to local member " + msg);
+            } else {
+                Member[] destmembers = channel.getMembers();
+                if (destmembers.length>0)
+                    channel.send(destmembers,msg,channelSendOptions);
+                else if (log.isDebugEnabled()) 
+                    log.debug("No members in cluster, ignoring message:"+msg);
+            }
+        } catch (Exception x) {
+            log.error("Unable to send message through cluster sender.", x);
+        }
+    }
+
+    /**
+     * New cluster member is registered
+     * 
+     * @see org.apache.catalina.tribes.MembershipListener#memberAdded(org.apache.catalina.tribes.Member)
+     */
+    @Override
+    public void memberAdded(Member member) {
+        try {
+            hasMembers = channel.hasMembers();
+            if (log.isInfoEnabled()) log.info("Replication member added:" + member);
+            // Notify our interested LifecycleListeners
+            fireLifecycleEvent(BEFORE_MEMBERREGISTER_EVENT, member);
+            // Notify our interested LifecycleListeners
+            fireLifecycleEvent(AFTER_MEMBERREGISTER_EVENT, member);
+        } catch (Exception x) {
+            log.error("Unable to connect to replication system.", x);
+        }
+
+    }
+
+    /**
+     * Cluster member is gone
+     * 
+     * @see org.apache.catalina.tribes.MembershipListener#memberDisappeared(org.apache.catalina.tribes.Member)
+     */
+    @Override
+    public void memberDisappeared(Member member) {
+        try {
+            hasMembers = channel.hasMembers();            
+            if (log.isInfoEnabled()) log.info("Received member disappeared:" + member);
+            // Notify our interested LifecycleListeners
+            fireLifecycleEvent(BEFORE_MEMBERUNREGISTER_EVENT, member);
+            // Notify our interested LifecycleListeners
+            fireLifecycleEvent(AFTER_MEMBERUNREGISTER_EVENT, member);
+        } catch (Exception x) {
+            log.error("Unable remove cluster node from replication system.", x);
+        }
+    }
+
+    // --------------------------------------------------------- receiver
+    // messages
+
+    /**
+     * notify all listeners from receiving a new message is not ClusterMessage
+     * emit Failure Event to LifecylceListener
+     * 
+     * @param msg
+     *            received Message
+     */
+    @Override
+    public boolean accept(Serializable msg, Member sender) {
+        return (msg instanceof ClusterMessage);
+    }
+    
+    
+    @Override
+    public void messageReceived(Serializable message, Member sender) {
+        ClusterMessage fwd = (ClusterMessage)message;
+        fwd.setAddress(sender);
+        messageReceived(fwd);
+    }
+
+    public void messageReceived(ClusterMessage message) {
+
+        if (log.isDebugEnabled() && message != null)
+            log.debug("Assuming clocks are synched: Replication for "
+                    + message.getUniqueId() + " took="
+                    + (System.currentTimeMillis() - (message).getTimestamp())
+                    + " ms.");
+
+        //invoke all the listeners
+        boolean accepted = false;
+        if (message != null) {
+            for (Iterator<ClusterListener> iter = clusterListeners.iterator();
+                    iter.hasNext();) {
+                ClusterListener listener = iter.next();
+                if (listener.accept(message)) {
+                    accepted = true;
+                    listener.messageReceived(message);
+                }
+            }
+            if (!accepted && notifyLifecycleListenerOnFailure) {
+                Member dest = message.getAddress();
+                // Notify our interested LifecycleListeners
+                fireLifecycleEvent(RECEIVE_MESSAGE_FAILURE_EVENT,
+                        new SendMessageData(message, dest, null));
+                if (log.isDebugEnabled()) {
+                    log.debug("Message " + message.toString() + " from type "
+                            + message.getClass().getName()
+                            + " transfered but no listener registered");
+                }
+            }
+        }
+        return;
+    }
+
+    // --------------------------------------------------------- Logger
+
+    @Override
+    public Log getLogger() {
+        return log;
+    }
+
+
+    // ------------------------------------------------------------- deprecated
+
+    /**
+     * 
+     * @see org.apache.catalina.Cluster#setProtocol(java.lang.String)
+     */
+    @Override
+    public void setProtocol(String protocol) {
+        // NO-OP
+    }
+
+    /**
+     * @see org.apache.catalina.Cluster#getProtocol()
+     */
+    @Override
+    public String getProtocol() {
+        return null;
+    }
+
+    public int getChannelStartOptions() {
+        return channelStartOptions;
+    }
+
+    public void setChannelStartOptions(int channelStartOptions) {
+        this.channelStartOptions = channelStartOptions;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/ha/tcp/mbeans-descriptors.xml b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/tcp/mbeans-descriptors.xml
new file mode 100644
index 0000000..296c964
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/ha/tcp/mbeans-descriptors.xml
@@ -0,0 +1,174 @@
+<?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="SimpleTcpCluster"
+    description="Tcp Cluster implementation"
+    domain="Catalina"
+    group="Cluster"
+    type="org.apache.catalina.ha.tcp.SimpleTcpCluster">
+    <attribute
+      name="channelSendOptions"
+      description="This sets channel behaviour on sent messages."
+      type="int"/>
+    <attribute
+      name="channelStartOptions"
+      description="This sets channel start behaviour."
+      type="java.lang.String"/>
+    <attribute
+      name="clusterName"
+      description="name of cluster"
+      type="java.lang.String"/>
+    <attribute
+      name="heartbeatBackgroundEnabled"
+      description="enable that container background thread call channel heartbeat, default is that channel mangage heartbeat itself."
+      is="true"
+      type="boolean"/>
+    <attribute
+      name="info"
+      description="Class version info"
+      type="java.lang.String"
+      writeable="false"/>
+    <attribute
+      name="notifyLifecycleListenerOnFailure"
+      description="notify lifecycleListener from message transfer failure"
+      is="true"
+      type="boolean"/>
+    <attribute
+      name="stateName"
+      description="The name of the LifecycleState that this component is currently in"
+      type="java.lang.String"
+      writeable="false"/>
+    <operation
+      name="setProperty"
+      description="set a property to all cluster managers (with prefix 'manager.')"
+      impact="ACTION"
+      returnType="void">
+      <parameter
+        name="key"
+        description="Property name"
+        type="java.lang.String"/>
+      <parameter
+        name="value"
+        description="Property value"
+        type="java.lang.String"/>
+    </operation>
+    <operation
+      name="send"
+      description="send message to all cluster members"
+      impact="ACTION"
+      returnType="void">
+      <parameter
+        name="message"
+        description="replication message"
+        type="org.apache.catalina.ha.ClusterMessage"/>
+    </operation>
+    <operation
+      name="start"
+      description="Start the cluster"
+      impact="ACTION"
+      returnType="void"/>
+    <operation
+      name="stop"
+      description="Stop the cluster"
+      impact="ACTION"
+      returnType="void"/>
+  </mbean>
+  <mbean
+    name="ReplicationValve"
+    description="Valve for simple tcp replication"
+    domain="Catalina"
+    group="Valve"
+    type="org.apache.catalina.ha.tcp.ReplicationValve">
+    <attribute
+      name="asyncSupported"
+      description="Does this valve support async reporting?"
+      type="boolean"/>
+    <attribute
+      name="doProcessingStats"
+      is="true"
+      description="active statistics counting"
+      type="boolean"/>
+    <attribute
+      name="filter"
+      description="resource filter to disable session replication check"
+      type="java.lang.String"/>
+    <attribute
+      name="info"
+      description="Class version info"
+      type="java.lang.String"
+      writeable="false"/>
+    <attribute
+      name="lastSendTime"
+      description="last replicated request time"
+      type="long"
+      writeable="false"/>
+    <attribute
+      name="nrOfCrossContextSendRequests"
+      description="number of send cross context session requests"
+      type="long"
+      writeable="false"/>
+    <attribute
+      name="nrOfFilterRequests"
+      description="number of filtered requests"
+      type="long"
+      writeable="false"/>
+    <attribute
+      name="nrOfSendRequests"
+      description="number of send requests"
+      type="long"
+      writeable="false"/>
+    <attribute
+      name="nrOfRequests"
+      description="number of replicated requests"
+      type="long"
+      writeable="false"/>
+    <attribute
+      name="primaryIndicator"
+      is="true"
+      description="set indicator that request processing is at primary session node"
+      type="boolean"/>
+    <attribute
+      name="primaryIndicatorName"
+      description="Request attribute name to indicate that request processing is at primary session node"
+      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="totalSendTime"
+      description="total replicated send time"
+      type="long"
+      writeable="false"/>
+    <attribute
+      name="totalRequestTime"
+      description="total replicated request time"
+      type="long"
+      writeable="false"/>
+    <operation
+      name="resetStatistics"
+      description="Reset all statistics"
+      impact="ACTION"
+      returnType="void"/>
+  </mbean>
+</mbeans-descriptors>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/manager/host/Constants.java b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/host/Constants.java
new file mode 100644
index 0000000..92d76bd
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/host/Constants.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.manager.host;
+
+
+public class Constants {
+
+    public static final String Package = "org.apache.catalina.manager.host";
+
+    public static final String 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";
+
+    public static final String 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";
+
+    public static final String 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";
+
+    public static final String 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";
+
+    public static final String 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";
+
+    public static final String 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";
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/manager/host/HTMLHostManagerServlet.java b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/host/HTMLHostManagerServlet.java
new file mode 100644
index 0000000..2c77f00
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/host/HTMLHostManagerServlet.java
@@ -0,0 +1,540 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.host;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.URLEncoder;
+import java.text.MessageFormat;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Host;
+import org.apache.catalina.util.RequestUtil;
+import org.apache.catalina.util.ServerInfo;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+* Servlet that enables remote management of the virtual hosts deployed
+* on the server.  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>HostManagerServlet</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>HostManagerServlet</code> you won't be able to upgrade
+* to this Servlet since the output are not in the
+* same format as from <code>HostManagerServlet</code>
+*
+* @author Bip Thelin
+* @author Malcolm Edgar
+* @author Glenn L. Nielsen
+* @author Peter Rossbach
+* @version $Id: HTMLHostManagerServlet.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+* @see org.apache.catalina.manager.ManagerServlet
+*/
+
+public final class HTMLHostManagerServlet extends HostManagerServlet {
+
+    private static final long serialVersionUID = 1L;
+
+    // --------------------------------------------------------- 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
+        String command = request.getPathInfo();
+
+        // 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) {
+            // No command == list
+        } else if (command.equals("/list")) {
+            // Nothing to do - always generate list
+        } else if (command.equals("/add") || command.equals("/remove") ||
+                command.equals("/start") || command.equals("/stop")) {
+            message = smClient.getString(
+                    "hostManagerServlet.postCommand", command);
+        } else {
+            message = smClient.getString(
+                    "hostManagerServlet.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 ServletException, IOException {
+
+        StringManager smClient = getStringManager(request);
+
+        // Identify the request parameters that we need
+        String command = request.getPathInfo();
+
+        String name = request.getParameter("name");
+ 
+        // 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) {
+            // No command == list
+        } else if (command.equals("/add")) {
+            message = add(request, name, smClient);
+        } else if (command.equals("/remove")) {
+            message = remove(name, smClient);
+        } else if (command.equals("/start")) {
+            message = start(name, smClient);
+        } else if (command.equals("/stop")) {
+            message = stop(name, smClient);
+        } else {
+            //Try GET
+            doGet(request, response);
+        }
+
+        list(request, response, message, smClient);
+    }
+
+
+    /**
+     * Add a host using the specified parameters.
+     *
+     * @param name host name
+     */
+    protected String add(HttpServletRequest request,String name,
+            StringManager smClient) {
+
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        super.add(request,printWriter,name,true, smClient);
+
+        return stringWriter.toString();
+    }
+
+
+    /**
+     * Remove the specified host.
+     *
+     * @param name host name
+     */
+    protected String remove(String name, StringManager smClient) {
+
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        super.remove(printWriter, name, smClient);
+
+        return stringWriter.toString();
+    }
+
+    
+    /**
+     * Start the host with the specified name.
+     *
+     * @param name Host name
+     */
+    protected String start(String name, StringManager smClient) {
+
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        super.start(printWriter, name, smClient);
+
+        return stringWriter.toString();
+    }
+
+    
+    /**
+     * Stop the host with the specified name.
+     *
+     * @param name Host name
+     */
+    protected String stop(String name, StringManager smClient) {
+
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        super.stop(printWriter, name, 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
+     */
+    public void list(HttpServletRequest request,
+                     HttpServletResponse response,
+                     String message,
+                     StringManager smClient) throws IOException {
+
+        if (debug >= 1) {
+            log(sm.getString("hostManagerServlet.list", engine.getName()));
+        }
+
+        PrintWriter writer = response.getWriter();
+
+        // HTML Header Section
+        writer.print(org.apache.catalina.manager.Constants.HTML_HEADER_SECTION);
+
+        // Body Header Section
+        Object[] args = new Object[2];
+        args[0] = request.getContextPath();
+        args[1] = smClient.getString("htmlHostManagerServlet.title");
+        writer.print(MessageFormat.format
+                     (Constants.BODY_HEADER_SECTION, args));
+
+        // Message Section
+        args = new Object[3];
+        args[0] = smClient.getString("htmlHostManagerServlet.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("htmlHostManagerServlet.manager");
+        args[1] = response.encodeURL(request.getContextPath() + "/html/list");
+        args[2] = smClient.getString("htmlHostManagerServlet.list");
+        args[3] = response.encodeURL
+            (request.getContextPath() + "/" +
+             smClient.getString("htmlHostManagerServlet.helpHtmlManagerFile"));
+        args[4] = smClient.getString("htmlHostManagerServlet.helpHtmlManager");
+        args[5] = response.encodeURL
+            (request.getContextPath() + "/" +
+             smClient.getString("htmlHostManagerServlet.helpManagerFile"));
+        args[6] = smClient.getString("htmlHostManagerServlet.helpManager");
+        args[7] = response.encodeURL("/manager/status");
+        args[8] = smClient.getString("statusServlet.title");
+        writer.print(MessageFormat.format(Constants.MANAGER_SECTION, args));
+
+         // Hosts Header Section
+        args = new Object[3];
+        args[0] = smClient.getString("htmlHostManagerServlet.hostName");
+        args[1] = smClient.getString("htmlHostManagerServlet.hostAliases");
+        args[2] = smClient.getString("htmlHostManagerServlet.hostTasks");
+        writer.print(MessageFormat.format(HOSTS_HEADER_SECTION, args));
+
+        // Hosts Row Section
+        // Create sorted map of host names.
+        Container[] children = engine.findChildren();
+        String hostNames[] = new String[children.length];
+        for (int i = 0; i < children.length; i++)
+            hostNames[i] = children[i].getName();
+
+        TreeMap<String,String> sortedHostNamesMap =
+            new TreeMap<String,String>();
+
+        for (int i = 0; i < hostNames.length; i++) {
+            String displayPath = hostNames[i];
+            sortedHostNamesMap.put(displayPath, hostNames[i]);
+        }
+
+        String hostsStart =
+            smClient.getString("htmlHostManagerServlet.hostsStart");
+        String hostsStop =
+            smClient.getString("htmlHostManagerServlet.hostsStop");
+        String hostsRemove =
+            smClient.getString("htmlHostManagerServlet.hostsRemove");
+
+        Iterator<Map.Entry<String,String>> iterator =
+            sortedHostNamesMap.entrySet().iterator();
+        while (iterator.hasNext()) {
+            Map.Entry<String,String> entry = iterator.next();
+            String hostName = entry.getKey();
+            Host host = (Host) engine.findChild(hostName);
+
+            if (host != null ) {
+                args = new Object[2];
+                args[0] = RequestUtil.filter(hostName);
+                String[] aliases = host.findAliases();
+                StringBuilder buf = new StringBuilder();
+                if (aliases.length > 0) {
+                    buf.append(aliases[0]);
+                    for (int j = 1; j < aliases.length; j++) {
+                        buf.append(", ").append(aliases[j]);
+                    }
+                }
+
+                if (buf.length() == 0) {
+                    buf.append("&nbsp;");
+                    args[1] = buf.toString();
+                } else {
+                    args[1] = RequestUtil.filter(buf.toString());
+                }
+
+                writer.print
+                    (MessageFormat.format(HOSTS_ROW_DETAILS_SECTION, args));
+
+                args = new Object[4];
+                if (host.getState().isAvailable()) {
+                    args[0] = response.encodeURL
+                    (request.getContextPath() +
+                     "/html/stop?name=" +
+                     URLEncoder.encode(hostName, "UTF-8"));
+                    args[1] = hostsStop;
+                } else {
+                    args[0] = response.encodeURL
+                        (request.getContextPath() +
+                         "/html/start?name=" +
+                         URLEncoder.encode(hostName, "UTF-8"));
+                    args[1] = hostsStart;
+                }
+                args[2] = response.encodeURL
+                    (request.getContextPath() +
+                     "/html/remove?name=" +
+                     URLEncoder.encode(hostName, "UTF-8"));
+                args[3] = hostsRemove;
+                if (host == this.installedHost) {
+                    writer.print(MessageFormat.format(
+                        MANAGER_HOST_ROW_BUTTON_SECTION, args));
+                } else {
+                    writer.print(MessageFormat.format(
+                        HOSTS_ROW_BUTTON_SECTION, args));
+                }
+            }
+        }
+
+        // Add Section
+        args = new Object[6];
+        args[0] = smClient.getString("htmlHostManagerServlet.addTitle");
+        args[1] = smClient.getString("htmlHostManagerServlet.addHost");
+        args[2] = response.encodeURL(request.getContextPath() + "/html/add");
+        args[3] = smClient.getString("htmlHostManagerServlet.addName");
+        args[4] = smClient.getString("htmlHostManagerServlet.addAliases");
+        args[5] = smClient.getString("htmlHostManagerServlet.addAppBase");
+        writer.print(MessageFormat.format(ADD_SECTION_START, args));
+ 
+        args = new Object[3];
+        args[0] = smClient.getString("htmlHostManagerServlet.addAutoDeploy");
+        args[1] = "autoDeploy";
+        args[2] = "checked";
+        writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args));
+        args[0] = smClient.getString(
+                "htmlHostManagerServlet.addDeployOnStartup");
+        args[1] = "deployOnStartup";
+        args[2] = "checked";
+        writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args));
+        args[0] = smClient.getString("htmlHostManagerServlet.addDeployXML");
+        args[1] = "deployXML";
+        args[2] = "checked";
+        writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args));
+        args[0] = smClient.getString("htmlHostManagerServlet.addUnpackWARs");
+        args[1] = "unpackWARs";
+        args[2] = "checked";
+        writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args));
+
+        args[0] = smClient.getString("htmlHostManagerServlet.addManager");
+        args[1] = "manager";
+        args[2] = "checked";
+        writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args));
+        
+        args = new Object[1];
+        args[0] = smClient.getString("htmlHostManagerServlet.addButton");
+        writer.print(MessageFormat.format(ADD_SECTION_END, args));
+
+        // Server Header Section
+        args = new Object[7];
+        args[0] = smClient.getString("htmlHostManagerServlet.serverTitle");
+        args[1] = smClient.getString("htmlHostManagerServlet.serverVersion");
+        args[2] = smClient.getString("htmlHostManagerServlet.serverJVMVersion");
+        args[3] = smClient.getString("htmlHostManagerServlet.serverJVMVendor");
+        args[4] = smClient.getString("htmlHostManagerServlet.serverOSName");
+        args[5] = smClient.getString("htmlHostManagerServlet.serverOSVersion");
+        args[6] = smClient.getString("htmlHostManagerServlet.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();
+    }
+
+    
+    // ------------------------------------------------------ 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 HOSTS_HEADER_SECTION =
+        "<table border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n" +
+        "<tr>\n" +
+        " <td colspan=\"5\" class=\"title\">{0}</td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td class=\"header-left\"><small>{0}</small></td>\n" +
+        " <td class=\"header-center\"><small>{1}</small></td>\n" +
+        " <td class=\"header-center\"><small>{2}</small></td>\n" +
+        "</tr>\n";
+
+    private static final String HOSTS_ROW_DETAILS_SECTION =
+        "<tr>\n" +
+        " <td class=\"row-left\"><small><a href=\"http://{0}\">{0}</a>" +
+        "</small></td>\n" +
+        " <td class=\"row-center\"><small>{1}</small></td>\n";
+
+    private static final String MANAGER_HOST_ROW_BUTTON_SECTION =
+        " <td class=\"row-left\">\n" +
+        "  <small>\n" +
+        sm.getString("htmlHostManagerServlet.hostThis") +
+        "  </small>\n" +
+        " </td>\n" +
+        "</tr>\n";
+
+    private static final String HOSTS_ROW_BUTTON_SECTION =
+        " <td class=\"row-left\" NOWRAP>\n" +
+        "  <form class=\"inline\" method=\"POST\" action=\"{0}\">" +
+        "   <small><input type=\"submit\" value=\"{1}\"></small>" +
+        "  </form>\n" +
+        "  <form class=\"inline\" method=\"POST\" action=\"{2}\">" +
+        "   <small><input type=\"submit\" value=\"{3}\"></small>" +
+        "  </form>\n" +
+        " </td>\n" +
+        "</tr>\n";
+
+    private static final String ADD_SECTION_START =
+        "</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=\"name\" 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=\"aliases\" size=\"64\">\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=\"appBase\" size=\"64\">\n" +
+        " </td>\n" +
+        "</tr>\n" ;
+    
+        private static final String ADD_SECTION_BOOLEAN =
+        "<tr>\n" +
+        " <td class=\"row-right\">\n" +
+        "  <small>{0}</small>\n" +
+        " </td>\n" +
+        " <td class=\"row-left\">\n" +
+        "  <input type=\"checkbox\" name=\"{1}\" {2}>\n" +
+        " </td>\n" +
+        "</tr>\n" ;
+        
+        private static final String ADD_SECTION_END =
+        "<tr>\n" +
+        " <td class=\"row-right\">\n" +
+        "  &nbsp;\n" +
+        " </td>\n" +
+        " <td class=\"row-left\">\n" +
+        "  <input type=\"submit\" value=\"{0}\">\n" +
+        " </td>\n" +
+        "</tr>\n" +
+         "</table>\n" +
+        "</form>\n" +
+        "</td>\n" +
+        "</tr>\n" +
+        "</table>\n" +
+        "<br>\n" +
+        "\n";
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/manager/host/HostManagerServlet.java b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/host/HostManagerServlet.java
new file mode 100644
index 0000000..9295fd2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/host/HostManagerServlet.java
@@ -0,0 +1,729 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.host;
+
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.StringTokenizer;
+
+import javax.management.MBeanServer;
+import javax.servlet.ServletException;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.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.Wrapper;
+import org.apache.catalina.core.ContainerBase;
+import org.apache.catalina.core.StandardHost;
+import org.apache.catalina.startup.HostConfig;
+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 virtual hosts installed
+ * on the server.  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>/add?name={host-name}&aliases={host-aliases}&manager={manager}</b> -
+ *     Create and add a new virtual host. The <code>host-name</code> attribute
+ *     indicates the name of the new host. The <code>host-aliases</code> 
+ *     attribute is a comma separated list of the host alias names. 
+ *     The <code>manager</code> attribute is a boolean value indicating if the
+ *     webapp manager will be installed in the newly created host (optional, 
+ *     false by default).</li>
+ * <li><b>/remove?name={host-name}</b> - Remove a virtual host. 
+ *     The <code>host-name</code> attribute indicates the name of the host.
+ *     </li>
+ * <li><b>/list</b> - List the virtual hosts installed on the server.
+ *     Each host will be listed with the following format 
+ *     <code>host-name#host-aliases</code>.</li>
+ * <li><b>/start?name={host-name}</b> - Start the virtual host.</li>
+ * <li><b>/stop?name={host-name}</b> - Stop the virtual host.</li>
+ * </ul>
+ * <p>
+ * <b>NOTE</b> - Attempting to stop or remove the host containing
+ * this servlet itself will not succeed.  Therefore, this servlet should
+ * generally be deployed in a separate virtual host.
+ * <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: HostManagerServlet.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ */
+
+public class HostManagerServlet
+    extends HttpServlet implements ContainerServlet {
+
+    private static final long serialVersionUID = 1L;
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The Context container associated with our web application.
+     */
+    protected transient Context context = null;
+
+
+    /**
+     * The debugging detail level for this servlet.
+     */
+    protected int debug = 1;
+
+
+    /**
+     * The associated host.
+     */
+    protected transient Host installedHost = null;
+
+    
+    /**
+     * The associated engine.
+     */
+    protected transient Engine engine = null;
+
+    
+    /**
+     * MBean server.
+     */
+    protected transient MBeanServer mBeanServer = 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;
+            installedHost = null;
+            engine = null;
+        } else {
+            context = (Context) wrapper.getParent();
+            installedHost = (Host) context.getParent();
+            engine = (Engine) installedHost.getParent();
+        }
+
+        // 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 name = request.getParameter("name");
+  
+        // 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(sm.getString("hostManagerServlet.noCommand"));
+        } else if (command.equals("/add")) {
+            add(request, writer, name, false, smClient);
+        } else if (command.equals("/remove")) {
+            remove(writer, name, smClient);
+        } else if (command.equals("/list")) {
+            list(writer, smClient);
+        } else if (command.equals("/start")) {
+            start(writer, name, smClient);
+        } else if (command.equals("/stop")) {
+            stop(writer, name, smClient);
+        } else {
+            writer.println(sm.getString("hostManagerServlet.unknownCommand",
+                                        command));
+        }
+
+        // Finish up the response
+        writer.flush();
+        writer.close();
+
+    }
+
+
+    /**
+     * Add host with the given parameters.
+     *
+     * @param request The request
+     * @param writer The output writer
+     * @param name The host name
+     * @param htmlMode Flag value
+     */
+    protected void add(HttpServletRequest request, PrintWriter writer,
+            String name, boolean htmlMode, StringManager smClient) {
+        String aliases = request.getParameter("aliases");
+        String appBase = request.getParameter("appBase");
+        boolean manager = booleanParameter(request, "manager", false, htmlMode);
+        boolean autoDeploy = booleanParameter(request, "autoDeploy", true, htmlMode);
+        boolean deployOnStartup = booleanParameter(request, "deployOnStartup", true, htmlMode);
+        boolean deployXML = booleanParameter(request, "deployXML", true, htmlMode);
+        boolean unpackWARs = booleanParameter(request, "unpackWARs", true, htmlMode);
+        add(writer, name, aliases, appBase, manager,
+            autoDeploy,
+            deployOnStartup,
+            deployXML,                                       
+            unpackWARs,
+            smClient);
+    }
+
+
+    /**
+     * Extract boolean value from checkbox with default.
+     * @param request
+     * @param parameter
+     * @param theDefault
+     * @param htmlMode
+     */
+    protected boolean booleanParameter(HttpServletRequest request,
+            String parameter, boolean theDefault, boolean htmlMode) {
+        String value = request.getParameter(parameter);
+        boolean booleanValue = theDefault;
+        if (value != null) {
+            if (htmlMode) {
+                if (value.equals("on")) {
+                    booleanValue = true;
+                }
+            } else if (theDefault) {
+                if (value.equals("false")) {
+                    booleanValue = false;
+                }
+            } else if (value.equals("true")) {
+                booleanValue = true;
+            }
+        } else if (htmlMode)
+            booleanValue = false;
+        return booleanValue;
+    }
+
+
+    /**
+     * 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("hostManagerServlet.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);
+        }
+
+    }
+
+
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Add a host using the specified parameters.
+     *
+     * @param writer Writer to render results to
+     * @param name host name
+     * @param aliases comma separated alias list
+     * @param appBase application base for the host
+     * @param manager should the manager webapp be deployed to the new host ?
+     */
+    protected synchronized void add
+        (PrintWriter writer, String name, String aliases, String appBase, 
+         boolean manager,
+         boolean autoDeploy,
+         boolean deployOnStartup,
+         boolean deployXML,                                       
+         boolean unpackWARs,
+         StringManager smClient) {
+        if (debug >= 1) {
+            log(sm.getString("hostManagerServlet.add", name));
+        }
+
+        // Validate the requested host name
+        if ((name == null) || name.length() == 0) {
+            writer.println(smClient.getString(
+                    "hostManagerServlet.invalidHostName", name));
+            return;
+        }
+
+        // Check if host already exists
+        if (engine.findChild(name) != null) {
+            writer.println(smClient.getString(
+                    "hostManagerServlet.alreadyHost", name));
+            return;
+        }
+
+        // Validate and create appBase
+        File appBaseFile = null;
+        File file = null;
+        if (appBase == null || appBase.length() == 0) {
+            file = new File(name);
+        } else {
+            file = new File(appBase);
+        }
+        if (!file.isAbsolute())
+            file = new File(System.getProperty(Globals.CATALINA_BASE_PROP), file.getPath());
+        try {
+            appBaseFile = file.getCanonicalFile();
+        } catch (IOException e) {
+            appBaseFile = file;
+        }
+        if (!appBaseFile.exists()) {
+            if (!appBaseFile.mkdirs()) {
+                writer.println(smClient.getString(
+                        "hostManagerServlet.appBaseCreateFail",
+                        appBaseFile.toString(), name));
+                return;
+            }
+        }
+        
+        // Create base for config files
+        File configBaseFile = getConfigBase(name);
+        
+        // Copy manager.xml if requested
+        if (manager) {
+            if (configBaseFile == null) {
+                writer.println(smClient.getString(
+                        "hostManagerServlet.configBaseCreateFail", name));
+                return;
+            }
+            InputStream is = null;
+            OutputStream os = null;
+            try {
+                is = getServletContext().getResourceAsStream("/manager.xml");
+                os = new FileOutputStream(new File(configBaseFile, "manager.xml"));
+                byte buffer[] = new byte[512];
+                int len = buffer.length;
+                while (true) {
+                    len = is.read(buffer);
+                    if (len == -1)
+                        break;
+                    os.write(buffer, 0, len);
+                }
+            } catch (IOException e) {
+                writer.println(smClient.getString(
+                        "hostManagerServlet.managerXml"));
+                return;
+            } finally {
+                if (is != null) {
+                    try {
+                        is.close();
+                    } catch (IOException e) {
+                        // Ignore
+                    }
+                }
+                if (os != null) {
+                    try {
+                        os.close();
+                    } catch (IOException e) {
+                        // Ignore
+                    }
+                }
+            }
+        }
+        
+        StandardHost host = new StandardHost();
+        host.setAppBase(appBase);
+        host.setName(name);
+
+        host.addLifecycleListener(new HostConfig());
+
+        // Add host aliases
+        if ((aliases != null) && !("".equals(aliases))) {
+            StringTokenizer tok = new StringTokenizer(aliases, ", ");
+            while (tok.hasMoreTokens()) {
+                host.addAlias(tok.nextToken());
+            }
+        }
+        host.setAutoDeploy(autoDeploy);
+        host.setDeployOnStartup(deployOnStartup);
+        host.setDeployXML(deployXML);
+        host.setUnpackWARs(unpackWARs);
+        
+        // Add new host
+        try {
+            engine.addChild(host);
+        } catch (Exception e) {
+            writer.println(smClient.getString("hostManagerServlet.exception",
+                    e.toString()));
+            return;
+        }
+        
+        host = (StandardHost) engine.findChild(name);
+        if (host != null) {
+            writer.println(smClient.getString("hostManagerServlet.add", name));
+        } else {
+            // Something failed
+            writer.println(smClient.getString(
+                    "hostManagerServlet.addFailed", name));
+        }
+        
+    }
+
+
+    /**
+     * Remove the specified host.
+     *
+     * @param writer Writer to render results to
+     * @param name host name
+     */
+    protected synchronized void remove(PrintWriter writer, String name,
+            StringManager smClient) {
+
+        if (debug >= 1) {
+            log(sm.getString("hostManagerServlet.remove", name));
+        }
+
+        // Validate the requested host name
+        if ((name == null) || name.length() == 0) {
+            writer.println(smClient.getString(
+                    "hostManagerServlet.invalidHostName", name));
+            return;
+        }
+
+        // Check if host exists
+        if (engine.findChild(name) == null) {
+            writer.println(smClient.getString(
+                    "hostManagerServlet.noHost", name));
+            return;
+        }
+
+        // Prevent removing our own host
+        if (engine.findChild(name) == installedHost) {
+            writer.println(smClient.getString(
+                    "hostManagerServlet.cannotRemoveOwnHost", name));
+            return;
+        }
+
+        // Remove host
+        // Note that the host will not get physically removed
+        try {
+            Container child = engine.findChild(name);
+            engine.removeChild(child);
+            if ( child instanceof ContainerBase ) ((ContainerBase)child).destroy();
+        } catch (Exception e) {
+            writer.println(smClient.getString("hostManagerServlet.exception",
+                    e.toString()));
+            return;
+        }
+        
+        Host host = (StandardHost) engine.findChild(name);
+        if (host == null) {
+            writer.println(smClient.getString(
+                    "hostManagerServlet.remove", name));
+        } else {
+            // Something failed
+            writer.println(smClient.getString(
+                    "hostManagerServlet.removeFailed", name));
+        }
+        
+    }
+
+
+    /**
+     * 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(sm.getString("hostManagerServlet.list", engine.getName()));
+        }
+
+        writer.println(smClient.getString("hostManagerServlet.listed",
+                engine.getName()));
+        Container[] hosts = engine.findChildren();
+        for (int i = 0; i < hosts.length; i++) {
+            Host host = (Host) hosts[i];
+            String name = host.getName();
+            String[] aliases = host.findAliases();
+            StringBuilder buf = new StringBuilder();
+            if (aliases.length > 0) {
+                buf.append(aliases[0]);
+                for (int j = 1; j < aliases.length; j++) {
+                    buf.append(',').append(aliases[j]);
+                }
+            }
+            writer.println(smClient.getString("hostManagerServlet.listitem",
+                                        name, buf.toString()));
+        }
+    }
+
+
+    /**
+     * Start the host with the specified name.
+     *
+     * @param writer Writer to render to
+     * @param name Host name
+     */
+    protected void start(PrintWriter writer, String name,
+            StringManager smClient) {
+
+        if (debug >= 1) {
+            log(sm.getString("hostManagerServlet.start", name));
+        }
+
+        // Validate the requested host name
+        if ((name == null) || name.length() == 0) {
+            writer.println(smClient.getString(
+                    "hostManagerServlet.invalidHostName", name));
+            return;
+        }
+
+        Container host = engine.findChild(name);
+        
+        // Check if host exists
+        if (host == null) {
+            writer.println(smClient.getString(
+                    "hostManagerServlet.noHost", name));
+            return;
+        }
+
+        // Prevent starting our own host
+        if (host == installedHost) {
+            writer.println(smClient.getString(
+                    "hostManagerServlet.cannotStartOwnHost", name));
+            return;
+        }
+
+        // Don't start host of already started
+        if (host.getState().isAvailable()) {
+            writer.println(smClient.getString(
+                    "hostManagerServlet.alreadyStarted", name));
+            return;
+        }
+
+        // Start host
+        try {
+            host.start();
+            writer.println(smClient.getString(
+                    "hostManagerServlet.started", name));
+        } catch (Exception e) {
+            getServletContext().log
+                (sm.getString("hostManagerServlet.startFailed", name), e);
+            writer.println(smClient.getString(
+                    "hostManagerServlet.startFailed", name));
+            writer.println(smClient.getString(
+                    "hostManagerServlet.exception", e.toString()));
+            return;
+        }
+        
+    }
+
+
+    /**
+     * Start the host with the specified name.
+     *
+     * @param writer Writer to render to
+     * @param name Host name
+     */
+    protected void stop(PrintWriter writer, String name,
+            StringManager smClient) {
+
+        if (debug >= 1) {
+            log(sm.getString("hostManagerServlet.stop", name));
+        }
+
+        // Validate the requested host name
+        if ((name == null) || name.length() == 0) {
+            writer.println(smClient.getString(
+                    "hostManagerServlet.invalidHostName", name));
+            return;
+        }
+
+        Container host = engine.findChild(name);
+
+        // Check if host exists
+        if (host == null) {
+            writer.println(smClient.getString("hostManagerServlet.noHost",
+                    name));
+            return;
+        }
+
+        // Prevent starting our own host
+        if (host == installedHost) {
+            writer.println(smClient.getString(
+                    "hostManagerServlet.cannotStopOwnHost", name));
+            return;
+        }
+
+        // Don't stop host of already stopped
+        if (!host.getState().isAvailable()) {
+            writer.println(smClient.getString(
+                    "hostManagerServlet.alreadyStopped", name));
+            return;
+        }
+
+        // Stop host
+        try {
+            host.stop();
+            writer.println(smClient.getString("hostManagerServlet.stopped",
+                    name));
+        } catch (Exception e) {
+            getServletContext().log(sm.getString(
+                    "hostManagerServlet.stopFailed", name), e);
+            writer.println(smClient.getString("hostManagerServlet.stopFailed",
+                    name));
+            writer.println(smClient.getString("hostManagerServlet.exception",
+                    e.toString()));
+            return;
+        }
+        
+    }
+
+
+    // -------------------------------------------------------- Support Methods
+
+
+    /**
+     * Get config base.
+     */
+    protected File getConfigBase(String hostName) {
+        File configBase = 
+            new File(System.getProperty(Globals.CATALINA_BASE_PROP), "conf");
+        if (!configBase.exists()) {
+            return null;
+        }
+        if (engine != null) {
+            configBase = new File(configBase, engine.getName());
+        }
+        if (installedHost != null) {
+            configBase = new File(configBase, hostName);
+        }
+        if (!configBase.exists()) {
+            if (!configBase.mkdirs()) {
+                return null;
+            }
+        }
+        return configBase;
+    }
+
+
+    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;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/manager/host/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/host/LocalStrings.properties
new file mode 100644
index 0000000..d3776dc
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/host/LocalStrings.properties
@@ -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.
+
+hostManagerServlet.alreadyStarted=FAIL - Host [{0}] is already started
+hostManagerServlet.alreadyStopped=FAIL - Host [{0}] is already stopped
+hostManagerServlet.appBaseCreateFail=FAIL - Failed to create appBase [{0}] for host [{1}]
+hostManagerServlet.configBaseCreateFail=FAIL - Failed to identify configBase for host [{0}]
+hostManagerServlet.noCommand=FAIL - No command was specified
+hostManagerServlet.postCommand=FAIL - Tried to use command {0} via a GET request but POST is required
+hostManagerServlet.unknownCommand=FAIL - Unknown command {0}
+hostManagerServlet.noWrapper=Container has not called setWrapper() for this servlet
+hostManagerServlet.invalidHostName=FAIL - Invalid host name {0} was specified
+hostManagerServlet.noHost=FAIL - Host name {0} does not exist
+hostManagerServlet.alreadyHost=FAIL - Host already exists with host name {0}
+hostManagerServlet.managerXml=FAIL - Couldn't install manager.xml
+hostManagerServlet.exception=FAIL - Encountered exception {0}
+hostManagerServlet.add=OK - Host {0} added
+hostManagerServlet.addFailed=FAIL - Failed to add host {0}
+hostManagerServlet.cannotRemoveOwnHost=FAIL - Cannot remove own host {0}
+hostManagerServlet.remove=OK - Removed host {0}
+hostManagerServlet.removeFailed=FAIL - Failed to remove host {0}
+hostManagerServlet.listed=OK - Listed hosts
+hostManagerServlet.listitem={0}:{1}
+hostManagerServlet.cannotStartOwnHost=FAIL - Cannot start own host {0}
+hostManagerServlet.started=OK - Host {0} started
+hostManagerServlet.startFailed=FAIL - Failed to start host {0}
+hostManagerServlet.cannotStopOwnHost=FAIL - Cannot stop own host {0}
+hostManagerServlet.stopped=OK - Host {0} stopped
+hostManagerServlet.stopFailed=FAIL - Failed to stop host {0}
+hostManagerServlet.add=add: Adding host [{0}]
+hostManagerServlet.remove=remove: Removing host [{0}]
+hostManagerServlet.list=list: Listing hosts for engine [{0}]
+hostManagerServlet.start=start: Starting host with name [{0}]
+hostManagerServlet.stop=stop: Stopping host with name [{0}]
+
+htmlHostManagerServlet.title=Tomcat Virtual Host Manager
+htmlHostManagerServlet.messageLabel=Message:
+htmlHostManagerServlet.manager=Host Manager
+htmlHostManagerServlet.list=List Virtual Hosts
+htmlHostManagerServlet.helpHtmlManagerFile=../docs/html-host-manager-howto.html
+htmlHostManagerServlet.helpHtmlManager=HTML Host Manager Help (TODO)
+htmlHostManagerServlet.helpManagerFile=../docs/host-manager-howto.html
+htmlHostManagerServlet.helpManager=Host Manager Help (TODO)
+htmlHostManagerServlet.hostName=Host name
+htmlHostManagerServlet.hostAliases=Host aliases
+htmlHostManagerServlet.hostTasks=Commands
+htmlHostManagerServlet.hostsStart=Start
+htmlHostManagerServlet.hostsStop=Stop
+htmlHostManagerServlet.hostsRemove=Remove
+htmlHostManagerServlet.hostThis=Host Manager installed - commands disabled
+htmlHostManagerServlet.addTitle=Add Virtual Host
+htmlHostManagerServlet.addHost=Host
+htmlHostManagerServlet.addName=Name:
+htmlHostManagerServlet.addAliases=Aliases:
+htmlHostManagerServlet.addAppBase=App base:
+htmlHostManagerServlet.addManager=Manager App
+htmlHostManagerServlet.addAutoDeploy=AutoDeploy
+htmlHostManagerServlet.addDeployOnStartup=DeployOnStartup
+htmlHostManagerServlet.addDeployXML=DeployXML
+htmlHostManagerServlet.addUnpackWARs=UnpackWARs
+htmlHostManagerServlet.addButton=Add
+htmlHostManagerServlet.serverTitle=Server Information
+htmlHostManagerServlet.serverVersion=Tomcat Version
+htmlHostManagerServlet.serverJVMVersion=JVM Version
+htmlHostManagerServlet.serverJVMVendor=JVM Vendor
+htmlHostManagerServlet.serverOSName=OS Name
+htmlHostManagerServlet.serverOSVersion=OS Version
+htmlHostManagerServlet.serverOSArch=OS Architecture
+
+statusServlet.title=Server Status
+statusServlet.complete=Complete Server Status
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/manager/host/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/host/LocalStrings_es.properties
new file mode 100644
index 0000000..dc3e276
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/manager/host/LocalStrings_es.properties
@@ -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.
+
+hostManagerServlet.noCommand = FALLO - No se ha especificado comando
+hostManagerServlet.unknownCommand = FALLO - Comando desconocido {0}
+hostManagerServlet.noWrapper = El contenedor no ha llamado a setWrapper() para este servlet
+hostManagerServlet.invalidHostName = FALLO - Se ha especificado un nombre inv\u00E1lido de m\u00E1quina {0}
+hostManagerServlet.alreadyHost = FALLO - Ya existe m\u00E1quina con nombre de m\u00E1quina {0}
+hostManagerServlet.managerXml = FALLO - no pude instalar manager.xml
+hostManagerServlet.exception = FALLO - Encontrada excepci\u00F3n {0}
+hostManagerServlet.add = OK - A\u00F1adida m\u00E1quina {0}
+hostManagerServlet.addFailed = FALLO - No pude a\u00F1adir m\u00E1quina {0}
+hostManagerServlet.cannotRemoveOwnHost = FALLO - No puedo quitar m\u00E1quina propia {0}
+hostManagerServlet.remove = OK - Quitada m\u00E1quina {0}
+hostManagerServlet.removeFailed = FALLO - No pude quitar m\u00E1quina {0}
+hostManagerServlet.listed = OK - M\u00E1quinas listadas
+hostManagerServlet.listitem = {0}\:{1}
+hostManagerServlet.cannotStartOwnHost = FALLO - No puedo empezar m\u00E1quina propia {0}
+hostManagerServlet.started = OK - M\u00E1quina {0} arrancada
+hostManagerServlet.startFailed = FALLO - No pude arrancar m\u00E1quina {0}
+hostManagerServlet.cannotStopOwnHost = FALLO - No puedo para m\u00E1quina propia {0}
+hostManagerServlet.stopped = OK - M\u00E1quina {0} parada
+hostManagerServlet.stopFailed = FALLO - No pude parar m\u00E1quina {0}
+htmlHostManagerServlet.title = Gestor de M\u00E1quina Virtual de Tomcat
+htmlHostManagerServlet.messageLabel = Mensaje\:
+htmlHostManagerServlet.manager = Gestor de M\u00E1quina
+htmlHostManagerServlet.list = Lista de M\u00E1quinas Virtuales
+htmlHostManagerServlet.helpHtmlManagerFile = html-host-manager-howto.html
+htmlHostManagerServlet.helpHtmlManager = Ayuda de Gestor de M\u00E1quina HTML (\u00A1En breve\!)
+htmlHostManagerServlet.helpManagerFile = ../docs/host-manager-howto.html
+htmlHostManagerServlet.helpManager = Ayuda de Gestor de M\u00E1quina
+htmlHostManagerServlet.hostName = Nombre de M\u00E1quina
+htmlHostManagerServlet.hostAliases = Aliases de M\u00E1quina
+htmlHostManagerServlet.hostTasks = Comandos
+htmlHostManagerServlet.hostsStart = Iniciar
+htmlHostManagerServlet.hostsStop = Parar
+htmlHostManagerServlet.hostsRemove = Quitar
+htmlHostManagerServlet.addTitle = A\u00F1adir M\u00E1quina Virtual
+htmlHostManagerServlet.addHost = M\u00E1quina
+htmlHostManagerServlet.addName = Nombre\:
+htmlHostManagerServlet.addAliases = Aliases\:
+htmlHostManagerServlet.addAppBase = App base\:
+htmlHostManagerServlet.addManager = App de Gestor
+htmlHostManagerServlet.addAutoDeploy = AutoDeploy
+htmlHostManagerServlet.addDeployOnStartup = DeployOnStartup
+htmlHostManagerServlet.addDeployXML = DeployXML
+htmlHostManagerServlet.addUnpackWARs = UnpackWARs
+htmlHostManagerServlet.addButton = A\u00F1adir
+htmlHostManagerServlet.serverTitle = Informaci\u00F3n de Servidor
+htmlHostManagerServlet.serverVersion = Versi\u00F3n de Tomcat
+htmlHostManagerServlet.serverJVMVersion = Versi\u00F3n de JVM
+htmlHostManagerServlet.serverJVMVendor = Vendedor JVM
+htmlHostManagerServlet.serverOSName = Nombre de SO
+htmlHostManagerServlet.serverOSVersion = Versi\u00F3n de SO
+htmlHostManagerServlet.serverOSArch = Arquitectura de SO
+statusServlet.title = Estado de Servidor
+statusServlet.complete = Completar Estado de Servidor
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans-descriptors.xml b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans-descriptors.xml
new file mode 100644
index 0000000..27ecc13
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/mbeans-descriptors.xml
@@ -0,0 +1,168 @@
+<?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="Group"
+         className="org.apache.catalina.mbeans.GroupMBean"
+          description="Group from a user database"
+               domain="Users"
+                group="Group"
+                 type="org.apache.catalina.Group">
+
+    <attribute   name="description"
+          description="Description of this group"
+                 type="java.lang.String"/>
+
+    <attribute   name="groupname"
+          description="Group name of this group"
+                 type="java.lang.String"/>
+
+    <attribute   name="roles"
+          description="MBean Names of roles for this group"
+                 type="[Ljava.lang.String;"
+            writeable="false"/>
+
+    <attribute   name="users"
+          description="MBean Names of user members of this group"
+                 type="[Ljava.lang.String;"
+            writeable="false"/>
+
+    <operation   name="addRole"
+          description="Add a new authorized role for this group"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="role"
+          description="Role to be added"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="removeRole"
+          description="Remove an old authorized role for this group"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="role"
+          description="Role to be removed"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="removeRoles"
+          description="Remove all authorized roles for this group"
+               impact="ACTION"
+           returnType="void">
+    </operation>
+
+  </mbean>
+
+  <mbean         name="Role"
+            className="org.apache.catalina.mbeans.RoleMBean"
+          description="Security role from a user database"
+               domain="Users"
+                group="Role"
+                 type="org.apache.catalina.Role">
+
+    <attribute   name="description"
+          description="Description of this role"
+                 type="java.lang.String"/>
+
+    <attribute   name="rolename"
+          description="Role name of this role"
+                 type="java.lang.String"
+            writeable="false"/>
+
+  </mbean>
+
+  <mbean         name="User"
+            className="org.apache.catalina.mbeans.UserMBean"
+          description="User from a user database"
+               domain="Users"
+                group="User"
+                 type="org.apache.catalina.User">
+
+    <attribute   name="fullName"
+          description="Full name of this user"
+                 type="java.lang.String"/>
+
+    <attribute   name="groups"
+          description="MBean Names of groups this user is a member of"
+                 type="[Ljava.lang.String;"
+            writeable="false"/>
+
+    <attribute   name="password"
+          description="Password of this user"
+                 type="java.lang.String"/>
+
+    <attribute   name="roles"
+          description="MBean Names of roles for this user"
+                 type="[Ljava.lang.String;"
+            writeable="false"/>
+
+    <attribute   name="username"
+          description="User name of this user"
+                 type="java.lang.String"/>
+
+    <operation   name="addGroup"
+          description="Add a new group membership for this user"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="groupname"
+          description="Group name of the new group"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="addRole"
+          description="Add a new authorized role for this user"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="role"
+          description="Role to be added"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="removeGroup"
+          description="Remove an old group membership for this user"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="groupname"
+          description="Group name of the old group"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="removeGroups"
+          description="Remove all group memberships for this user"
+               impact="ACTION"
+           returnType="void">
+    </operation>
+
+    <operation   name="removeRole"
+          description="Remove an old authorized role for this user"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="role"
+          description="Role to be removed"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="removeRoles"
+          description="Remove all authorized roles for this user"
+               impact="ACTION"
+           returnType="void">
+    </operation>
+
+  </mbean>
+
+</mbeans-descriptors>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/realm/CombinedRealm.java b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/CombinedRealm.java
new file mode 100644
index 0000000..70b2e7f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/CombinedRealm.java
@@ -0,0 +1,344 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.realm;
+
+import java.security.Principal;
+import java.security.cert.X509Certificate;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.management.ObjectName;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.Realm;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSName;
+
+/**
+ * Realm implementation that contains one or more realms. Authentication is
+ * attempted for each realm in the order they were configured. If any realm
+ * authenticates the user then the authentication succeeds. When combining
+ * realms usernames should be unique across all combined realms.
+ */
+public class CombinedRealm extends RealmBase {
+
+    private static final Log log = LogFactory.getLog(CombinedRealm.class);
+
+    /**
+     * The list of Realms contained by this Realm.
+     */
+    protected List<Realm> realms = new LinkedList<Realm>();
+
+    /**
+     * Descriptive information about this Realm implementation.
+     */
+    protected static final String name = "CombinedRealm";
+
+    /**
+     * Add a realm to the list of realms that will be used to authenticate
+     * users.
+     */
+    public void addRealm(Realm theRealm) {
+        realms.add(theRealm);
+        
+        if (log.isDebugEnabled()) {
+            sm.getString("combinedRealm.addRealm", theRealm.getInfo(), 
+                    Integer.toString(realms.size()));
+        }
+    }
+
+
+    /**
+     * Return the set of Realms that this Realm is wrapping
+     */
+    public ObjectName[] getRealms() {
+        ObjectName[] result = new ObjectName[realms.size()];
+        for (Realm realm : realms) {
+            if (realm instanceof RealmBase) {
+                result[realms.indexOf(realm)] =
+                    ((RealmBase) realm).getObjectName();
+            }
+        }
+        return result;
+    }
+
+
+    /**
+     * Return the Principal associated with the specified username, which
+     * matches the digest calculated using the given parameters using the
+     * method described in RFC 2069; otherwise return <code>null</code>.
+     *
+     * @param username Username of the Principal to look up
+     * @param clientDigest Digest which has been submitted by the client
+     * @param nonce Unique (or supposedly unique) token which has been used
+     * for this request
+     * @param realmName Realm name
+     * @param md5a2 Second MD5 digest used to calculate the digest :
+     * MD5(Method + ":" + uri)
+     */
+    @Override
+    public Principal authenticate(String username, String clientDigest,
+            String nonce, String nc, String cnonce, String qop,
+            String realmName, String md5a2) {
+        Principal authenticatedUser = null;
+        
+        for (Realm realm : realms) {
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString("combinedRealm.authStart", username, realm.getInfo()));
+            }
+
+            authenticatedUser = realm.authenticate(username, clientDigest, nonce,
+                    nc, cnonce, qop, realmName, md5a2);
+
+            if (authenticatedUser == null) {
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString("combinedRealm.authFail", username, realm.getInfo()));
+                }
+            } else {
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString("combinedRealm.authSucess", username, realm.getInfo()));
+                }
+                break;
+            }
+        }
+        return authenticatedUser;
+    }
+
+
+    /**
+     * Return the Principal associated with the specified username and
+     * credentials, if there is one; otherwise return <code>null</code>.
+     *
+     * @param username Username of the Principal to look up
+     * @param credentials Password or other credentials to use in
+     *  authenticating this username
+     */
+    @Override
+    public Principal authenticate(String username, String credentials) {
+        Principal authenticatedUser = null;
+        
+        for (Realm realm : realms) {
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString("combinedRealm.authStart", username, realm.getInfo()));
+            }
+
+            authenticatedUser = realm.authenticate(username, credentials);
+
+            if (authenticatedUser == null) {
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString("combinedRealm.authFail", username, realm.getInfo()));
+                }
+            } else {
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString("combinedRealm.authSucess", username, realm.getInfo()));
+                }
+                break;
+            }
+        }
+        return authenticatedUser;
+    }
+
+
+    /**
+     * Set the Container with which this Realm has been associated.
+     *
+     * @param container The associated Container
+     */
+    @Override
+    public void setContainer(Container container) {
+        for(Realm realm : realms) {
+            // Set the realmPath for JMX naming
+            if (realm instanceof RealmBase) {
+                ((RealmBase) realm).setRealmPath(
+                        getRealmPath() + "/realm" + realms.indexOf(realm));
+            }
+            
+            // Set the container for sub-realms. Mainly so logging works.
+            realm.setContainer(container);
+        }
+        super.setContainer(container);
+    }
+
+
+    /**
+     * Prepare for the beginning of active use of the public methods of 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 void startInternal() throws LifecycleException {
+        // Start 'sub-realms' then this one
+        Iterator<Realm> iter = realms.iterator();
+        
+        while (iter.hasNext()) {
+            Realm realm = iter.next();
+            if (realm instanceof Lifecycle) {
+                try {
+                    ((Lifecycle) realm).start();
+                } catch (LifecycleException e) {
+                    // If realm doesn't start can't authenticate against it
+                    iter.remove();
+                    log.error(sm.getString("combinedRealm.realmStartFail",
+                            realm.getInfo()), e);
+                }
+            }
+        }
+        super.startInternal();
+    }
+
+
+    /**
+     * Gracefully terminate the active use of the public methods of this
+     * component and implement the requirements of
+     * {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
+     *
+     * @exception LifecycleException if this component detects a fatal error
+     *  that needs to be reported
+     */
+     @Override
+    protected void stopInternal() throws LifecycleException {
+        // Stop this realm, then the sub-realms (reverse order to start)
+        super.stopInternal();
+        for (Realm realm : realms) {
+            if (realm instanceof Lifecycle) {
+                ((Lifecycle) realm).stop();
+            }
+        }        
+    }
+
+
+    /**
+     * Return the Principal associated with the specified chain of X509
+     * client certificates.  If there is none, return <code>null</code>.
+     *
+     * @param certs Array of client certificates, with the first one in
+     *  the array being the certificate of the client itself.
+     */
+    @Override
+    public Principal authenticate(X509Certificate[] certs) {
+        Principal authenticatedUser = null;
+        String username = null;
+        if (certs != null && certs.length >0) {
+            username = certs[0].getSubjectDN().getName();
+        }
+        
+        for (Realm realm : realms) {
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString("combinedRealm.authStart", username, realm.getInfo()));
+            }
+
+            authenticatedUser = realm.authenticate(certs);
+
+            if (authenticatedUser == null) {
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString("combinedRealm.authFail", username, realm.getInfo()));
+                }
+            } else {
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString("combinedRealm.authSucess", username, realm.getInfo()));
+                }
+                break;
+            }
+        }
+        return authenticatedUser;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Principal authenticate(GSSContext gssContext, boolean storeCreds) {
+        if (gssContext.isEstablished()) {
+            Principal authenticatedUser = null;
+            String username = null;
+            
+            GSSName name = null;
+            try {
+                name = gssContext.getSrcName();
+            } catch (GSSException e) {
+                log.warn(sm.getString("realmBase.gssNameFail"), e);
+                return null;
+            }
+            
+            username = name.toString();
+
+            for (Realm realm : realms) {
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString("combinedRealm.authStart",
+                            username, realm.getInfo()));
+                }
+
+                authenticatedUser = realm.authenticate(gssContext, storeCreds);
+
+                if (authenticatedUser == null) {
+                    if (log.isDebugEnabled()) {
+                        log.debug(sm.getString("combinedRealm.authFail",
+                                username, realm.getInfo()));
+                    }
+                } else {
+                    if (log.isDebugEnabled()) {
+                        log.debug(sm.getString("combinedRealm.authSucess",
+                                username, realm.getInfo()));
+                    }
+                    break;
+                }
+            }
+            return authenticatedUser;
+        }
+        
+        // Fail in all other cases
+        return null;
+    }
+    
+    @Override
+    protected String getName() {
+        return name;
+    }
+
+    @Override
+    protected String getPassword(String username) {
+        // This method should never be called
+        // Stack trace will show where this was called from
+        UnsupportedOperationException uoe =
+            new UnsupportedOperationException(
+                    sm.getString("combinedRealm.getPassword"));
+        log.error(sm.getString("combinedRealm.unexpectedMethod"), uoe);
+        throw uoe;
+    }
+
+    @Override
+    protected Principal getPrincipal(String username) {
+        // This method should never be called
+        // Stack trace will show where this was called from
+        UnsupportedOperationException uoe =
+            new UnsupportedOperationException(
+                    sm.getString("combinedRealm.getPrincipal"));
+        log.error(sm.getString("combinedRealm.unexpectedMethod"), uoe);
+        throw uoe;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/realm/Constants.java b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/Constants.java
new file mode 100644
index 0000000..5529db1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/Constants.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.realm;
+
+
+/**
+ * Manifest constants for this Java package.
+ *
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: Constants.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ */
+
+public final class Constants {
+
+    public static final String Package = "org.apache.catalina.realm";
+    
+        // Authentication methods for login configuration
+    public static final String FORM_METHOD = "FORM";
+
+    // Form based authentication constants
+    public static final String FORM_ACTION = "/j_security_check";
+
+    // User data constraints for transport guarantee
+    public static final String NONE_TRANSPORT = "NONE";
+    public static final String INTEGRAL_TRANSPORT = "INTEGRAL";
+    public static final String CONFIDENTIAL_TRANSPORT = "CONFIDENTIAL";
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/realm/DataSourceRealm.java b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/DataSourceRealm.java
new file mode 100644
index 0000000..cc7aaa9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/DataSourceRealm.java
@@ -0,0 +1,642 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.realm;
+
+
+import java.security.Principal;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+
+import javax.naming.Context;
+import javax.sql.DataSource;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.naming.ContextBindings;
+
+/**
+*
+* Implementation of <b>Realm</b> that works with any JDBC JNDI DataSource.
+* See the JDBCRealm.howto for more details on how to set up the database and
+* for configuration options.
+*
+* @author Glenn L. Nielsen
+* @author Craig R. McClanahan
+* @author Carson McDonald
+* @author Ignacio Ortega
+* @version $Revision: 1.1 $
+*/
+
+public class DataSourceRealm
+    extends RealmBase {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The generated string for the roles PreparedStatement
+     */
+    private String preparedRoles = null;
+
+
+    /**
+     * The generated string for the credentials PreparedStatement
+     */
+    private String preparedCredentials = null;
+
+
+    /**
+     * The name of the JNDI JDBC DataSource
+     */
+    protected String dataSourceName = null;
+
+
+    /**
+     * Descriptive information about this Realm implementation.
+     */
+    protected static final String info =
+        "org.apache.catalina.realm.DataSourceRealm/1.0";
+
+
+    /**
+     * Context local datasource.
+     */
+    protected boolean localDataSource = false;
+
+
+    /**
+     * Descriptive information about this Realm implementation.
+     */
+    protected static final String name = "DataSourceRealm";
+
+
+    /**
+     * The column in the user role table that names a role
+     */
+    protected String roleNameCol = null;
+
+
+    /**
+     * The column in the user table that holds the user's credentials
+     */
+    protected String userCredCol = null;
+
+
+    /**
+     * The column in the user table that holds the user's name
+     */
+    protected String userNameCol = null;
+
+
+    /**
+     * The table that holds the relation between user's and roles
+     */
+    protected String userRoleTable = null;
+
+
+    /**
+     * The table that holds user data.
+     */
+    protected String userTable = null;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the name of the JNDI JDBC DataSource.
+     *
+     */
+    public String getDataSourceName() {
+        return dataSourceName;
+    }
+
+    /**
+     * Set the name of the JNDI JDBC DataSource.
+     *
+     * @param dataSourceName the name of the JNDI JDBC DataSource
+     */
+    public void setDataSourceName( String dataSourceName) {
+      this.dataSourceName = dataSourceName;
+    }
+
+    /**
+     * Return if the datasource will be looked up in the webapp JNDI Context.
+     */
+    public boolean getLocalDataSource() {
+        return localDataSource;
+    }
+
+    /**
+     * Set to true to cause the datasource to be looked up in the webapp JNDI
+     * Context.
+     *
+     * @param localDataSource the new flag value
+     */
+    public void setLocalDataSource(boolean localDataSource) {
+      this.localDataSource = localDataSource;
+    }
+
+    /**
+     * Return the column in the user role table that names a role.
+     *
+     */
+    public String getRoleNameCol() {
+        return roleNameCol;
+    }
+
+    /**
+     * Set the column in the user role table that names a role.
+     *
+     * @param roleNameCol The column name
+     */
+    public void setRoleNameCol( String roleNameCol ) {
+        this.roleNameCol = roleNameCol;
+    }
+
+    /**
+     * Return the column in the user table that holds the user's credentials.
+     *
+     */
+    public String getUserCredCol() {
+        return userCredCol;
+    }
+
+    /**
+     * Set the column in the user table that holds the user's credentials.
+     *
+     * @param userCredCol The column name
+     */
+    public void setUserCredCol( String userCredCol ) {
+       this.userCredCol = userCredCol;
+    }
+
+    /**
+     * Return the column in the user table that holds the user's name.
+     *
+     */
+    public String getUserNameCol() {
+        return userNameCol;
+    }
+
+    /**
+     * Set the column in the user table that holds the user's name.
+     *
+     * @param userNameCol The column name
+     */
+    public void setUserNameCol( String userNameCol ) {
+       this.userNameCol = userNameCol;
+    }
+
+    /**
+     * Return the table that holds the relation between user's and roles.
+     *
+     */
+    public String getUserRoleTable() {
+        return userRoleTable;
+    }
+
+    /**
+     * Set the table that holds the relation between user's and roles.
+     *
+     * @param userRoleTable The table name
+     */
+    public void setUserRoleTable( String userRoleTable ) {
+        this.userRoleTable = userRoleTable;
+    }
+
+    /**
+     * Return the table that holds user data..
+     *
+     */
+    public String getUserTable() {
+        return userTable;
+    }
+
+    /**
+     * Set the table that holds user data.
+     *
+     * @param userTable The table name
+     */
+    public void setUserTable( String userTable ) {
+      this.userTable = userTable;
+    }
+
+    /**
+     * Return descriptive information about this Realm implementation and
+     * the corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    @Override
+    public String getInfo() {
+
+        return info;
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return the Principal associated with the specified username and
+     * credentials, if there is one; otherwise return <code>null</code>.
+     *
+     * If there are any errors with the JDBC connection, executing
+     * the query or anything we return null (don't authenticate). This
+     * event is also logged, and the connection will be closed so that
+     * a subsequent request will automatically re-open it.
+     *
+     * @param username Username of the Principal to look up
+     * @param credentials Password or other credentials to use in
+     *  authenticating this username
+     */
+    @Override
+    public Principal authenticate(String username, String credentials) {
+        
+        // No user or no credentials
+        // Can't possibly authenticate, don't bother the database then
+        if (username == null || credentials == null) {
+            return null;
+        }
+        
+        Connection dbConnection = null;
+
+        // Ensure that we have an open database connection
+        dbConnection = open();
+        if (dbConnection == null) {
+            // If the db connection open fails, return "not authenticated"
+            return null;
+        }
+        
+        // Acquire a Principal object for this user
+        Principal principal = authenticate(dbConnection, username, credentials);
+            
+        close(dbConnection);
+
+        return principal;
+    }
+
+
+    // -------------------------------------------------------- Package Methods
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Return the Principal associated with the specified username and
+     * credentials, if there is one; otherwise return <code>null</code>.
+     *
+     * @param dbConnection The database connection to be used
+     * @param username Username of the Principal to look up
+     * @param credentials Password or other credentials to use in
+     *  authenticating this username
+     */
+    protected Principal authenticate(Connection dbConnection,
+                                               String username,
+                                               String credentials) {
+
+        String dbCredentials = getPassword(dbConnection, username);
+
+        // Validate the user's credentials
+        boolean validated = false;
+        if (hasMessageDigest()) {
+            // Hex hashes should be compared case-insensitive
+            validated = (digest(credentials).equalsIgnoreCase(dbCredentials));
+        } else
+            validated = (digest(credentials).equals(dbCredentials));
+
+        if (validated) {
+            if (containerLog.isTraceEnabled())
+                containerLog.trace(
+                    sm.getString("dataSourceRealm.authenticateSuccess",
+                                 username));
+        } else {
+            if (containerLog.isTraceEnabled())
+                containerLog.trace(
+                    sm.getString("dataSourceRealm.authenticateFailure",
+                                 username));
+            return (null);
+        }
+
+        ArrayList<String> list = getRoles(dbConnection, username);
+
+        // Create and return a suitable Principal for this user
+        return (new GenericPrincipal(username, credentials, list));
+
+    }
+
+
+    /**
+     * Close the specified database connection.
+     *
+     * @param dbConnection The connection to be closed
+     */
+    protected void close(Connection dbConnection) {
+
+        // Do nothing if the database connection is already closed
+        if (dbConnection == null)
+            return;
+
+        // Commit if not auto committed
+        try {
+            if (!dbConnection.getAutoCommit()) {
+                dbConnection.commit();
+            }            
+        } catch (SQLException e) {
+            containerLog.error("Exception committing connection before closing:", e);
+        }
+
+        // Close this database connection, and log any errors
+        try {
+            dbConnection.close();
+        } catch (SQLException e) {
+            containerLog.error(sm.getString("dataSourceRealm.close"), e); // Just log it here
+        }
+
+    }
+
+    /**
+     * Open the specified database connection.
+     *
+     * @return Connection to the database
+     */
+    protected Connection open() {
+
+        try {
+            Context context = null;
+            if (localDataSource) {
+                context = ContextBindings.getClassLoader();
+                context = (Context) context.lookup("comp/env");
+            } else {
+                context = getServer().getGlobalNamingContext();
+            }
+            DataSource dataSource = (DataSource)context.lookup(dataSourceName);
+        return dataSource.getConnection();
+        } catch (Exception e) {
+            // Log the problem for posterity
+            containerLog.error(sm.getString("dataSourceRealm.exception"), e);
+        }  
+        return null;
+    }
+
+    /**
+     * Return a short name for this Realm implementation.
+     */
+    @Override
+    protected String getName() {
+
+        return (name);
+
+    }
+
+    /**
+     * Return the password associated with the given principal's user name.
+     */
+    @Override
+    protected String getPassword(String username) {
+
+        Connection dbConnection = null;
+
+        // Ensure that we have an open database connection
+        dbConnection = open();
+        if (dbConnection == null) {
+            return null;
+        }
+
+        try {
+            return getPassword(dbConnection, username);            
+        } finally {
+            close(dbConnection);
+        }
+    }
+    
+    /**
+     * Return the password associated with the given principal's user name.
+     * @param dbConnection The database connection to be used
+     * @param username Username for which password should be retrieved
+     */
+    protected String getPassword(Connection dbConnection, 
+                                 String username) {
+
+        ResultSet rs = null;
+        PreparedStatement stmt = null;
+        String dbCredentials = null;
+
+        try {
+            stmt = credentials(dbConnection, username);
+            rs = stmt.executeQuery();
+            if (rs.next()) {
+                dbCredentials = rs.getString(1);
+            }
+
+            return (dbCredentials != null) ? dbCredentials.trim() : null;
+            
+        } catch(SQLException e) {
+            containerLog.error(
+                    sm.getString("dataSourceRealm.getPassword.exception",
+                                 username));
+        } finally {
+            try {
+                if (rs != null) {
+                    rs.close();
+                }
+                if (stmt != null) {
+                    stmt.close();
+                }
+            } catch (SQLException e) {
+                    containerLog.error(
+                        sm.getString("dataSourceRealm.getPassword.exception",
+                             username));
+                
+            }
+        }
+        
+        return null;
+    }
+
+
+    /**
+     * Return the Principal associated with the given user name.
+     */
+    @Override
+    protected Principal getPrincipal(String username) {
+        Connection dbConnection = open();
+        if (dbConnection == null) {
+            return new GenericPrincipal(username, null, null);
+        }
+        try {
+            return (new GenericPrincipal(username,
+                    getPassword(dbConnection, username),
+                    getRoles(dbConnection, username)));
+        } finally {
+            close(dbConnection);
+        }
+
+    }
+
+    /**
+     * Return the roles associated with the given user name.
+     * @param username Username for which roles should be retrieved
+     */
+    protected ArrayList<String> getRoles(String username) {
+
+        Connection dbConnection = null;
+
+        // Ensure that we have an open database connection
+        dbConnection = open();
+        if (dbConnection == null) {
+            return null;
+        }
+
+        try {
+            return getRoles(dbConnection, username);
+        } finally {
+            close(dbConnection);
+        }
+    }
+    
+    /**
+     * Return the roles associated with the given user name
+     * @param dbConnection The database connection to be used
+     * @param username Username for which roles should be retrieved
+     */
+    protected ArrayList<String> getRoles(Connection dbConnection,
+                                     String username) {
+        
+        ResultSet rs = null;
+        PreparedStatement stmt = null;
+        ArrayList<String> list = null;
+        
+        try {
+            stmt = roles(dbConnection, username);
+            rs = stmt.executeQuery();
+            list = new ArrayList<String>();
+            
+            while (rs.next()) {
+                String role = rs.getString(1);
+                if (role != null) {
+                    list.add(role.trim());
+                }
+            }
+            return list;
+        } catch(SQLException e) {
+            containerLog.error(
+                sm.getString("dataSourceRealm.getRoles.exception", username));
+        }
+        finally {
+            try {
+                if (rs != null) {
+                    rs.close();
+                }
+                if (stmt != null) {
+                    stmt.close();
+                }
+            } catch (SQLException e) {
+                    containerLog.error(
+                        sm.getString("dataSourceRealm.getRoles.exception",
+                                     username));
+            }
+        }
+        
+        return null;
+    }
+
+    /**
+     * Return a PreparedStatement configured to perform the SELECT required
+     * to retrieve user credentials for the specified username.
+     *
+     * @param dbConnection The database connection to be used
+     * @param username Username for which credentials should be retrieved
+     *
+     * @exception SQLException if a database error occurs
+     */
+    private PreparedStatement credentials(Connection dbConnection,
+                                            String username)
+        throws SQLException {
+
+        PreparedStatement credentials =
+            dbConnection.prepareStatement(preparedCredentials);
+
+        credentials.setString(1, username);
+        return (credentials);
+
+    }
+    
+    /**
+     * Return a PreparedStatement configured to perform the SELECT required
+     * to retrieve user roles for the specified username.
+     *
+     * @param dbConnection The database connection to be used
+     * @param username Username for which roles should be retrieved
+     *
+     * @exception SQLException if a database error occurs
+     */
+    private PreparedStatement roles(Connection dbConnection, String username)
+        throws SQLException {
+
+        PreparedStatement roles = 
+            dbConnection.prepareStatement(preparedRoles);
+
+        roles.setString(1, username);
+        return (roles);
+
+    }
+
+    // ------------------------------------------------------ Lifecycle Methods
+
+
+    /**
+     * Prepare for the beginning of active use of the public methods of 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 void startInternal() throws LifecycleException {
+
+        // Create the roles PreparedStatement string
+        StringBuilder temp = new StringBuilder("SELECT ");
+        temp.append(roleNameCol);
+        temp.append(" FROM ");
+        temp.append(userRoleTable);
+        temp.append(" WHERE ");
+        temp.append(userNameCol);
+        temp.append(" = ?");
+        preparedRoles = temp.toString();
+
+        // Create the credentials PreparedStatement string
+        temp = new StringBuilder("SELECT ");
+        temp.append(userCredCol);
+        temp.append(" FROM ");
+        temp.append(userTable);
+        temp.append(" WHERE ");
+        temp.append(userNameCol);
+        temp.append(" = ?");
+        preparedCredentials = temp.toString();
+        
+        super.startInternal();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/realm/GenericPrincipal.java b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/GenericPrincipal.java
new file mode 100644
index 0000000..7584024
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/GenericPrincipal.java
@@ -0,0 +1,262 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.realm;
+
+
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.security.auth.login.LoginContext;
+
+import org.ietf.jgss.GSSCredential;
+
+
+/**
+ * Generic implementation of <strong>java.security.Principal</strong> that
+ * is available for use by <code>Realm</code> implementations.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: GenericPrincipal.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ */
+
+public class GenericPrincipal implements Principal {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new Principal, associated with the specified Realm, for the
+     * specified username and password.
+     *
+     * @param name The username of the user represented by this Principal
+     * @param password Credentials used to authenticate this user
+     */
+    public GenericPrincipal(String name, String password) {
+
+        this(name, password, null);
+
+    }
+
+
+    /**
+     * Construct a new Principal, associated with the specified Realm, for the
+     * specified username and password, with the specified role names
+     * (as Strings).
+     *
+     * @param name The username of the user represented by this Principal
+     * @param password Credentials used to authenticate this user
+     * @param roles List of roles (must be Strings) possessed by this user
+     */
+    public GenericPrincipal(String name, String password, List<String> roles) {
+        this(name, password, roles, null);
+    }
+
+    /**
+     * Construct a new Principal, associated with the specified Realm, for the
+     * specified username and password, with the specified role names
+     * (as Strings).
+     *
+     * @param name The username of the user represented by this Principal
+     * @param password Credentials used to authenticate this user
+     * @param roles List of roles (must be Strings) possessed by this user
+     * @param userPrincipal - the principal to be returned from the request 
+     *        getUserPrincipal call if not null; if null, this will be returned
+     */
+    public GenericPrincipal(String name, String password, List<String> roles,
+            Principal userPrincipal) {
+        this(name, password, roles, userPrincipal, null);
+    }
+    
+    /**
+     * Construct a new Principal, associated with the specified Realm, for the
+     * specified username and password, with the specified role names
+     * (as Strings).
+     *
+     * @param name The username of the user represented by this Principal
+     * @param password Credentials used to authenticate this user
+     * @param roles List of roles (must be Strings) possessed by this user
+     * @param userPrincipal - the principal to be returned from the request 
+     *        getUserPrincipal call if not null; if null, this will be returned
+     * @param loginContext  - If provided, this will be used to log out the user
+     *        at the appropriate time
+     */
+    public GenericPrincipal(String name, String password, List<String> roles,
+            Principal userPrincipal, LoginContext loginContext) {
+        this(name, password, roles, userPrincipal, loginContext, null);
+    }
+    
+    /**
+     * Construct a new Principal, associated with the specified Realm, for the
+     * specified username and password, with the specified role names
+     * (as Strings).
+     *
+     * @param name The username of the user represented by this Principal
+     * @param password Credentials used to authenticate this user
+     * @param roles List of roles (must be Strings) possessed by this user
+     * @param userPrincipal - the principal to be returned from the request 
+     *        getUserPrincipal call if not null; if null, this will be returned
+     * @param loginContext  - If provided, this will be used to log out the user
+     *        at the appropriate time
+     * @param gssCredential - If provided, the user&apos;s delegated credentials
+     */
+    public GenericPrincipal(String name, String password, List<String> roles,
+            Principal userPrincipal, LoginContext loginContext,
+            GSSCredential gssCredential) {
+        super();
+        this.name = name;
+        this.password = password;
+        this.userPrincipal = userPrincipal;
+        if (roles != null) {
+            this.roles = new String[roles.size()];
+            this.roles = roles.toArray(this.roles);
+            if (this.roles.length > 0)
+                Arrays.sort(this.roles);
+        }
+        this.loginContext = loginContext;
+        this.gssCredential = gssCredential;
+    }
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * The username of the user represented by this Principal.
+     */
+    protected String name = null;
+
+    @Override
+    public String getName() {
+        return (this.name);
+    }
+
+
+    /**
+     * The authentication credentials for the user represented by
+     * this Principal.
+     */
+    protected String password = null;
+
+    public String getPassword() {
+        return (this.password);
+    }
+
+
+    /**
+     * The set of roles associated with this user.
+     */
+    protected String roles[] = new String[0];
+
+    public String[] getRoles() {
+        return (this.roles);
+    }
+
+
+    /**
+     * The authenticated Principal to be exposed to applications.
+     */
+    protected Principal userPrincipal = null;
+
+    public Principal getUserPrincipal() {
+        if (userPrincipal != null) {
+            return userPrincipal;
+        } else {
+            return this;
+        }
+    }
+
+    
+    /**
+     * The JAAS LoginContext, if any, used to authenticate this Principal.
+     * Kept so we can call logout().
+     */
+    protected LoginContext loginContext = null;
+
+
+    /**
+     * The user&apos;s delegated credentials.
+     */
+    protected GSSCredential gssCredential = null;
+
+    public GSSCredential getGssCredential() {
+        return this.gssCredential;
+    }
+    protected void setGssCredential(GSSCredential gssCredential) {
+        this.gssCredential = gssCredential;
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Does the user represented by this Principal possess the specified role?
+     *
+     * @param role Role to be tested
+     */
+    public boolean hasRole(String role) {
+
+        if("*".equals(role)) // Special 2.4 role meaning everyone
+            return true;
+        if (role == null)
+            return (false);
+        return (Arrays.binarySearch(roles, role) >= 0);
+
+    }
+
+
+    /**
+     * Return a String representation of this object, which exposes only
+     * information that should be public.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("GenericPrincipal[");
+        sb.append(this.name);
+        sb.append("(");
+        for( int i=0;i<roles.length; i++ ) {
+            sb.append( roles[i]).append(",");
+        }
+        sb.append(")]");
+        return (sb.toString());
+
+    }
+
+    
+    /**
+     * Calls logout, if necessary, on any associated JAASLoginContext. May in
+     * the future be extended to cover other logout requirements.
+     * 
+     * @throws Exception If something goes wrong with the logout. Uses Exception
+     *                   to allow for future expansion of this method to cover
+     *                   other logout mechanisms that might throw a different
+     *                   exception to LoginContext
+     * 
+     */
+    public void logout() throws Exception {
+        if (loginContext != null) {
+            loginContext.logout();
+        }
+    }
+
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/realm/JAASCallbackHandler.java b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/JAASCallbackHandler.java
new file mode 100644
index 0000000..08970c5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/JAASCallbackHandler.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.realm;
+
+
+import java.io.IOException;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.TextInputCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * <p>Implementation of the JAAS <code>CallbackHandler</code> interface,
+ * used to negotiate delivery of the username and credentials that were
+ * specified to our constructor.  No interaction with the user is required
+ * (or possible).</p>
+ *
+ * <p>This <code>CallbackHandler</code> will pre-digest the supplied
+ * password, if required by the <code>&lt;Realm&gt;</code> element in 
+ * <code>server.xml</code>.</p>
+ * <p>At present, <code>JAASCallbackHandler</code> knows how to handle callbacks of
+ * type <code>javax.security.auth.callback.NameCallback</code> and
+ * <code>javax.security.auth.callback.PasswordCallback</code>.</p>
+ *
+ * @author Craig R. McClanahan
+ * @author Andrew R. Jaquith
+ * @version $Id: JAASCallbackHandler.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ */
+
+public class JAASCallbackHandler implements CallbackHandler {
+
+    // ------------------------------------------------------------ Constructor
+
+
+    /**
+     * Construct a callback handler configured with the specified values.
+     * Note that if the <code>JAASRealm</code> instance specifies digested passwords,
+     * the <code>password</code> parameter will be pre-digested here.
+     *
+     * @param realm Our associated JAASRealm instance
+     * @param username Username to be authenticated with
+     * @param password Password to be authenticated with
+     */
+    public JAASCallbackHandler(JAASRealm realm, String username,
+                               String password) {
+
+        super();
+        this.realm = realm;
+        this.username = username;
+
+        if (realm.hasMessageDigest()) {
+            this.password = realm.digest(password);
+        }
+        else {
+            this.password = password;
+        }
+    }
+
+    
+    /**
+     * Construct a callback handler for DIGEST authentication.
+     *
+     * @param realm         Our associated JAASRealm instance
+     * @param username      Username to be authenticated with
+     * @param password      Password to be authenticated with
+     * @param nonce         Server generated nonce
+     * @param nc            Nonce count
+     * @param cnonce        Client generated nonce
+     * @param qop           Quality of protection applied to the message
+     * @param realmName     Realm name
+     * @param md5a2         Second MD5 digest used to calculate the digest
+     *                      MD5(Method + ":" + uri)
+     * @param authMethod    The authentication method in use 
+     */
+    public JAASCallbackHandler(JAASRealm realm, String username,
+                               String password, String nonce, String nc,
+                               String cnonce, String qop, String realmName,
+                               String md5a2, String authMethod) {
+        this(realm, username, password);
+        this.nonce = nonce;
+        this.nc = nc;
+        this.cnonce = cnonce;
+        this.qop = qop;
+        this.realmName = realmName;
+        this.md5a2 = md5a2;
+        this.authMethod = authMethod;
+    }
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    /**
+     * The password to be authenticated with.
+     */
+    protected String password = null;
+
+
+    /**
+     * The associated <code>JAASRealm</code> instance.
+     */
+    protected JAASRealm realm = null;
+
+    /**
+     * The username to be authenticated with.
+     */
+    protected String username = null;
+
+    /**
+     * Server generated nonce.
+     */
+    protected String nonce = null;
+    
+    /**
+     * Nonce count.
+     */
+    protected String nc = null;
+    
+    /**
+     * Client generated nonce.
+     */
+    protected String cnonce = null;
+
+    /**
+     * Quality of protection applied to the message.
+     */
+    protected String qop;
+
+    /**
+     * Realm name.
+     */
+    protected String realmName;
+
+    /**
+     * Second MD5 digest.
+     */
+    protected String md5a2;
+
+    /**
+     * The authentication method to be used. If null, assume BASIC/FORM.
+     */
+    protected String authMethod;
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Retrieve the information requested in the provided <code>Callbacks</code>.
+     * This implementation only recognizes {@link NameCallback},
+     * {@link PasswordCallback} and {@link TextInputCallback}.
+     * {@link TextInputCallback} is used to pass the various additional
+     * parameters required for DIGEST authentication. 
+     *
+     * @param callbacks The set of <code>Callback</code>s to be processed
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception UnsupportedCallbackException if the login method requests
+     *  an unsupported callback type
+     */
+    @Override
+    public void handle(Callback callbacks[])
+        throws IOException, UnsupportedCallbackException {
+
+        for (int i = 0; i < callbacks.length; i++) {
+
+            if (callbacks[i] instanceof NameCallback) {
+                if (realm.getContainer().getLogger().isTraceEnabled())
+                    realm.getContainer().getLogger().trace(sm.getString("jaasCallback.username", username));
+                ((NameCallback) callbacks[i]).setName(username);
+            } else if (callbacks[i] instanceof PasswordCallback) {
+                final char[] passwordcontents;
+                if (password != null) {
+                    passwordcontents = password.toCharArray();
+                } else {
+                    passwordcontents = new char[0];
+                }
+                ((PasswordCallback) callbacks[i]).setPassword
+                    (passwordcontents);
+            } else if (callbacks[i] instanceof TextInputCallback) {
+                TextInputCallback cb = ((TextInputCallback) callbacks[i]);
+                if (cb.getPrompt().equals("nonce")) {
+                    cb.setText(nonce);
+                } else if (cb.getPrompt().equals("nc")) {
+                    cb.setText(nc);
+                } else if (cb.getPrompt().equals("cnonce")) {
+                    cb.setText(cnonce);
+                } else if (cb.getPrompt().equals("qop")) {
+                    cb.setText(qop);
+                } else if (cb.getPrompt().equals("realmName")) {
+                    cb.setText(realmName);
+                } else if (cb.getPrompt().equals("md5a2")) {
+                    cb.setText(md5a2);
+                } else if (cb.getPrompt().equals("authMethod")) {
+                    cb.setText(authMethod);
+                } else {
+                    throw new UnsupportedCallbackException(callbacks[i]);
+                }
+            } else {
+                throw new UnsupportedCallbackException(callbacks[i]);
+            }
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/realm/JAASMemoryLoginModule.java b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/JAASMemoryLoginModule.java
new file mode 100644
index 0000000..bb7d59e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/JAASMemoryLoginModule.java
@@ -0,0 +1,370 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.realm;
+
+
+import java.io.File;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.TextInputCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.FailedLoginException;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+
+import org.apache.catalina.Globals;
+import org.apache.catalina.authenticator.Constants;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.digester.Digester;
+
+
+/**
+ * <p>Implementation of the JAAS <strong>LoginModule</strong> interface,
+ * primarily for use in testing <code>JAASRealm</code>.  It utilizes an
+ * XML-format data file of username/password/role information identical to
+ * that supported by <code>org.apache.catalina.realm.MemoryRealm</code>
+ * (except that digested passwords are not supported).</p>
+ *
+ * <p>This class recognizes the following string-valued options, which are
+ * specified in the configuration file (and passed to our constructor in
+ * the <code>options</code> argument:</p>
+ * <ul>
+ * <li><strong>debug</strong> - Set to "true" to get debugging messages
+ *     generated to System.out.  The default value is <code>false</code>.</li>
+ * <li><strong>pathname</strong> - Relative (to the pathname specified by the
+ *     "catalina.base" system property) or absolute pathname to the
+ *     XML file containing our user information, in the format supported by
+ *     {@link MemoryRealm}.  The default value matches the MemoryRealm
+ *     default.</li>
+ * </ul>
+ *
+ * <p><strong>IMPLEMENTATION NOTE</strong> - This class implements
+ * <code>Realm</code> only to satisfy the calling requirements of the
+ * <code>GenericPrincipal</code> constructor.  It does not actually perform
+ * the functionality required of a <code>Realm</code> implementation.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: JAASMemoryLoginModule.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ */
+
+public class JAASMemoryLoginModule extends MemoryRealm implements LoginModule {
+    // We need to extend MemoryRealm to avoid class cast
+
+    private static final Log log = LogFactory.getLog(JAASMemoryLoginModule.class);
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The callback handler responsible for answering our requests.
+     */
+    protected CallbackHandler callbackHandler = null;
+
+
+    /**
+     * Has our own <code>commit()</code> returned successfully?
+     */
+    protected boolean committed = false;
+
+
+    /**
+     * The configuration information for this <code>LoginModule</code>.
+     */
+    protected Map<String,?> options = null;
+
+
+    /**
+     * The absolute or relative pathname to the XML configuration file.
+     */
+    protected String pathname = "conf/tomcat-users.xml";
+
+
+    /**
+     * The <code>Principal</code> identified by our validation, or
+     * <code>null</code> if validation failed.
+     */
+    protected Principal principal = null;
+
+
+    /**
+     * The state information that is shared with other configured
+     * <code>LoginModule</code> instances.
+     */
+    protected Map<String,?> sharedState = null;
+
+
+    /**
+     * The subject for which we are performing authentication.
+     */
+    protected Subject subject = null;
+
+
+    // --------------------------------------------------------- Public Methods
+
+    public JAASMemoryLoginModule() {
+        log.debug("MEMORY LOGIN MODULE");
+    }
+
+    /**
+     * Phase 2 of authenticating a <code>Subject</code> when Phase 1
+     * fails.  This method is called if the <code>LoginContext</code>
+     * failed somewhere in the overall authentication chain.
+     *
+     * @return <code>true</code> if this method succeeded, or
+     *  <code>false</code> if this <code>LoginModule</code> should be
+     *  ignored
+     *
+     * @exception LoginException if the abort fails
+     */
+    @Override
+    public boolean abort() throws LoginException {
+
+        // If our authentication was not successful, just return false
+        if (principal == null)
+            return (false);
+
+        // Clean up if overall authentication failed
+        if (committed)
+            logout();
+        else {
+            committed = false;
+            principal = null;
+        }
+        log.debug("Abort");
+        return (true);
+
+    }
+
+
+    /**
+     * Phase 2 of authenticating a <code>Subject</code> when Phase 1
+     * was successful.  This method is called if the <code>LoginContext</code>
+     * succeeded in the overall authentication chain.
+     *
+     * @return <code>true</code> if the authentication succeeded, or
+     *  <code>false</code> if this <code>LoginModule</code> should be
+     *  ignored
+     *
+     * @exception LoginException if the commit fails
+     */
+    @Override
+    public boolean commit() throws LoginException {
+        log.debug("commit " + principal);
+
+        // If authentication was not successful, just return false
+        if (principal == null)
+            return (false);
+
+        // Add our Principal to the Subject if needed
+        if (!subject.getPrincipals().contains(principal)) {
+            subject.getPrincipals().add(principal);
+            // Add the roles as additional subjects as per the contract with the
+            // JAASRealm
+            if (principal instanceof GenericPrincipal) {
+                String roles[] = ((GenericPrincipal) principal).getRoles();
+                for (int i = 0; i < roles.length; i++) {
+                    subject.getPrincipals().add(
+                            new GenericPrincipal(null, roles[i], null));
+                }
+                
+            }
+        }
+
+        committed = true;
+        return (true);
+
+    }
+
+    
+    /**
+     * Initialize this <code>LoginModule</code> with the specified
+     * configuration information.
+     *
+     * @param subject The <code>Subject</code> to be authenticated
+     * @param callbackHandler A <code>CallbackHandler</code> for communicating
+     *  with the end user as necessary
+     * @param sharedState State information shared with other
+     *  <code>LoginModule</code> instances
+     * @param options Configuration information for this specific
+     *  <code>LoginModule</code> instance
+     */
+    @Override
+    public void initialize(Subject subject, CallbackHandler callbackHandler,
+                           Map<String,?> sharedState, Map<String,?> options) {
+        log.debug("Init");
+
+        // Save configuration values
+        this.subject = subject;
+        this.callbackHandler = callbackHandler;
+        this.sharedState = sharedState;
+        this.options = options;
+
+        // Perform instance-specific initialization
+        if (options.get("pathname") != null)
+            this.pathname = (String) options.get("pathname");
+
+        // Load our defined Principals
+        load();
+
+    }
+
+
+    /**
+     * Phase 1 of authenticating a <code>Subject</code>.
+     *
+     * @return <code>true</code> if the authentication succeeded, or
+     *  <code>false</code> if this <code>LoginModule</code> should be
+     *  ignored
+     *
+     * @exception LoginException if the authentication fails
+     */
+    @Override
+    public boolean login() throws LoginException {
+
+        // Set up our CallbackHandler requests
+        if (callbackHandler == null)
+            throw new LoginException("No CallbackHandler specified");
+        Callback callbacks[] = new Callback[9];
+        callbacks[0] = new NameCallback("Username: ");
+        callbacks[1] = new PasswordCallback("Password: ", false);
+        callbacks[2] = new TextInputCallback("nonce");
+        callbacks[3] = new TextInputCallback("nc");
+        callbacks[4] = new TextInputCallback("cnonce");
+        callbacks[5] = new TextInputCallback("qop");
+        callbacks[6] = new TextInputCallback("realmName");
+        callbacks[7] = new TextInputCallback("md5a2");
+        callbacks[8] = new TextInputCallback("authMethod");
+
+        // Interact with the user to retrieve the username and password
+        String username = null;
+        String password = null;
+        String nonce = null;
+        String nc = null;
+        String cnonce = null;
+        String qop = null;
+        String realmName = null;
+        String md5a2 = null;
+        String authMethod = null;
+
+        try {
+            callbackHandler.handle(callbacks);
+            username = ((NameCallback) callbacks[0]).getName();
+            password =
+                new String(((PasswordCallback) callbacks[1]).getPassword());
+            nonce = ((TextInputCallback) callbacks[2]).getText();
+            nc = ((TextInputCallback) callbacks[3]).getText();
+            cnonce = ((TextInputCallback) callbacks[4]).getText();
+            qop = ((TextInputCallback) callbacks[5]).getText();
+            realmName = ((TextInputCallback) callbacks[6]).getText();
+            md5a2 = ((TextInputCallback) callbacks[7]).getText();
+            authMethod = ((TextInputCallback) callbacks[8]).getText();
+        } catch (IOException e) {
+            throw new LoginException(e.toString());
+        } catch (UnsupportedCallbackException e) {
+            throw new LoginException(e.toString());
+        }
+
+        // Validate the username and password we have received
+        if (authMethod == null) {
+            // BASIC or FORM
+            principal = super.authenticate(username, password);
+        } else if (authMethod.equals(Constants.DIGEST_METHOD)) {
+            principal = super.authenticate(username, password, nonce, nc,
+                    cnonce, qop, realmName, md5a2);
+        } else if (authMethod.equals(Constants.CERT_METHOD)) {
+            principal = super.getPrincipal(username);
+        } else {
+            throw new LoginException("Unknown authentication method");
+        }
+
+        log.debug("login " + username + " " + principal);
+
+        // Report results based on success or failure
+        if (principal != null) {
+            return (true);
+        } else {
+            throw new
+                FailedLoginException("Username or password is incorrect");
+        }
+
+    }
+
+
+    /**
+     * Log out this user.
+     *
+     * @return <code>true</code> in all cases because the
+     *  <code>LoginModule</code> should not be ignored
+     *
+     * @exception LoginException if logging out failed
+     */
+    @Override
+    public boolean logout() throws LoginException {
+
+        subject.getPrincipals().remove(principal);
+        committed = false;
+        principal = null;
+        return (true);
+
+    }
+
+
+    // ---------------------------------------------------------- Realm Methods
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Load the contents of our configuration file.
+     */
+    protected void load() {
+
+        // Validate the existence of our configuration file
+        File file = new File(pathname);
+        if (!file.isAbsolute())
+            file = new File(System.getProperty(Globals.CATALINA_BASE_PROP), pathname);
+        if (!file.exists() || !file.canRead()) {
+            log.warn("Cannot load configuration file " + file.getAbsolutePath());
+            return;
+        }
+
+        // Load the contents of our configuration file
+        Digester digester = new Digester();
+        digester.setValidating(false);
+        digester.addRuleSet(new MemoryRuleSet());
+        try {
+            digester.push(this);
+            digester.parse(file);
+        } catch (Exception e) {
+            log.warn("Error processing configuration file " +
+                file.getAbsolutePath(), e);
+            return;
+        } finally {
+            digester.reset();
+        }
+
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/realm/JAASRealm.java b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/JAASRealm.java
new file mode 100644
index 0000000..c7ff259
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/JAASRealm.java
@@ -0,0 +1,610 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.realm;
+
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.AccountExpiredException;
+import javax.security.auth.login.CredentialExpiredException;
+import javax.security.auth.login.FailedLoginException;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.authenticator.Constants;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+
+
+/**
+ * <p>Implementation of <b>Realm</b> that authenticates users via the <em>Java
+ * Authentication and Authorization Service</em> (JAAS).  JAAS support requires
+ * either JDK 1.4 (which includes it as part of the standard platform) or
+ * JDK 1.3 (with the plug-in <code>jaas.jar</code> file).</p>
+ *
+ * <p>The value configured for the <code>appName</code> property is passed to
+ * the <code>javax.security.auth.login.LoginContext</code> constructor, to
+ * specify the <em>application name</em> used to select the set of relevant
+ * <code>LoginModules</code> required.</p>
+ *
+ * <p>The JAAS Specification describes the result of a successful login as a
+ * <code>javax.security.auth.Subject</code> instance, which can contain zero
+ * or more <code>java.security.Principal</code> objects in the return value
+ * of the <code>Subject.getPrincipals()</code> method.  However, it provides
+ * no guidance on how to distinguish Principals that describe the individual
+ * user (and are thus appropriate to return as the value of
+ * request.getUserPrincipal() in a web application) from the Principal(s)
+ * that describe the authorized roles for this user.  To maintain as much
+ * independence as possible from the underlying <code>LoginMethod</code>
+ * implementation executed by JAAS, the following policy is implemented by
+ * this Realm:</p>
+ * <ul>
+ * <li>The JAAS <code>LoginModule</code> is assumed to return a
+ *     <code>Subject</code> with at least one <code>Principal</code> instance
+ *     representing the user himself or herself, and zero or more separate
+ *     <code>Principals</code> representing the security roles authorized
+ *     for this user.</li>
+ * <li>On the <code>Principal</code> representing the user, the Principal
+ *     name is an appropriate value to return via the Servlet API method
+ *     <code>HttpServletRequest.getRemoteUser()</code>.</li>
+ * <li>On the <code>Principals</code> representing the security roles, the
+ *     name is the name of the authorized security role.</li>
+ * <li>This Realm will be configured with two lists of fully qualified Java
+ *     class names of classes that implement
+ *     <code>java.security.Principal</code> - one that identifies class(es)
+ *     representing a user, and one that identifies class(es) representing
+ *     a security role.</li>
+ * <li>As this Realm iterates over the <code>Principals</code> returned by
+ *     <code>Subject.getPrincipals()</code>, it will identify the first
+ *     <code>Principal</code> that matches the "user classes" list as the
+ *     <code>Principal</code> for this user.</li>
+ * <li>As this Realm iterates over the <code>Principals</code> returned by
+ *     <code>Subject.getPrincipals()</code>, it will accumulate the set of
+ *     all <code>Principals</code> matching the "role classes" list as
+ *     identifying the security roles for this user.</li>
+ * <li>It is a configuration error for the JAAS login method to return a
+ *     validated <code>Subject</code> without a <code>Principal</code> that
+ *     matches the "user classes" list.</li>
+ * <li>By default, the enclosing Container's name serves as the
+ *     application name used to obtain the JAAS LoginContext ("Catalina" in
+ *     a default installation). Tomcat must be able to find an application
+ *     with this name in the JAAS configuration file. Here is a hypothetical
+ *     JAAS configuration file entry for a database-oriented login module that uses 
+ *     a Tomcat-managed JNDI database resource:
+ *     <blockquote><pre>Catalina {
+org.foobar.auth.DatabaseLoginModule REQUIRED
+    JNDI_RESOURCE=jdbc/AuthDB
+  USER_TABLE=users
+  USER_ID_COLUMN=id
+  USER_NAME_COLUMN=name
+  USER_CREDENTIAL_COLUMN=password
+  ROLE_TABLE=roles
+  ROLE_NAME_COLUMN=name
+  PRINCIPAL_FACTORY=org.foobar.auth.impl.SimplePrincipalFactory;
+};</pre></blockquote></li>
+ * <li>To set the JAAS configuration file
+ *     location, set the <code>CATALINA_OPTS</code> environment variable
+ *     similar to the following:
+<blockquote><code>CATALINA_OPTS="-Djava.security.auth.login.config=$CATALINA_HOME/conf/jaas.config"</code></blockquote>
+ * </li>
+ * <li>As part of the login process, JAASRealm registers its own <code>CallbackHandler</code>,
+ *     called (unsurprisingly) <code>JAASCallbackHandler</code>. This handler supplies the 
+ *     HTTP requests's username and credentials to the user-supplied <code>LoginModule</code></li>
+ * <li>As with other <code>Realm</code> implementations, digested passwords are supported if
+ *     the <code>&lt;Realm&gt;</code> element in <code>server.xml</code> contains a 
+ *     <code>digest</code> attribute; <code>JAASCallbackHandler</code> will digest the password
+ *     prior to passing it back to the <code>LoginModule</code></li>  
+* </ul>
+*
+* @author Craig R. McClanahan
+* @author Yoav Shapira
+ * @version $Id: JAASRealm.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ */
+
+public class JAASRealm
+    extends RealmBase
+ {
+    private static final Log log = LogFactory.getLog(JAASRealm.class);
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The application name passed to the JAAS <code>LoginContext</code>,
+     * which uses it to select the set of relevant <code>LoginModule</code>s.
+     */
+    protected String appName = null;
+
+
+    /**
+     * Descriptive information about this <code>Realm</code> implementation.
+     */
+    protected static final String info =
+        "org.apache.catalina.realm.JAASRealm/1.0";
+
+
+    /**
+     * Descriptive information about this <code>Realm</code> implementation.
+     */
+    protected static final String name = "JAASRealm";
+
+
+    /**
+     * The list of role class names, split out for easy processing.
+     */
+    protected List<String> roleClasses = new ArrayList<String>();
+
+
+    /**
+     * The set of user class names, split out for easy processing.
+     */
+    protected List<String> userClasses = new ArrayList<String>();
+
+
+    /**
+     * Whether to use context ClassLoader or default ClassLoader.
+     * True means use context ClassLoader, and True is the default
+     * value.
+     */
+     protected boolean useContextClassLoader = true;
+
+
+    // ------------------------------------------------------------- Properties
+
+    
+    /**
+     * setter for the <code>appName</code> member variable
+     * @deprecated JAAS should use the <code>Engine</code> (domain) name and webpp/host overrides
+     */
+    @Deprecated
+    public void setAppName(String name) {
+        appName = name;
+    }
+    
+    /**
+     * getter for the <code>appName</code> member variable
+     */
+    public String getAppName() {
+        return appName;
+    }
+
+    /**
+     * Sets whether to use the context or default ClassLoader.
+     * True means use context ClassLoader.
+     *
+     * @param useContext True means use context ClassLoader
+     */
+    public void setUseContextClassLoader(boolean useContext) {
+      useContextClassLoader = useContext;
+      log.info("Setting useContextClassLoader = " + useContext);
+    }
+
+    /**
+     * Returns whether to use the context or default ClassLoader.
+     * True means to use the context ClassLoader.
+     *
+     * @return The value of useContextClassLoader
+     */
+    public boolean isUseContextClassLoader() {
+        return useContextClassLoader;
+    } 
+
+    @Override
+    public void setContainer(Container container) {
+        super.setContainer(container);
+
+        if( appName==null  ) {
+            String name = container.getName();
+            if (!name.startsWith("/")) {
+                name = "/" + name;
+            }
+            name = makeLegalForJAAS(name);
+
+            appName=name;
+
+            log.info("Set JAAS app name " + appName);
+        }
+    }
+
+     /**
+      * Comma-delimited list of <code>java.security.Principal</code> classes
+      * that represent security roles.
+      */
+     protected String roleClassNames = null;
+     
+     public String getRoleClassNames() {
+         return (this.roleClassNames);
+     }
+     
+     /**
+      * Sets the list of comma-delimited classes that represent roles. The
+      * classes in the list must implement <code>java.security.Principal</code>.
+      * The supplied list of classes will be parsed when {@link #start()} is
+      * called.
+      */
+     public void setRoleClassNames(String roleClassNames) {
+         this.roleClassNames = roleClassNames;
+     }
+     
+     /**
+      * Parses a comma-delimited list of class names, and store the class names
+      * in the provided List. Each class must implement
+      * <code>java.security.Principal</code>.
+      * 
+      * @param classNamesString a comma-delimited list of fully qualified class names.
+      * @param classNamesList the list in which the class names will be stored.
+      *        The list is cleared before being populated. 
+      */
+     protected void parseClassNames(String classNamesString, List<String> classNamesList) {
+         classNamesList.clear();
+         if (classNamesString == null) return;
+
+         ClassLoader loader = this.getClass().getClassLoader();
+         if (isUseContextClassLoader())
+             loader = Thread.currentThread().getContextClassLoader();
+
+         String[] classNames = classNamesString.split("[ ]*,[ ]*");
+         for (int i=0; i<classNames.length; i++) {
+             if (classNames[i].length()==0) continue;        
+             try {
+                 Class<?> principalClass = Class.forName(classNames[i], false,
+                         loader);
+                 if (Principal.class.isAssignableFrom(principalClass)) {
+                     classNamesList.add(classNames[i]);
+                 } else {
+                     log.error("Class "+classNames[i]+" is not implementing "+
+                               "java.security.Principal! Class not added.");
+                 }
+             } catch (ClassNotFoundException e) {
+                 log.error("Class "+classNames[i]+" not found! Class not added.");
+             }
+         }
+     }     
+     
+     /**
+      * Comma-delimited list of <code>java.security.Principal</code> classes
+      * that represent individual users.
+      */
+     protected String userClassNames = null;
+     
+     public String getUserClassNames() {
+         return (this.userClassNames);
+     }
+     
+     /**
+      * Sets the list of comma-delimited classes that represent individual
+      * users. The classes in the list must implement
+      * <code>java.security.Principal</code>. The supplied list of classes will
+      * be parsed when {@link #start()} is called.
+      */
+    public void setUserClassNames(String userClassNames) {
+        this.userClassNames = userClassNames;
+    }
+
+    /**
+     * Return descriptive information about this Realm implementation and
+     * the corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    @Override
+    public String getInfo() {
+
+        return info;
+
+    }
+
+    
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return the <code>Principal</code> associated with the specified username
+     * and credentials, if there is one; otherwise return <code>null</code>.
+     *
+     * @param username Username of the <code>Principal</code> to look up
+     * @param credentials Password or other credentials to use in
+     *  authenticating this username
+     */
+    @Override
+    public Principal authenticate(String username, String credentials) {
+        return authenticate(username,
+                new JAASCallbackHandler(this, username, credentials));
+    }
+     
+
+    /**
+     * Return the <code>Principal</code> associated with the specified username
+     * and digest, if there is one; otherwise return <code>null</code>.
+     *
+     * @param username      Username of the <code>Principal</code> to look up
+     * @param clientDigest  Digest to use in authenticating this username
+     * @param nonce         Server generated nonce
+     * @param nc            Nonce count
+     * @param cnonce        Client generated nonce
+     * @param qop           Quality of protection applied to the message
+     * @param realmName     Realm name
+     * @param md5a2         Second MD5 digest used to calculate the digest
+     *                          MD5(Method + ":" + uri)
+     */
+    @Override
+    public Principal authenticate(String username, String clientDigest,
+            String nonce, String nc, String cnonce, String qop,
+            String realmName, String md5a2) {
+        return authenticate(username,
+                new JAASCallbackHandler(this, username, clientDigest, nonce,
+                        nc, cnonce, qop, realmName, md5a2,
+                        Constants.DIGEST_METHOD));
+    }
+
+
+    // -------------------------------------------------------- Package Methods
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Perform the actual JAAS authentication
+     */
+    protected Principal authenticate(String username,
+            CallbackHandler callbackHandler) {
+
+        // Establish a LoginContext to use for authentication
+        try {
+        LoginContext loginContext = null;
+        if( appName==null ) appName="Tomcat";
+
+        if( log.isDebugEnabled())
+            log.debug(sm.getString("jaasRealm.beginLogin", username, appName));
+
+        // What if the LoginModule is in the container class loader ?
+        ClassLoader ocl = null;
+
+        if (!isUseContextClassLoader()) {
+          ocl = Thread.currentThread().getContextClassLoader();
+          Thread.currentThread().setContextClassLoader(
+                  this.getClass().getClassLoader());
+        }
+
+        try {
+            loginContext = new LoginContext(appName, callbackHandler);
+        } catch (Throwable e) {
+            ExceptionUtils.handleThrowable(e);
+            log.error(sm.getString("jaasRealm.unexpectedError"), e);
+            return (null);
+        } finally {
+            if(!isUseContextClassLoader()) {
+              Thread.currentThread().setContextClassLoader(ocl);
+            }
+        }
+
+        if( log.isDebugEnabled())
+            log.debug("Login context created " + username);
+
+        // Negotiate a login via this LoginContext
+        Subject subject = null;
+        try {
+            loginContext.login();
+            subject = loginContext.getSubject();
+            if (subject == null) {
+                if( log.isDebugEnabled())
+                    log.debug(sm.getString("jaasRealm.failedLogin", username));
+                return (null);
+            }
+        } catch (AccountExpiredException e) {
+            if (log.isDebugEnabled())
+                log.debug(sm.getString("jaasRealm.accountExpired", username));
+            return (null);
+        } catch (CredentialExpiredException e) {
+            if (log.isDebugEnabled())
+                log.debug(sm.getString("jaasRealm.credentialExpired", username));
+            return (null);
+        } catch (FailedLoginException e) {
+            if (log.isDebugEnabled())
+                log.debug(sm.getString("jaasRealm.failedLogin", username));
+            return (null);
+        } catch (LoginException e) {
+            log.warn(sm.getString("jaasRealm.loginException", username), e);
+            return (null);
+        } catch (Throwable e) {
+            ExceptionUtils.handleThrowable(e);
+            log.error(sm.getString("jaasRealm.unexpectedError"), e);
+            return (null);
+        }
+
+        if( log.isDebugEnabled())
+            log.debug(sm.getString("jaasRealm.loginContextCreated", username));
+
+        // Return the appropriate Principal for this authenticated Subject
+        Principal principal = createPrincipal(username, subject, loginContext);
+        if (principal == null) {
+            log.debug(sm.getString("jaasRealm.authenticateFailure", username));
+            return (null);
+        }
+        if (log.isDebugEnabled()) {
+            log.debug(sm.getString("jaasRealm.authenticateSuccess", username));
+        }
+
+        return (principal);
+        } catch( Throwable t) {
+            log.error( "error ", t);
+            return null;
+        }
+    }
+
+    /**
+     * Return a short name for this <code>Realm</code> implementation.
+     */
+    @Override
+    protected String getName() {
+
+        return (name);
+
+    }
+
+
+    /**
+     * Return the password associated with the given principal's user name. This
+     * always returns null as the JAASRealm has no way of obtaining this
+     * information.
+     */
+    @Override
+    protected String getPassword(String username) {
+
+        return (null);
+
+    }
+
+
+    /**
+     * Return the <code>Principal</code> associated with the given user name.
+     */
+    @Override
+    protected Principal getPrincipal(String username) {
+
+        return authenticate(username,
+                new JAASCallbackHandler(this, username, null, null, null, null,
+                        null, null, null, Constants.CERT_METHOD));
+
+    }
+
+
+    /**
+     * Identify and return a <code>java.security.Principal</code> instance
+     * representing the authenticated user for the specified <code>Subject</code>.
+     * The Principal is constructed by scanning the list of Principals returned
+     * by the JAASLoginModule. The first <code>Principal</code> object that matches
+     * one of the class names supplied as a "user class" is the user Principal.
+     * This object is returned to the caller.
+     * Any remaining principal objects returned by the LoginModules are mapped to  
+     * roles, but only if their respective classes match one of the "role class" classes. 
+     * If a user Principal cannot be constructed, return <code>null</code>.
+     * @param subject The <code>Subject</code> representing the logged-in user
+     * @param loginContext Associated with the Principal so
+     *                     {@link LoginContext#logout()} can be called later
+     */
+    protected Principal createPrincipal(String username, Subject subject,
+            LoginContext loginContext) {
+        // Prepare to scan the Principals for this Subject
+
+        List<String> roles = new ArrayList<String>();
+        Principal userPrincipal = null;
+
+        // Scan the Principals for this Subject
+        Iterator<Principal> principals = subject.getPrincipals().iterator();
+        while (principals.hasNext()) {
+            Principal principal = principals.next();
+
+            String principalClass = principal.getClass().getName();
+
+            if( log.isDebugEnabled() ) {
+                log.debug(sm.getString("jaasRealm.checkPrincipal", principal, principalClass));
+            }
+
+            if (userPrincipal == null && userClasses.contains(principalClass)) {
+                userPrincipal = principal;
+                if( log.isDebugEnabled() ) {
+                    log.debug(sm.getString("jaasRealm.userPrincipalSuccess", principal.getName()));
+                }
+            }
+            
+            if (roleClasses.contains(principalClass)) {
+                roles.add(principal.getName());
+                if( log.isDebugEnabled() ) {
+                    log.debug(sm.getString("jaasRealm.rolePrincipalAdd", principal.getName()));
+                }
+            }
+        }
+
+        // Print failure message if needed
+        if (userPrincipal == null) {
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString("jaasRealm.userPrincipalFailure"));
+                log.debug(sm.getString("jaasRealm.rolePrincipalFailure"));
+            }
+        } else {
+            if (roles.size() == 0) {
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString("jaasRealm.rolePrincipalFailure"));
+                }
+            }
+        }
+
+        // Return the resulting Principal for our authenticated user
+        return new GenericPrincipal(username, null, roles, userPrincipal,
+                loginContext);
+    }
+
+     /**
+      * Ensure the given name is legal for JAAS configuration.
+      * Added for Bugzilla 30869, made protected for easy customization
+      * in case my implementation is insufficient, which I think is
+      * very likely.
+      *
+      * @param src The name to validate
+      * @return A string that's a valid JAAS realm name
+      */
+     protected String makeLegalForJAAS(final String src) {
+         String result = src;
+         
+         // Default name is "other" per JAAS spec
+         if(result == null) {
+             result = "other";
+         }
+
+         // Strip leading slash if present, as Sun JAAS impl
+         // barfs on it (see Bugzilla 30869 bug report).
+         if(result.startsWith("/")) {
+             result = result.substring(1);
+         }
+
+         return result;
+     }
+
+
+    // ------------------------------------------------------ Lifecycle Methods
+
+
+     /**
+      * Prepare for the beginning of active use of the public methods of 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 void startInternal() throws LifecycleException {
+
+        // These need to be called after loading configuration, in case
+        // useContextClassLoader appears after them in xml config
+        parseClassNames(userClassNames, userClasses);
+        parseClassNames(roleClassNames, roleClasses);
+
+        super.startInternal();
+     }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/realm/JDBCRealm.java b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/JDBCRealm.java
new file mode 100644
index 0000000..64f22ff
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/JDBCRealm.java
@@ -0,0 +1,810 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.realm;
+
+
+import java.security.Principal;
+import java.sql.Connection;
+import java.sql.Driver;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Properties;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.tomcat.util.ExceptionUtils;
+
+
+/**
+*
+* Implementation of <b>Realm</b> that works with any JDBC supported database.
+* See the JDBCRealm.howto for more details on how to set up the database and
+* for configuration options.
+*
+* <p>For a <b>Realm</b> implementation that supports connection pooling and
+* doesn't require synchronisation of <code>authenticate()</code>,
+* <code>getPassword()</code>, <code>roles()</code> and
+* <code>getPrincipal()</code> or the ugly connection logic use the
+* <code>DataSourceRealm</code>.</p>
+*
+* @author Craig R. McClanahan
+* @author Carson McDonald
+* @author Ignacio Ortega
+* @version $Id: JDBCRealm.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+*/
+
+public class JDBCRealm
+    extends RealmBase {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The connection username to use when trying to connect to the database.
+     */
+    protected String connectionName = null;
+
+
+    /**
+     * The connection URL to use when trying to connect to the database.
+     */
+    protected String connectionPassword = null;
+
+
+    /**
+     * The connection URL to use when trying to connect to the database.
+     */
+    protected String connectionURL = null;
+
+
+    /**
+     * The connection to the database.
+     */
+    protected Connection dbConnection = null;
+
+
+    /**
+     * Instance of the JDBC Driver class we use as a connection factory.
+     */
+    protected Driver driver = null;
+
+
+    /**
+     * The JDBC driver to use.
+     */
+    protected String driverName = null;
+
+
+    /**
+     * Descriptive information about this Realm implementation.
+     */
+    protected static final String info =
+        "org.apache.catalina.realm.JDBCRealm/1.0";
+
+
+    /**
+     * Descriptive information about this Realm implementation.
+     */
+    protected static final String name = "JDBCRealm";
+
+
+    /**
+     * The PreparedStatement to use for authenticating users.
+     */
+    protected PreparedStatement preparedCredentials = null;
+
+
+    /**
+     * The PreparedStatement to use for identifying the roles for
+     * a specified user.
+     */
+    protected PreparedStatement preparedRoles = null;
+
+
+    /**
+     * The column in the user role table that names a role
+     */
+    protected String roleNameCol = null;
+
+
+    /**
+     * The column in the user table that holds the user's credentials
+     */
+    protected String userCredCol = null;
+
+
+    /**
+     * The column in the user table that holds the user's name
+     */
+    protected String userNameCol = null;
+
+
+    /**
+     * The table that holds the relation between user's and roles
+     */
+    protected String userRoleTable = null;
+
+
+    /**
+     * The table that holds user data.
+     */
+    protected String userTable = null;
+
+
+    // ------------------------------------------------------------- Properties
+
+    /**
+     * Return the username to use to connect to the database.
+     *
+     */
+    public String getConnectionName() {
+        return connectionName;
+    }
+
+    /**
+     * Set the username to use to connect to the database.
+     *
+     * @param connectionName Username
+     */
+    public void setConnectionName(String connectionName) {
+        this.connectionName = connectionName;
+    }
+
+    /**
+     * Return the password to use to connect to the database.
+     *
+     */
+    public String getConnectionPassword() {
+        return connectionPassword;
+    }
+
+    /**
+     * Set the password to use to connect to the database.
+     *
+     * @param connectionPassword User password
+     */
+    public void setConnectionPassword(String connectionPassword) {
+        this.connectionPassword = connectionPassword;
+    }
+
+    /**
+     * Return the URL to use to connect to the database.
+     *
+     */
+    public String getConnectionURL() {
+        return connectionURL;
+    }
+
+    /**
+     * Set the URL to use to connect to the database.
+     *
+     * @param connectionURL The new connection URL
+     */
+    public void setConnectionURL( String connectionURL ) {
+      this.connectionURL = connectionURL;
+    }
+
+    /**
+     * Return the JDBC driver that will be used.
+     *
+     */
+    public String getDriverName() {
+        return driverName;
+    }
+
+    /**
+     * Set the JDBC driver that will be used.
+     *
+     * @param driverName The driver name
+     */
+    public void setDriverName( String driverName ) {
+      this.driverName = driverName;
+    }
+
+    /**
+     * Return the column in the user role table that names a role.
+     *
+     */
+    public String getRoleNameCol() {
+        return roleNameCol;
+    }
+
+    /**
+     * Set the column in the user role table that names a role.
+     *
+     * @param roleNameCol The column name
+     */
+    public void setRoleNameCol( String roleNameCol ) {
+        this.roleNameCol = roleNameCol;
+    }
+
+    /**
+     * Return the column in the user table that holds the user's credentials.
+     *
+     */
+    public String getUserCredCol() {
+        return userCredCol;
+    }
+
+    /**
+     * Set the column in the user table that holds the user's credentials.
+     *
+     * @param userCredCol The column name
+     */
+    public void setUserCredCol( String userCredCol ) {
+       this.userCredCol = userCredCol;
+    }
+
+    /**
+     * Return the column in the user table that holds the user's name.
+     *
+     */
+    public String getUserNameCol() {
+        return userNameCol;
+    }
+
+    /**
+     * Set the column in the user table that holds the user's name.
+     *
+     * @param userNameCol The column name
+     */
+    public void setUserNameCol( String userNameCol ) {
+       this.userNameCol = userNameCol;
+    }
+
+    /**
+     * Return the table that holds the relation between user's and roles.
+     *
+     */
+    public String getUserRoleTable() {
+        return userRoleTable;
+    }
+
+    /**
+     * Set the table that holds the relation between user's and roles.
+     *
+     * @param userRoleTable The table name
+     */
+    public void setUserRoleTable( String userRoleTable ) {
+        this.userRoleTable = userRoleTable;
+    }
+
+    /**
+     * Return the table that holds user data..
+     *
+     */
+    public String getUserTable() {
+        return userTable;
+    }
+
+    /**
+     * Set the table that holds user data.
+     *
+     * @param userTable The table name
+     */
+    public void setUserTable( String userTable ) {
+      this.userTable = userTable;
+    }
+
+    /**
+     * Return descriptive information about this Realm implementation and
+     * the corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    @Override
+    public String getInfo() {
+
+        return info;
+
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return the Principal associated with the specified username and
+     * credentials, if there is one; otherwise return <code>null</code>.
+     *
+     * If there are any errors with the JDBC connection, executing
+     * the query or anything we return null (don't authenticate). This
+     * event is also logged, and the connection will be closed so that
+     * a subsequent request will automatically re-open it.
+     *
+     *
+     * @param username Username of the Principal to look up
+     * @param credentials Password or other credentials to use in
+     *  authenticating this username
+     */
+    @Override
+    public synchronized Principal authenticate(String username, String credentials) {
+
+        // Number of tries is the number of attempts to connect to the database
+        // during this login attempt (if we need to open the database)
+        // This needs rewritten with better pooling support, the existing code
+        // needs signature changes since the Prepared statements needs cached
+        // with the connections.
+        // The code below will try twice if there is a SQLException so the
+        // connection may try to be opened again. On normal conditions (including
+        // invalid login - the above is only used once.
+        int numberOfTries = 2;
+        while (numberOfTries>0) {
+            try {
+
+                // Ensure that we have an open database connection
+                open();
+
+                // Acquire a Principal object for this user
+                Principal principal = authenticate(dbConnection,
+                                                   username, credentials);
+
+
+                // Return the Principal (if any)
+                return (principal);
+
+            } catch (SQLException e) {
+
+                // Log the problem for posterity
+                containerLog.error(sm.getString("jdbcRealm.exception"), e);
+
+                // Close the connection so that it gets reopened next time
+                if (dbConnection != null)
+                    close(dbConnection);
+
+            }
+
+            numberOfTries--;
+        }
+
+        // Worst case scenario
+        return null;
+
+    }
+
+
+    // -------------------------------------------------------- Package Methods
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Return the Principal associated with the specified username and
+     * credentials, if there is one; otherwise return <code>null</code>.
+     *
+     * @param dbConnection The database connection to be used
+     * @param username Username of the Principal to look up
+     * @param credentials Password or other credentials to use in
+     *  authenticating this username
+     */
+    public synchronized Principal authenticate(Connection dbConnection,
+                                               String username,
+                                               String credentials) {
+
+        // No user or no credentials
+        // Can't possibly authenticate, don't bother the database then
+        if (username == null || credentials == null) {
+            return null;
+        }
+
+        // Look up the user's credentials
+        String dbCredentials = getPassword(username);
+
+        // Validate the user's credentials
+        boolean validated = false;
+        if (hasMessageDigest()) {
+            // Hex hashes should be compared case-insensitive
+            validated = (digest(credentials).equalsIgnoreCase(dbCredentials));
+        } else {
+            validated = (digest(credentials).equals(dbCredentials));
+        }
+
+        if (validated) {
+            if (containerLog.isTraceEnabled())
+                containerLog.trace(sm.getString("jdbcRealm.authenticateSuccess",
+                                                username));
+        } else {
+            if (containerLog.isTraceEnabled())
+                containerLog.trace(sm.getString("jdbcRealm.authenticateFailure",
+                                                username));
+            return (null);
+        }
+
+        ArrayList<String> roles = getRoles(username);
+        
+        // Create and return a suitable Principal for this user
+        return (new GenericPrincipal(username, credentials, roles));
+
+    }
+
+
+    /**
+     * Close the specified database connection.
+     *
+     * @param dbConnection The connection to be closed
+     */
+    protected void close(Connection dbConnection) {
+
+        // Do nothing if the database connection is already closed
+        if (dbConnection == null)
+            return;
+
+        // Close our prepared statements (if any)
+        try {
+            preparedCredentials.close();
+        } catch (Throwable f) {
+            ExceptionUtils.handleThrowable(f);
+        }
+        this.preparedCredentials = null;
+
+
+        try {
+            preparedRoles.close();
+        } catch (Throwable f) {
+            ExceptionUtils.handleThrowable(f);
+        }
+        this.preparedRoles = null;
+
+
+        // Close this database connection, and log any errors
+        try {
+            dbConnection.close();
+        } catch (SQLException e) {
+            containerLog.warn(sm.getString("jdbcRealm.close"), e); // Just log it here
+        } finally {
+           this.dbConnection = null;
+        }
+
+    }
+
+
+    /**
+     * Return a PreparedStatement configured to perform the SELECT required
+     * to retrieve user credentials for the specified username.
+     *
+     * @param dbConnection The database connection to be used
+     * @param username Username for which credentials should be retrieved
+     *
+     * @exception SQLException if a database error occurs
+     */
+    protected PreparedStatement credentials(Connection dbConnection,
+                                            String username)
+        throws SQLException {
+
+        if (preparedCredentials == null) {
+            StringBuilder sb = new StringBuilder("SELECT ");
+            sb.append(userCredCol);
+            sb.append(" FROM ");
+            sb.append(userTable);
+            sb.append(" WHERE ");
+            sb.append(userNameCol);
+            sb.append(" = ?");
+
+            if(containerLog.isDebugEnabled()) {
+                containerLog.debug("credentials query: " + sb.toString());
+            }
+
+            preparedCredentials =
+                dbConnection.prepareStatement(sb.toString());
+        }
+
+        if (username == null) {
+            preparedCredentials.setNull(1,java.sql.Types.VARCHAR);
+        } else {
+            preparedCredentials.setString(1, username);
+        }
+
+        return (preparedCredentials);
+    }
+
+
+    /**
+     * Return a short name for this Realm implementation.
+     */
+    @Override
+    protected String getName() {
+
+        return (name);
+
+    }
+
+
+    /**
+     * Return the password associated with the given principal's user name.
+     */
+    @Override
+    protected synchronized String getPassword(String username) {
+
+        // Look up the user's credentials
+        String dbCredentials = null;
+        PreparedStatement stmt = null;
+        ResultSet rs = null;
+
+        // Number of tries is the number of attempts to connect to the database
+        // during this login attempt (if we need to open the database)
+        // This needs rewritten with better pooling support, the existing code
+        // needs signature changes since the Prepared statements needs cached
+        // with the connections.
+        // The code below will try twice if there is a SQLException so the
+        // connection may try to be opened again. On normal conditions (including
+        // invalid login - the above is only used once.
+        int numberOfTries = 2;
+        while (numberOfTries>0) {
+            try {
+                
+                // Ensure that we have an open database connection
+                open();
+                
+                try {
+                    stmt = credentials(dbConnection, username);
+                    rs = stmt.executeQuery();
+                    
+                    if (rs.next()) {
+                        dbCredentials = rs.getString(1);
+                    }
+                    rs.close();
+                    rs = null;
+                    if (dbCredentials == null) {
+                        return (null);
+                    }
+                    
+                    dbCredentials = dbCredentials.trim();
+                    return dbCredentials;
+                    
+                } finally {
+                    if (rs!=null) {
+                        try {
+                            rs.close();
+                        } catch(SQLException e) {
+                            containerLog.warn(sm.getString("jdbcRealm.abnormalCloseResultSet"));
+                        }
+                    }
+                    dbConnection.commit();
+                }
+                
+            } catch (SQLException e) {
+                
+                // Log the problem for posterity
+                containerLog.error(sm.getString("jdbcRealm.exception"), e);
+                
+                // Close the connection so that it gets reopened next time
+                if (dbConnection != null)
+                    close(dbConnection);
+                
+            }
+            
+            numberOfTries--;
+        }
+        
+        return (null);
+    }
+
+
+    /**
+     * Return the Principal associated with the given user name.
+     */
+    @Override
+    protected synchronized Principal getPrincipal(String username) {
+
+        return (new GenericPrincipal(username,
+                                     getPassword(username),
+                                     getRoles(username)));
+
+    }
+
+
+    /**
+     * Return the roles associated with the gven user name.
+     */
+    protected ArrayList<String> getRoles(String username) {
+        
+        PreparedStatement stmt = null;
+        ResultSet rs = null;
+
+        // Number of tries is the number of attempts to connect to the database
+        // during this login attempt (if we need to open the database)
+        // This needs rewritten wuth better pooling support, the existing code
+        // needs signature changes since the Prepared statements needs cached
+        // with the connections.
+        // The code below will try twice if there is a SQLException so the
+        // connection may try to be opened again. On normal conditions (including
+        // invalid login - the above is only used once.
+        int numberOfTries = 2;
+        while (numberOfTries>0) {
+            try {
+                
+                // Ensure that we have an open database connection
+                open();
+                
+                try {
+                    // Accumulate the user's roles
+                    ArrayList<String> roleList = new ArrayList<String>();
+                    stmt = roles(dbConnection, username);
+                    rs = stmt.executeQuery();
+                    while (rs.next()) {
+                        String role = rs.getString(1);
+                        if (null!=role) {
+                            roleList.add(role.trim());
+                        }
+                    }
+                    rs.close();
+                    rs = null;
+                    
+                    return (roleList);
+                    
+                } finally {
+                    if (rs!=null) {
+                        try {
+                            rs.close();
+                        } catch(SQLException e) {
+                            containerLog.warn(sm.getString("jdbcRealm.abnormalCloseResultSet"));
+                        }
+                    }
+                    dbConnection.commit();
+                }
+                
+            } catch (SQLException e) {
+                
+                // Log the problem for posterity
+                containerLog.error(sm.getString("jdbcRealm.exception"), e);
+                
+                // Close the connection so that it gets reopened next time
+                if (dbConnection != null)
+                    close(dbConnection);
+                
+            }
+            
+            numberOfTries--;
+        }
+        
+        return (null);
+        
+    }
+    
+    
+    /**
+     * Open (if necessary) and return a database connection for use by
+     * this Realm.
+     *
+     * @exception SQLException if a database error occurs
+     */
+    protected Connection open() throws SQLException {
+
+        // Do nothing if there is a database connection already open
+        if (dbConnection != null)
+            return (dbConnection);
+
+        // Instantiate our database driver if necessary
+        if (driver == null) {
+            try {
+                Class<?> clazz = Class.forName(driverName);
+                driver = (Driver) clazz.newInstance();
+            } catch (Throwable e) {
+                ExceptionUtils.handleThrowable(e);
+                throw new SQLException(e.getMessage(), e);
+            }
+        }
+
+        // Open a new connection
+        Properties props = new Properties();
+        if (connectionName != null)
+            props.put("user", connectionName);
+        if (connectionPassword != null)
+            props.put("password", connectionPassword);
+        dbConnection = driver.connect(connectionURL, props);
+        if (dbConnection == null) {
+            throw new SQLException(sm.getString(
+                    "jdbcRealm.open.invalidurl",driverName, connectionURL));
+        }
+        dbConnection.setAutoCommit(false);
+        return (dbConnection);
+
+    }
+
+
+    /**
+     * Release our use of this connection so that it can be recycled.
+     *
+     * @param dbConnection The connection to be released
+     */
+    protected void release(Connection dbConnection) {
+
+        // NO-OP since we are not pooling anything
+
+    }
+
+
+    /**
+     * Return a PreparedStatement configured to perform the SELECT required
+     * to retrieve user roles for the specified username.
+     *
+     * @param dbConnection The database connection to be used
+     * @param username Username for which roles should be retrieved
+     *
+     * @exception SQLException if a database error occurs
+     */
+    protected synchronized PreparedStatement roles(Connection dbConnection,
+            String username)
+        throws SQLException {
+
+        if (preparedRoles == null) {
+            StringBuilder sb = new StringBuilder("SELECT ");
+            sb.append(roleNameCol);
+            sb.append(" FROM ");
+            sb.append(userRoleTable);
+            sb.append(" WHERE ");
+            sb.append(userNameCol);
+            sb.append(" = ?");
+            preparedRoles =
+                dbConnection.prepareStatement(sb.toString());
+        }
+
+        preparedRoles.setString(1, username);
+        return (preparedRoles);
+
+    }
+
+
+    // ------------------------------------------------------ Lifecycle Methods
+
+
+    /**
+     * Prepare for the beginning of active use of the public methods of 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 void startInternal() throws LifecycleException {
+
+        // Validate that we can open our connection - but let tomcat
+        // startup in case the database is temporarily unavailable
+        try {
+            open();
+        } catch (SQLException e) {
+            containerLog.error(sm.getString("jdbcRealm.open"), e);
+        }
+
+        super.startInternal();
+    }
+
+
+    /**
+     * Gracefully terminate the active use of the public methods of this
+     * component and implement the requirements of
+     * {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
+     *
+     * @exception LifecycleException if this component detects a fatal error
+     *  that needs to be reported
+     */
+     @Override
+    protected void stopInternal() throws LifecycleException {
+
+        super.stopInternal();
+
+        // Close any open DB connection
+        close(this.dbConnection);
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/realm/JNDIRealm.java b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/JNDIRealm.java
new file mode 100644
index 0000000..ffbbe10
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/JNDIRealm.java
@@ -0,0 +1,2409 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.realm;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.Principal;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+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.Set;
+
+import javax.naming.AuthenticationException;
+import javax.naming.CommunicationException;
+import javax.naming.CompositeName;
+import javax.naming.Context;
+import javax.naming.InvalidNameException;
+import javax.naming.Name;
+import javax.naming.NameNotFoundException;
+import javax.naming.NameParser;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.PartialResultException;
+import javax.naming.ServiceUnavailableException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.util.Base64;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.CharChunk;
+import org.ietf.jgss.GSSCredential;
+
+/**
+ * <p>Implementation of <strong>Realm</strong> that works with a directory
+ * server accessed via the Java Naming and Directory Interface (JNDI) APIs.
+ * The following constraints are imposed on the data structure in the
+ * underlying directory server:</p>
+ * <ul>
+ *
+ * <li>Each user that can be authenticated is represented by an individual
+ *     element in the top level <code>DirContext</code> that is accessed
+ *     via the <code>connectionURL</code> property.</li>
+ *
+ * <li>If a socket connection can not be made to the <code>connectURL</code>
+ *     an attempt will be made to use the <code>alternateURL</code> if it
+ *     exists.</li>
+ *
+ * <li>Each user element has a distinguished name that can be formed by
+ *     substituting the presented username into a pattern configured by the
+ *     <code>userPattern</code> property.</li>
+ *
+ * <li>Alternatively, if the <code>userPattern</code> property is not
+ *     specified, a unique element can be located by searching the directory
+ *     context. In this case:
+ *     <ul>
+ *     <li>The <code>userSearch</code> pattern specifies the search filter
+ *         after substitution of the username.</li>
+ *     <li>The <code>userBase</code> property can be set to the element that
+ *         is the base of the subtree containing users.  If not specified,
+ *         the search base is the top-level context.</li>
+ *     <li>The <code>userSubtree</code> property can be set to
+ *         <code>true</code> if you wish to search the entire subtree of the
+ *         directory context.  The default value of <code>false</code>
+ *         requests a search of only the current level.</li>
+ *    </ul>
+ * </li>
+ *
+ * <li>The user may be authenticated by binding to the directory with the
+ *      username and password presented. This method is used when the
+ *      <code>userPassword</code> property is not specified.</li>
+ *
+ * <li>The user may be authenticated by retrieving the value of an attribute
+ *     from the directory and comparing it explicitly with the value presented
+ *     by the user. This method is used when the <code>userPassword</code>
+ *     property is specified, in which case:
+ *     <ul>
+ *     <li>The element for this user must contain an attribute named by the
+ *         <code>userPassword</code> property.
+ *     <li>The value of the user password attribute is either a cleartext
+ *         String, or the result of passing a cleartext String through the
+ *         <code>RealmBase.digest()</code> method (using the standard digest
+ *         support included in <code>RealmBase</code>).
+ *     <li>The user is considered to be authenticated if the presented
+ *         credentials (after being passed through
+ *         <code>RealmBase.digest()</code>) are equal to the retrieved value
+ *         for the user password attribute.</li>
+ *     </ul></li>
+ *
+ * <li>Each group of users that has been assigned a particular role may be
+ *     represented by an individual element in the top level
+ *     <code>DirContext</code> that is accessed via the
+ *     <code>connectionURL</code> property.  This element has the following
+ *     characteristics:
+ *     <ul>
+ *     <li>The set of all possible groups of interest can be selected by a
+ *         search pattern configured by the <code>roleSearch</code>
+ *         property.</li>
+ *     <li>The <code>roleSearch</code> pattern optionally includes pattern
+ *         replacements "{0}" for the distinguished name, and/or "{1}" for
+ *         the username, of the authenticated user for which roles will be
+ *         retrieved.</li>
+ *     <li>The <code>roleBase</code> property can be set to the element that
+ *         is the base of the search for matching roles.  If not specified,
+ *         the entire context will be searched.</li>
+ *     <li>The <code>roleSubtree</code> property can be set to
+ *         <code>true</code> if you wish to search the entire subtree of the
+ *         directory context.  The default value of <code>false</code>
+ *         requests a search of only the current level.</li>
+ *     <li>The element includes an attribute (whose name is configured by
+ *         the <code>roleName</code> property) containing the name of the
+ *         role represented by this element.</li>
+ *     </ul></li>
+ *
+ * <li>In addition, roles may be represented by the values of an attribute
+ * in the user's element whose name is configured by the
+ * <code>userRoleName</code> property.</li>
+ *
+ * <li>A default role can be assigned to each user that was successfully
+ * authenticated by setting the <code>commonRole</code> property to the
+ * name of this role. The role doesn't have to exist in the directory.</li>
+ *
+ * <li>If the directory server contains nested roles, you can search for them
+ * by setting <code>roleNested</code> to <code>true</code>.
+ * The default value is <code>false</code>, so role searches will not find
+ * nested roles.</li>
+ *
+ * <li>Note that the standard <code>&lt;security-role-ref&gt;</code> element in
+ *     the web application deployment descriptor allows applications to refer
+ *     to roles programmatically by names other than those used in the
+ *     directory server itself.</li>
+ * </ul>
+ *
+ * <p><strong>TODO</strong> - Support connection pooling (including message
+ * format objects) so that <code>authenticate()</code> does not have to be
+ * synchronized.</p>
+ *
+ * <p><strong>WARNING</strong> - There is a reported bug against the Netscape
+ * provider code (com.netscape.jndi.ldap.LdapContextFactory) with respect to
+ * successfully authenticated a non-existing user. The
+ * report is here: http://issues.apache.org/bugzilla/show_bug.cgi?id=11210 .
+ * With luck, Netscape has updated their provider code and this is not an
+ * issue. </p>
+ *
+ * @author John Holman
+ * @author Craig R. McClanahan
+ * @version $Id: JNDIRealm.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ */
+
+public class JNDIRealm extends RealmBase {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     *  The type of authentication to use
+     */
+    protected String authentication = null;
+
+    /**
+     * The connection username for the server we will contact.
+     */
+    protected String connectionName = null;
+
+
+    /**
+     * The connection password for the server we will contact.
+     */
+    protected String connectionPassword = null;
+
+
+    /**
+     * The connection URL for the server we will contact.
+     */
+    protected String connectionURL = null;
+
+
+    /**
+     * The directory context linking us to our directory server.
+     */
+    protected DirContext context = null;
+
+
+    /**
+     * The JNDI context factory used to acquire our InitialContext.  By
+     * default, assumes use of an LDAP server using the standard JNDI LDAP
+     * provider.
+     */
+    protected String contextFactory = "com.sun.jndi.ldap.LdapCtxFactory";
+
+
+    /**
+     * How aliases should be dereferenced during search operations.
+     */
+    protected String derefAliases = null;
+
+    /**
+     * Constant that holds the name of the environment property for specifying
+     * the manner in which aliases should be dereferenced.
+     */
+    public static final String DEREF_ALIASES = "java.naming.ldap.derefAliases";
+
+    /**
+     * Descriptive information about this Realm implementation.
+     */
+    protected static final String info =
+        "org.apache.catalina.realm.JNDIRealm/1.0";
+
+
+    /**
+     * Descriptive information about this Realm implementation.
+     */
+    protected static final String name = "JNDIRealm";
+
+
+    /**
+     * The protocol that will be used in the communication with the
+     * directory server.
+     */
+    protected String protocol = null;
+
+
+    /**
+     * Should we ignore PartialResultExceptions when iterating over NamingEnumerations?
+     * Microsoft Active Directory often returns referrals, which lead
+     * to PartialResultExceptions. Unfortunately there's no stable way to detect,
+     * if the Exceptions really come from an AD referral.
+     * Set to true to ignore PartialResultExceptions.
+     */
+    protected boolean adCompat = false;
+
+
+    /**
+     * How should we handle referrals?  Microsoft Active Directory often returns
+     * referrals. If you need to follow them set referrals to "follow".
+     * Caution: if your DNS is not part of AD, the LDAP client lib might try
+     * to resolve your domain name in DNS to find another LDAP server.
+     */
+    protected String referrals = null;
+
+
+    /**
+     * The base element for user searches.
+     */
+    protected String userBase = "";
+
+
+    /**
+     * The message format used to search for a user, with "{0}" marking
+     * the spot where the username goes.
+     */
+    protected String userSearch = null;
+
+
+    /**
+     * The MessageFormat object associated with the current
+     * <code>userSearch</code>.
+     */
+    protected MessageFormat userSearchFormat = null;
+
+
+    /**
+     * Should we search the entire subtree for matching users?
+     */
+    protected boolean userSubtree = false;
+
+
+    /**
+     * The attribute name used to retrieve the user password.
+     */
+    protected String userPassword = null;
+
+
+    /**
+     * A string of LDAP user patterns or paths, ":"-separated
+     * These will be used to form the distinguished name of a
+     * user, with "{0}" marking the spot where the specified username
+     * goes.
+     * This is similar to userPattern, but allows for multiple searches
+     * for a user.
+     */
+    protected String[] userPatternArray = null;
+
+
+    /**
+     * The message format used to form the distinguished name of a
+     * user, with "{0}" marking the spot where the specified username
+     * goes.
+     */
+    protected String userPattern = null;
+
+
+    /**
+     * An array of MessageFormat objects associated with the current
+     * <code>userPatternArray</code>.
+     */
+    protected MessageFormat[] userPatternFormatArray = null;
+
+    /**
+     * The base element for role searches.
+     */
+    protected String roleBase = "";
+
+
+    /**
+     * The MessageFormat object associated with the current
+     * <code>roleBase</code>.
+     */
+    protected MessageFormat roleBaseFormat = null;
+
+
+    /**
+     * The MessageFormat object associated with the current
+     * <code>roleSearch</code>.
+     */
+    protected MessageFormat roleFormat = null;
+
+
+    /**
+     * The name of an attribute in the user's entry containing
+     * roles for that user
+     */
+    protected String userRoleName = null;
+
+
+    /**
+     * The name of the attribute containing roles held elsewhere
+     */
+    protected String roleName = null;
+
+
+    /**
+     * The message format used to select roles for a user, with "{0}" marking
+     * the spot where the distinguished name of the user goes.
+     */
+    protected String roleSearch = null;
+
+
+    /**
+     * Should we search the entire subtree for matching memberships?
+     */
+    protected boolean roleSubtree = false;
+    
+    /**
+     * Should we look for nested group in order to determine roles?
+     */
+    protected boolean roleNested = false;
+
+    /**
+     * When searching for user roles, should the search be performed as the user
+     * currently being authenticated? If false, {@link #connectionName} and
+     * {@link #connectionPassword} will be used if specified, else an anonymous
+     * connection will be used. 
+     */
+    protected boolean roleSearchAsUser = false;
+    
+    /**
+     * An alternate URL, to which, we should connect if connectionURL fails.
+     */
+    protected String alternateURL;
+
+    /**
+     * The number of connection attempts.  If greater than zero we use the
+     * alternate url.
+     */
+    protected int connectionAttempt = 0;
+
+    /**
+     *  Add this role to every authenticated user
+     */
+    protected String commonRole = null;
+
+
+    /**
+     * The timeout, in milliseconds, to use when trying to create a connection
+     * to the directory. The default is 5000 (5 seconds).
+     */
+    protected String connectionTimeout = "5000";
+    
+    /**
+     * The sizeLimit (also known as the countLimit) to use when the realm is
+     * configured with {@link #userSearch}. Zero for no limit.
+     */
+    protected long sizeLimit = 0;
+
+    /**
+     * The timeLimit (in milliseconds) to use when the realm is configured with
+     * {@link #userSearch}. Zero for no limit.
+     */
+    protected int timeLimit = 0;
+
+    
+    /**
+     * Should delegated credentials from the SPNEGO authenticator be used if
+     * available
+     */
+    protected boolean useDelegatedCredential = true;
+
+
+    // ------------------------------------------------------------- Properties
+
+    /**
+     * Return the type of authentication to use.
+     */
+    public String getAuthentication() {
+
+        return authentication;
+
+    }
+
+    /**
+     * Set the type of authentication to use.
+     *
+     * @param authentication The authentication
+     */
+    public void setAuthentication(String authentication) {
+
+        this.authentication = authentication;
+
+    }
+
+    /**
+     * Return the connection username for this Realm.
+     */
+    public String getConnectionName() {
+
+        return (this.connectionName);
+
+    }
+
+
+    /**
+     * Set the connection username for this Realm.
+     *
+     * @param connectionName The new connection username
+     */
+    public void setConnectionName(String connectionName) {
+
+        this.connectionName = connectionName;
+
+    }
+
+
+    /**
+     * Return the connection password for this Realm.
+     */
+    public String getConnectionPassword() {
+
+        return (this.connectionPassword);
+
+    }
+
+
+    /**
+     * Set the connection password for this Realm.
+     *
+     * @param connectionPassword The new connection password
+     */
+    public void setConnectionPassword(String connectionPassword) {
+
+        this.connectionPassword = connectionPassword;
+
+    }
+
+
+    /**
+     * Return the connection URL for this Realm.
+     */
+    public String getConnectionURL() {
+
+        return (this.connectionURL);
+
+    }
+
+
+    /**
+     * Set the connection URL for this Realm.
+     *
+     * @param connectionURL The new connection URL
+     */
+    public void setConnectionURL(String connectionURL) {
+
+        this.connectionURL = connectionURL;
+
+    }
+
+
+    /**
+     * Return the JNDI context factory for this Realm.
+     */
+    public String getContextFactory() {
+
+        return (this.contextFactory);
+
+    }
+
+
+    /**
+     * Set the JNDI context factory for this Realm.
+     *
+     * @param contextFactory The new context factory
+     */
+    public void setContextFactory(String contextFactory) {
+
+        this.contextFactory = contextFactory;
+
+    }
+
+    /**
+     * Return the derefAliases setting to be used.
+     */
+    public java.lang.String getDerefAliases() {
+        return derefAliases;
+    }
+
+    /**
+     * Set the value for derefAliases to be used when searching the directory.
+     *
+     * @param derefAliases New value of property derefAliases.
+     */
+    public void setDerefAliases(java.lang.String derefAliases) {
+      this.derefAliases = derefAliases;
+    }
+
+    /**
+     * Return the protocol to be used.
+     */
+    public String getProtocol() {
+
+        return protocol;
+
+    }
+
+    /**
+     * Set the protocol for this Realm.
+     *
+     * @param protocol The new protocol.
+     */
+    public void setProtocol(String protocol) {
+
+        this.protocol = protocol;
+
+    }
+
+
+    /**
+     * Returns the current settings for handling PartialResultExceptions
+     */
+    public boolean getAdCompat () {
+        return adCompat;
+    }
+
+
+    /**
+     * How do we handle PartialResultExceptions?
+     * True: ignore all PartialResultExceptions.
+     */
+    public void setAdCompat (boolean adCompat) {
+        this.adCompat = adCompat;
+    }
+
+
+    /**
+     * Returns the current settings for handling JNDI referrals.
+     */
+    public String getReferrals () {
+        return referrals;
+    }
+
+
+    /**
+     * How do we handle JNDI referrals? ignore, follow, or throw
+     * (see javax.naming.Context.REFERRAL for more information).
+     */
+    public void setReferrals (String referrals) {
+        this.referrals = referrals;
+    }
+
+
+    /**
+     * Return the base element for user searches.
+     */
+    public String getUserBase() {
+
+        return (this.userBase);
+
+    }
+
+
+    /**
+     * Set the base element for user searches.
+     *
+     * @param userBase The new base element
+     */
+    public void setUserBase(String userBase) {
+
+        this.userBase = userBase;
+
+    }
+
+
+    /**
+     * Return the message format pattern for selecting users in this Realm.
+     */
+    public String getUserSearch() {
+
+        return (this.userSearch);
+
+    }
+
+
+    /**
+     * Set the message format pattern for selecting users in this Realm.
+     *
+     * @param userSearch The new user search pattern
+     */
+    public void setUserSearch(String userSearch) {
+
+        this.userSearch = userSearch;
+        if (userSearch == null)
+            userSearchFormat = null;
+        else
+            userSearchFormat = new MessageFormat(userSearch);
+
+    }
+
+
+    /**
+     * Return the "search subtree for users" flag.
+     */
+    public boolean getUserSubtree() {
+
+        return (this.userSubtree);
+
+    }
+
+
+    /**
+     * Set the "search subtree for users" flag.
+     *
+     * @param userSubtree The new search flag
+     */
+    public void setUserSubtree(boolean userSubtree) {
+
+        this.userSubtree = userSubtree;
+
+    }
+
+
+    /**
+     * Return the user role name attribute name for this Realm.
+     */
+    public String getUserRoleName() {
+
+        return userRoleName;
+    }
+
+
+    /**
+     * Set the user role name attribute name for this Realm.
+     *
+     * @param userRoleName The new userRole name attribute name
+     */
+    public void setUserRoleName(String userRoleName) {
+
+        this.userRoleName = userRoleName;
+
+    }
+
+
+    /**
+     * Return the base element for role searches.
+     */
+    public String getRoleBase() {
+
+        return (this.roleBase);
+
+    }
+
+
+    /**
+     * Set the base element for role searches.
+     *
+     * @param roleBase The new base element
+     */
+    public void setRoleBase(String roleBase) {
+
+        this.roleBase = roleBase;
+        if (roleBase == null)
+            roleBaseFormat = null;
+        else
+            roleBaseFormat = new MessageFormat(roleBase);
+
+    }
+
+
+    /**
+     * Return the role name attribute name for this Realm.
+     */
+    public String getRoleName() {
+
+        return (this.roleName);
+
+    }
+
+
+    /**
+     * Set the role name attribute name for this Realm.
+     *
+     * @param roleName The new role name attribute name
+     */
+    public void setRoleName(String roleName) {
+
+        this.roleName = roleName;
+
+    }
+
+
+    /**
+     * Return the message format pattern for selecting roles in this Realm.
+     */
+    public String getRoleSearch() {
+
+        return (this.roleSearch);
+
+    }
+
+
+    /**
+     * Set the message format pattern for selecting roles in this Realm.
+     *
+     * @param roleSearch The new role search pattern
+     */
+    public void setRoleSearch(String roleSearch) {
+
+        this.roleSearch = roleSearch;
+        if (roleSearch == null)
+            roleFormat = null;
+        else
+            roleFormat = new MessageFormat(roleSearch);
+
+    }
+
+
+    /**
+     * Return the "search subtree for roles" flag.
+     */
+    public boolean getRoleSubtree() {
+
+        return (this.roleSubtree);
+
+    }
+
+
+    /**
+     * Set the "search subtree for roles" flag.
+     *
+     * @param roleSubtree The new search flag
+     */
+    public void setRoleSubtree(boolean roleSubtree) {
+
+        this.roleSubtree = roleSubtree;
+
+    }
+    
+    /**
+     * Return the "The nested group search flag" flag.
+     */
+    public boolean getRoleNested() {
+
+        return (this.roleNested);
+
+    }
+
+
+    /**
+     * Set the "search subtree for roles" flag.
+     *
+     * @param roleNested The nested group search flag
+     */
+    public void setRoleNested(boolean roleNested) {
+
+        this.roleNested = roleNested;
+
+    }
+
+
+    /**
+     * Return the password attribute used to retrieve the user password.
+     */
+    public String getUserPassword() {
+
+        return (this.userPassword);
+
+    }
+
+
+    /**
+     * Set the password attribute used to retrieve the user password.
+     *
+     * @param userPassword The new password attribute
+     */
+    public void setUserPassword(String userPassword) {
+
+        this.userPassword = userPassword;
+
+    }
+
+
+    /**
+     * Return the message format pattern for selecting users in this Realm.
+     */
+    public String getUserPattern() {
+
+        return (this.userPattern);
+
+    }
+
+
+    /**
+     * Set the message format pattern for selecting users in this Realm.
+     * This may be one simple pattern, or multiple patterns to be tried,
+     * separated by parentheses. (for example, either "cn={0}", or
+     * "(cn={0})(cn={0},o=myorg)" Full LDAP search strings are also supported,
+     * but only the "OR", "|" syntax, so "(|(cn={0})(cn={0},o=myorg))" is
+     * also valid. Complex search strings with &, etc are NOT supported.
+     *
+     * @param userPattern The new user pattern
+     */
+    public void setUserPattern(String userPattern) {
+
+        this.userPattern = userPattern;
+        if (userPattern == null)
+            userPatternArray = null;
+        else {
+            userPatternArray = parseUserPatternString(userPattern);
+            int len = this.userPatternArray.length;
+            userPatternFormatArray = new MessageFormat[len];
+            for (int i=0; i < len; i++) {
+                userPatternFormatArray[i] =
+                    new MessageFormat(userPatternArray[i]);
+            }
+        }
+    }
+
+
+    /**
+     * Getter for property alternateURL.
+     *
+     * @return Value of property alternateURL.
+     */
+    public String getAlternateURL() {
+
+        return this.alternateURL;
+
+    }
+
+
+    /**
+     * Setter for property alternateURL.
+     *
+     * @param alternateURL New value of property alternateURL.
+     */
+    public void setAlternateURL(String alternateURL) {
+
+        this.alternateURL = alternateURL;
+
+    }
+
+
+    /**
+     * Return the common role
+     */
+    public String getCommonRole() {
+
+        return commonRole;
+
+    }
+
+
+    /**
+     * Set the common role
+     *
+     * @param commonRole The common role
+     */
+    public void setCommonRole(String commonRole) {
+
+        this.commonRole = commonRole;
+
+    }
+
+
+    /**
+     * Return the connection timeout.
+     */
+    public String getConnectionTimeout() {
+
+        return connectionTimeout;
+
+    }
+
+
+    /**
+     * Set the connection timeout.
+     *
+     * @param timeout The new connection timeout
+     */
+    public void setConnectionTimeout(String timeout) {
+
+        this.connectionTimeout = timeout;
+
+    }
+
+
+    public long getSizeLimit() {
+        return sizeLimit;
+    }
+
+
+    public void setSizeLimit(long sizeLimit) {
+        this.sizeLimit = sizeLimit;
+    }
+
+
+    public int getTimeLimit() {
+        return timeLimit;
+    }
+
+
+    public void setTimeLimit(int timeLimit) {
+        this.timeLimit = timeLimit;
+    }
+
+
+    
+    public boolean isUseDelegatedCredential() {
+        return useDelegatedCredential;
+    }
+
+    public void setUseDelegatedCredential(boolean useDelegatedCredential) {
+        this.useDelegatedCredential = useDelegatedCredential;
+    }
+
+    /**
+     * Return descriptive information about this Realm implementation and
+     * the corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    @Override
+    public String getInfo() {
+
+        return info;
+
+    }
+
+
+    // ---------------------------------------------------------- Realm Methods
+
+
+    /**
+     * Return the Principal associated with the specified username and
+     * credentials, if there is one; otherwise return <code>null</code>.
+     *
+     * If there are any errors with the JDBC connection, executing
+     * the query or anything we return null (don't authenticate). This
+     * event is also logged, and the connection will be closed so that
+     * a subsequent request will automatically re-open it.
+     *
+     * @param username Username of the Principal to look up
+     * @param credentials Password or other credentials to use in
+     *  authenticating this username
+     */
+    @Override
+    public Principal authenticate(String username, String credentials) {
+
+        DirContext context = null;
+        Principal principal = null;
+
+        try {
+
+            // Ensure that we have a directory context available
+            context = open();
+
+            // Occassionally the directory context will timeout.  Try one more
+            // time before giving up.
+            try {
+
+                // Authenticate the specified username if possible
+                principal = authenticate(context, username, credentials);
+
+            } catch (NullPointerException e) {
+                /* BZ 42449 - Kludge Sun's LDAP provider
+                   with broken SSL
+                */
+                // log the exception so we know it's there.
+                containerLog.warn(sm.getString("jndiRealm.exception"), e);
+
+                // close the connection so we know it will be reopened.
+                if (context != null)
+                    close(context);
+
+                // open a new directory context.
+                context = open();
+
+                // Try the authentication again.
+                principal = authenticate(context, username, credentials);
+
+            } catch (CommunicationException e) {
+
+                // log the exception so we know it's there.
+                containerLog.warn(sm.getString("jndiRealm.exception"), e);
+
+                // close the connection so we know it will be reopened.
+                if (context != null)
+                    close(context);
+
+                // open a new directory context.
+                context = open();
+
+                // Try the authentication again.
+                principal = authenticate(context, username, credentials);
+
+            } catch (ServiceUnavailableException e) {
+
+                // log the exception so we know it's there.
+                containerLog.warn(sm.getString("jndiRealm.exception"), e);
+
+                // close the connection so we know it will be reopened.
+                if (context != null)
+                    close(context);
+
+                // open a new directory context.
+                context = open();
+
+                // Try the authentication again.
+                principal = authenticate(context, username, credentials);
+
+            }
+
+
+            // Release this context
+            release(context);
+
+            // Return the authenticated Principal (if any)
+            return (principal);
+
+        } catch (NamingException e) {
+
+            // Log the problem for posterity
+            containerLog.error(sm.getString("jndiRealm.exception"), e);
+
+            // Close the connection so that it gets reopened next time
+            if (context != null)
+                close(context);
+
+            // Return "not authenticated" for this request
+            if (containerLog.isDebugEnabled())
+                containerLog.debug("Returning null principal.");
+            return (null);
+
+        }
+
+    }
+
+
+    // -------------------------------------------------------- Package Methods
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Return the Principal associated with the specified username and
+     * credentials, if there is one; otherwise return <code>null</code>.
+     *
+     * @param context The directory context
+     * @param username Username of the Principal to look up
+     * @param credentials Password or other credentials to use in
+     *  authenticating this username
+     *
+     * @exception NamingException if a directory server error occurs
+     */
+    public synchronized Principal authenticate(DirContext context,
+                                               String username,
+                                               String credentials)
+        throws NamingException {
+
+        if (username == null || username.equals("")
+            || credentials == null || credentials.equals("")) {
+            if (containerLog.isDebugEnabled())
+                containerLog.debug("username null or empty: returning null principal.");
+            return (null);
+        }
+
+        if (userPatternArray != null) {
+            for (int curUserPattern = 0;
+                 curUserPattern < userPatternFormatArray.length;
+                 curUserPattern++) {
+                // Retrieve user information
+                User user = getUser(context, username, credentials, curUserPattern);
+                if (user != null) {
+                    try {
+                        // Check the user's credentials
+                        if (checkCredentials(context, user, credentials)) {
+                            // Search for additional roles
+                            List<String> roles = getRoles(context, user);
+                            if (containerLog.isDebugEnabled()) {
+                                Iterator<String> it = roles.iterator();
+                                // TODO: Use a single log message
+                                while (it.hasNext()) {
+                                    containerLog.debug("Found role: " + it.next());
+                                }
+                            }
+                            return (new GenericPrincipal(username,
+                                                         credentials,
+                                                         roles));
+                        }
+                    } catch (InvalidNameException ine) {
+                        // Log the problem for posterity
+                        containerLog.warn(sm.getString("jndiRealm.exception"), ine);
+                        // ignore; this is probably due to a name not fitting
+                        // the search path format exactly, as in a fully-
+                        // qualified name being munged into a search path
+                        // that already contains cn= or vice-versa
+                    }
+                }
+            }
+            return null;
+        } else {
+            // Retrieve user information
+            User user = getUser(context, username, credentials);
+            if (user == null)
+                return (null);
+
+            // Check the user's credentials
+            if (!checkCredentials(context, user, credentials))
+                return (null);
+
+            // Search for additional roles
+            List<String> roles = getRoles(context, user);
+            if (containerLog.isDebugEnabled()) {
+                Iterator<String> it = roles.iterator();
+                // TODO: Use a single log message
+                while (it.hasNext()) {
+                    containerLog.debug("Found role: " + it.next());
+                }
+            }
+
+            // Create and return a suitable Principal for this user
+            return (new GenericPrincipal(username, credentials, roles));
+        }
+    }
+
+
+    /**
+     * Return a User object containing information about the user
+     * with the specified username, if found in the directory;
+     * otherwise return <code>null</code>.
+     *
+     * @param context The directory context
+     * @param username Username to be looked up
+     *
+     * @exception NamingException if a directory server error occurs
+     *
+     * @see #getUser(DirContext, String, String, int)
+     */
+    protected User getUser(DirContext context, String username)
+        throws NamingException {
+
+        return getUser(context, username, null, -1);
+    }
+
+
+    /**
+     * Return a User object containing information about the user
+     * with the specified username, if found in the directory;
+     * otherwise return <code>null</code>.
+     *
+     * @param context The directory context
+     * @param username Username to be looked up
+     * @param credentials User credentials (optional)
+     *
+     * @exception NamingException if a directory server error occurs
+     *
+     * @see #getUser(DirContext, String, String, int)
+     */
+    protected User getUser(DirContext context, String username, String credentials)
+        throws NamingException {
+
+        return getUser(context, username, credentials, -1);
+    }
+
+
+    /**
+     * Return a User object containing information about the user
+     * with the specified username, if found in the directory;
+     * otherwise return <code>null</code>.
+     *
+     * If the <code>userPassword</code> configuration attribute is
+     * specified, the value of that attribute is retrieved from the
+     * user's directory entry. If the <code>userRoleName</code>
+     * configuration attribute is specified, all values of that
+     * attribute are retrieved from the directory entry.
+     *
+     * @param context The directory context
+     * @param username Username to be looked up
+     * @param credentials User credentials (optional)
+     * @param curUserPattern Index into userPatternFormatArray
+     *
+     * @exception NamingException if a directory server error occurs
+     */
+    protected User getUser(DirContext context, String username,
+                           String credentials, int curUserPattern)
+        throws NamingException {
+
+        User user = null;
+
+        // Get attributes to retrieve from user entry
+        ArrayList<String> list = new ArrayList<String>();
+        if (userPassword != null)
+            list.add(userPassword);
+        if (userRoleName != null)
+            list.add(userRoleName);
+        String[] attrIds = new String[list.size()];
+        list.toArray(attrIds);
+
+        // Use pattern or search for user entry
+        if (userPatternFormatArray != null && curUserPattern >= 0) {
+            user = getUserByPattern(context, username, credentials, attrIds, curUserPattern);
+        } else {
+            user = getUserBySearch(context, username, attrIds);
+        }
+
+        return user;
+    }
+
+
+    /**
+     * Use the distinguished name to locate the directory
+     * entry for the user with the specified username and
+     * return a User object; otherwise return <code>null</code>.
+     *
+     * @param context The directory context
+     * @param username The username
+     * @param attrIds String[]containing names of attributes to
+     * @param dn Distinguished name of the user
+     * retrieve.
+     *
+     * @exception NamingException if a directory server error occurs
+     */
+    protected User getUserByPattern(DirContext context,
+                                    String username,
+                                    String[] attrIds,
+                                    String dn)
+        throws NamingException {
+
+        // If no attributes are requested, no need to look for them
+        if (attrIds == null || attrIds.length == 0) {
+            return new User(username, dn, null, null);
+        }
+
+        // Get required attributes from user entry
+        Attributes attrs = null;
+        try {
+            attrs = context.getAttributes(dn, attrIds);
+        } catch (NameNotFoundException e) {
+            return (null);
+        }
+        if (attrs == null)
+            return (null);
+
+        // Retrieve value of userPassword
+        String password = null;
+        if (userPassword != null)
+            password = getAttributeValue(userPassword, attrs);
+
+        // Retrieve values of userRoleName attribute
+        ArrayList<String> roles = null;
+        if (userRoleName != null)
+            roles = addAttributeValues(userRoleName, attrs, roles);
+
+        return new User(username, dn, password, roles);
+    }
+
+
+    /**
+     * Use the <code>UserPattern</code> configuration attribute to
+     * locate the directory entry for the user with the specified
+     * username and return a User object; otherwise return
+     * <code>null</code>.
+     *
+     * @param context The directory context
+     * @param username The username
+     * @param credentials User credentials (optional)
+     * @param attrIds String[]containing names of attributes to
+     * @param curUserPattern Index into userPatternFormatArray
+     *
+     * @exception NamingException if a directory server error occurs
+     * @see #getUserByPattern(DirContext, String, String[], String)
+     */
+    protected User getUserByPattern(DirContext context,
+                                    String username,
+                                    String credentials,
+                                    String[] attrIds,
+                                    int curUserPattern)
+        throws NamingException {
+
+        User user = null;
+
+        if (username == null || userPatternFormatArray[curUserPattern] == null)
+            return (null);
+
+        // Form the dn from the user pattern
+        String dn = userPatternFormatArray[curUserPattern].format(new String[] { username });
+
+        try {
+            user = getUserByPattern(context, username, attrIds, dn);
+        } catch (NameNotFoundException e) {
+            return (null);
+        } catch (NamingException e) {
+            // If the getUserByPattern() call fails, try it again with the
+            // credentials of the user that we're searching for
+            try {
+                userCredentialsAdd(context, dn, credentials);
+
+                user = getUserByPattern(context, username, attrIds, dn);
+            } finally {
+                userCredentialsRemove(context);
+            }
+        }
+        return user;
+    }
+
+
+    /**
+     * Search the directory to return a User object containing
+     * information about the user with the specified username, if
+     * found in the directory; otherwise return <code>null</code>.
+     *
+     * @param context The directory context
+     * @param username The username
+     * @param attrIds String[]containing names of attributes to retrieve.
+     *
+     * @exception NamingException if a directory server error occurs
+     */
+    protected User getUserBySearch(DirContext context,
+                                   String username,
+                                   String[] attrIds)
+        throws NamingException {
+
+        if (username == null || userSearchFormat == null)
+            return (null);
+
+        // Form the search filter
+        String filter = userSearchFormat.format(new String[] { username });
+
+        // Set up the search controls
+        SearchControls constraints = new SearchControls();
+
+        if (userSubtree) {
+            constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
+        }
+        else {
+            constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+        }
+
+        constraints.setCountLimit(sizeLimit);
+        constraints.setTimeLimit(timeLimit);
+
+        // Specify the attributes to be retrieved
+        if (attrIds == null)
+            attrIds = new String[0];
+        constraints.setReturningAttributes(attrIds);
+
+        NamingEnumeration<SearchResult> results =
+            context.search(userBase, filter, constraints);
+
+
+        // Fail if no entries found
+        try {
+            if (results == null || !results.hasMore()) {
+                return (null);
+            }
+        } catch (PartialResultException ex) {
+            if (!adCompat)
+                throw ex;
+            else
+                return (null);
+        }
+
+        // Get result for the first entry found
+        SearchResult result = results.next();
+
+        // Check no further entries were found
+        try {
+            if (results.hasMore()) {
+                if(containerLog.isInfoEnabled())
+                    containerLog.info("username " + username + " has multiple entries");
+                return (null);
+            }
+        } catch (PartialResultException ex) {
+            if (!adCompat)
+                throw ex;
+        }
+
+        String dn = getDistinguishedName(context, userBase, result);
+
+        if (containerLog.isTraceEnabled())
+            containerLog.trace("  entry found for " + username + " with dn " + dn);
+
+        // Get the entry's attributes
+        Attributes attrs = result.getAttributes();
+        if (attrs == null)
+            return null;
+
+        // Retrieve value of userPassword
+        String password = null;
+        if (userPassword != null)
+            password = getAttributeValue(userPassword, attrs);
+
+        // Retrieve values of userRoleName attribute
+        ArrayList<String> roles = null;
+        if (userRoleName != null)
+            roles = addAttributeValues(userRoleName, attrs, roles);
+
+        return new User(username, dn, password, roles);
+    }
+
+
+    /**
+     * Check whether the given User can be authenticated with the
+     * given credentials. If the <code>userPassword</code>
+     * configuration attribute is specified, the credentials
+     * previously retrieved from the directory are compared explicitly
+     * with those presented by the user. Otherwise the presented
+     * credentials are checked by binding to the directory as the
+     * user.
+     *
+     * @param context The directory context
+     * @param user The User to be authenticated
+     * @param credentials The credentials presented by the user
+     *
+     * @exception NamingException if a directory server error occurs
+     */
+    protected boolean checkCredentials(DirContext context,
+                                     User user,
+                                     String credentials)
+         throws NamingException {
+
+         boolean validated = false;
+
+         if (userPassword == null) {
+             validated = bindAsUser(context, user, credentials);
+         } else {
+             validated = compareCredentials(context, user, credentials);
+         }
+
+         if (containerLog.isTraceEnabled()) {
+             if (validated) {
+                 containerLog.trace(sm.getString("jndiRealm.authenticateSuccess",
+                                  user.getUserName()));
+             } else {
+                 containerLog.trace(sm.getString("jndiRealm.authenticateFailure",
+                                  user.getUserName()));
+             }
+         }
+         return (validated);
+     }
+
+
+    /**
+     * Check whether the credentials presented by the user match those
+     * retrieved from the directory.
+     *
+     * @param context The directory context
+     * @param info The User to be authenticated
+     * @param credentials Authentication credentials
+     *
+     * @exception NamingException if a directory server error occurs
+     */
+    protected boolean compareCredentials(DirContext context,
+                                         User info,
+                                         String credentials)
+        throws NamingException {
+
+        if (info == null || credentials == null)
+            return (false);
+
+        String password = info.getPassword();
+        if (password == null)
+            return (false);
+
+        // Validate the credentials specified by the user
+        if (containerLog.isTraceEnabled())
+            containerLog.trace("  validating credentials");
+
+        boolean validated = false;
+        if (hasMessageDigest()) {
+            // Some directories prefix the password with the hash type
+            // The string is in a format compatible with Base64.encode not
+            // the Hex encoding of the parent class.
+            if (password.startsWith("{MD5}") || password.startsWith("{SHA}")) {
+                /* sync since super.digest() does this same thing */
+                synchronized (this) {
+                    password = password.substring(5);
+                    md.reset();
+                    md.update(credentials.getBytes());
+                    String digestedPassword = Base64.encode(md.digest());
+                    validated = password.equals(digestedPassword);
+                }
+            } else if (password.startsWith("{SSHA}")) {
+                // Bugzilla 32938
+                /* sync since super.digest() does this same thing */
+                synchronized (this) {
+                    password = password.substring(6);
+
+                    md.reset();
+                    md.update(credentials.getBytes());
+
+                    // Decode stored password.
+                    ByteChunk pwbc = new ByteChunk(password.length());
+                    try {
+                        pwbc.append(password.getBytes(), 0, password.length());
+                    } catch (IOException e) {
+                        // Should never happen
+                        containerLog.error("Could not append password bytes to chunk: ", e);
+                    }
+
+                    CharChunk decoded = new CharChunk();
+                    Base64.decode(pwbc, decoded);
+                    char[] pwarray = decoded.getBuffer();
+
+                    // Split decoded password into hash and salt.
+                    final int saltpos = 20;
+                    byte[] hash = new byte[saltpos];
+                    for (int i=0; i< hash.length; i++) {
+                        hash[i] = (byte) pwarray[i];
+                    }
+
+                    byte[] salt = new byte[pwarray.length - saltpos];
+                    for (int i=0; i< salt.length; i++)
+                        salt[i] = (byte)pwarray[i+saltpos];
+
+                    md.update(salt);
+                    byte[] dp = md.digest();
+
+                    validated = Arrays.equals(dp, hash);
+                } // End synchronized(this) block
+            } else {
+                // Hex hashes should be compared case-insensitive
+                validated = (digest(credentials).equalsIgnoreCase(password));
+            }
+        } else
+            validated = (digest(credentials).equals(password));
+        return (validated);
+
+    }
+
+
+    /**
+     * Check credentials by binding to the directory as the user
+     *
+     * @param context The directory context
+     * @param user The User to be authenticated
+     * @param credentials Authentication credentials
+     *
+     * @exception NamingException if a directory server error occurs
+     */
+     protected boolean bindAsUser(DirContext context,
+                                  User user,
+                                  String credentials)
+         throws NamingException {
+
+         if (credentials == null || user == null)
+             return (false);
+
+         String dn = user.getDN();
+         if (dn == null)
+             return (false);
+
+         // Validate the credentials specified by the user
+         if (containerLog.isTraceEnabled()) {
+             containerLog.trace("  validating credentials by binding as the user");
+        }
+
+        userCredentialsAdd(context, dn, credentials);
+
+        // Elicit an LDAP bind operation
+        boolean validated = false;
+        try {
+            if (containerLog.isTraceEnabled()) {
+                containerLog.trace("  binding as "  + dn);
+            }
+            context.getAttributes("", null);
+            validated = true;
+        }
+        catch (AuthenticationException e) {
+            if (containerLog.isTraceEnabled()) {
+                containerLog.trace("  bind attempt failed");
+            }
+        }
+
+        userCredentialsRemove(context);
+
+        return (validated);
+    }
+
+     /**
+      * Configure the context to use the provided credentials for
+      * authentication.
+      *
+      * @param context      DirContext to configure
+      * @param dn           Distinguished name of user
+      * @param credentials  Credentials of user
+      */
+    private void userCredentialsAdd(DirContext context, String dn,
+            String credentials) throws NamingException {
+        // Set up security environment to bind as the user
+        context.addToEnvironment(Context.SECURITY_PRINCIPAL, dn);
+        context.addToEnvironment(Context.SECURITY_CREDENTIALS, credentials);
+    }
+
+    /**
+     * Configure the context to use {@link #connectionName} and
+     * {@link #connectionPassword} if specified or an anonymous connection if
+     * those attributes are not specified.
+     * 
+      * @param context      DirContext to configure
+     */
+    private void userCredentialsRemove(DirContext context)
+            throws NamingException {
+        // Restore the original security environment
+        if (connectionName != null) {
+            context.addToEnvironment(Context.SECURITY_PRINCIPAL,
+                                     connectionName);
+        } else {
+            context.removeFromEnvironment(Context.SECURITY_PRINCIPAL);
+        }
+
+        if (connectionPassword != null) {
+            context.addToEnvironment(Context.SECURITY_CREDENTIALS,
+                                     connectionPassword);
+        }
+        else {
+            context.removeFromEnvironment(Context.SECURITY_CREDENTIALS);
+        }
+    }
+
+    /**
+     * Return a List of roles associated with the given User.  Any
+     * roles present in the user's directory entry are supplemented by
+     * a directory search. If no roles are associated with this user,
+     * a zero-length List is returned.
+     *
+     * @param context The directory context we are searching
+     * @param user The User to be checked
+     *
+     * @exception NamingException if a directory server error occurs
+     */
+    protected List<String> getRoles(DirContext context, User user)
+        throws NamingException {
+
+        if (user == null)
+            return (null);
+
+        String dn = user.getDN();
+        String username = user.getUserName();
+
+        if (dn == null || username == null)
+            return (null);
+
+        if (containerLog.isTraceEnabled())
+            containerLog.trace("  getRoles(" + dn + ")");
+
+        // Start with roles retrieved from the user entry
+        List<String> list = new ArrayList<String>(); 
+        List<String> userRoles = user.getRoles();
+        if (userRoles != null) {
+            list.addAll(userRoles); 
+        }
+        if (commonRole != null)
+            list.add(commonRole);
+
+        if (containerLog.isTraceEnabled()) {
+            containerLog.trace("  Found " + list.size() + " user internal roles");
+            for (int i=0; i<list.size(); i++)
+                containerLog.trace(  "  Found user internal role " + list.get(i));
+        }
+
+        // Are we configured to do role searches?
+        if ((roleFormat == null) || (roleName == null))
+            return (list);
+        
+        // Set up parameters for an appropriate search
+        String filter = roleFormat.format(new String[] { doRFC2254Encoding(dn), username });
+        SearchControls controls = new SearchControls();
+        if (roleSubtree)
+            controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
+        else
+            controls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+        controls.setReturningAttributes(new String[] {roleName});
+
+        String base = null;
+        if (roleBaseFormat != null) {
+            NameParser np = context.getNameParser("");
+            Name name = np.parse(dn);
+            String nameParts[] = new String[name.size()];
+            for (int i = 0; i < name.size(); i++) {
+                nameParts[i] = name.get(i);
+            }
+            base = roleBaseFormat.format(nameParts);
+        }
+
+        // Perform the configured search and process the results
+        NamingEnumeration<SearchResult> results = null;
+        try {
+            if (roleSearchAsUser) {
+                userCredentialsAdd(context, dn, user.getPassword());
+            }
+            results = context.search(base, filter, controls);
+        } finally {
+            if (roleSearchAsUser) {
+                userCredentialsRemove(context);
+            }
+        }
+
+        if (results == null)
+            return (list);  // Should never happen, but just in case ...
+
+        HashMap<String, String> groupMap = new HashMap<String, String>();
+        try {
+            while (results.hasMore()) {
+                SearchResult result = results.next();
+                Attributes attrs = result.getAttributes();
+                if (attrs == null)
+                    continue;
+                String dname = getDistinguishedName(context, roleBase, result);
+                String name = getAttributeValue(roleName, attrs);
+                if (name != null && dname != null) {
+                    groupMap.put(dname, name);
+                }
+            }
+        } catch (PartialResultException ex) {
+            if (!adCompat)
+                throw ex;
+        }
+
+        Set<String> keys = groupMap.keySet();
+        if (containerLog.isTraceEnabled()) {
+            containerLog.trace("  Found " + keys.size() + " direct roles");
+            for (String key: keys) {
+                containerLog.trace(  "  Found direct role " + key + " -> " + groupMap.get(key));
+            }
+        }
+
+        // if nested group search is enabled, perform searches for nested groups until no new group is found
+        if (getRoleNested()) {
+
+            // The following efficient algorithm is known as memberOf Algorithm, as described in "Practices in 
+            // Directory Groups". It avoids group slurping and handles cyclic group memberships as well.
+            // See http://middleware.internet2.edu/dir/ for details
+
+            Map<String, String> newGroups = new HashMap<String,String>(groupMap);
+            while (!newGroups.isEmpty()) {
+                Map<String, String> newThisRound = new HashMap<String, String>(); // Stores the groups we find in this iteration
+
+                for (Entry<String, String> group : newGroups.entrySet()) {
+                    filter = roleFormat.format(new String[] { group.getKey(), group.getValue() });
+
+                    if (containerLog.isTraceEnabled()) {
+                        containerLog.trace("Perform a nested group search with base "+ roleBase + " and filter " + filter);
+                    }
+
+                    results = context.search(roleBase, filter, controls);
+
+                    try {
+                        while (results.hasMore()) {
+                            SearchResult result = results.next();
+                            Attributes attrs = result.getAttributes();
+                            if (attrs == null)
+                                continue;
+                            String dname = getDistinguishedName(context, roleBase, result);
+                            String name = getAttributeValue(roleName, attrs);
+                            if (name != null && dname != null && !groupMap.keySet().contains(dname)) {
+                                groupMap.put(dname, name);
+                                newThisRound.put(dname, name);
+
+                                if (containerLog.isTraceEnabled()) {
+                                    containerLog.trace("  Found nested role " + dname + " -> " + name);
+                                }
+
+                            }
+                         }
+                    } catch (PartialResultException ex) {
+                        if (!adCompat)
+                            throw ex;
+                    }
+                }
+
+                newGroups = newThisRound;
+            }
+        }
+
+        list.addAll(groupMap.values());
+        return list;
+    }
+
+
+    /**
+     * Return a String representing the value of the specified attribute.
+     *
+     * @param attrId Attribute name
+     * @param attrs Attributes containing the required value
+     *
+     * @exception NamingException if a directory server error occurs
+     */
+    private String getAttributeValue(String attrId, Attributes attrs)
+        throws NamingException {
+
+        if (containerLog.isTraceEnabled())
+            containerLog.trace("  retrieving attribute " + attrId);
+
+        if (attrId == null || attrs == null)
+            return null;
+
+        Attribute attr = attrs.get(attrId);
+        if (attr == null)
+            return (null);
+        Object value = attr.get();
+        if (value == null)
+            return (null);
+        String valueString = null;
+        if (value instanceof byte[])
+            valueString = new String((byte[]) value);
+        else
+            valueString = value.toString();
+
+        return valueString;
+    }
+
+
+    /**
+     * Add values of a specified attribute to a list
+     *
+     * @param attrId Attribute name
+     * @param attrs Attributes containing the new values
+     * @param values ArrayList containing values found so far
+     *
+     * @exception NamingException if a directory server error occurs
+     */
+    private ArrayList<String> addAttributeValues(String attrId,
+                                         Attributes attrs,
+                                         ArrayList<String> values)
+        throws NamingException{
+
+        if (containerLog.isTraceEnabled())
+            containerLog.trace("  retrieving values for attribute " + attrId);
+        if (attrId == null || attrs == null)
+            return values;
+        if (values == null)
+            values = new ArrayList<String>();
+        Attribute attr = attrs.get(attrId);
+        if (attr == null)
+            return (values);
+        NamingEnumeration<?> e = attr.getAll();
+        try {
+            while(e.hasMore()) {
+                String value = (String)e.next();
+                values.add(value);
+            }
+        } catch (PartialResultException ex) {
+            if (!adCompat)
+                throw ex;
+        }
+        return values;
+    }
+
+
+    /**
+     * Close any open connection to the directory server for this Realm.
+     *
+     * @param context The directory context to be closed
+     */
+    protected void close(DirContext context) {
+
+        // Do nothing if there is no opened connection
+        if (context == null)
+            return;
+
+        // Close our opened connection
+        try {
+            if (containerLog.isDebugEnabled())
+                containerLog.debug("Closing directory context");
+            context.close();
+        } catch (NamingException e) {
+            containerLog.error(sm.getString("jndiRealm.close"), e);
+        }
+        this.context = null;
+
+    }
+
+
+    /**
+     * Return a short name for this Realm implementation.
+     */
+    @Override
+    protected String getName() {
+
+        return (name);
+
+    }
+
+
+    /**
+     * Return the password associated with the given principal's user name.
+     */
+    @Override
+    protected String getPassword(String username) {
+
+        return (null);
+
+    }
+
+    /**
+     * Return the Principal associated with the given user name.
+     */
+    @Override
+    protected Principal getPrincipal(String username) {
+        return getPrincipal(username, null);
+    }
+    
+    @Override
+    protected Principal getPrincipal(String username,
+            GSSCredential gssCredential) {
+
+        DirContext context = null;
+        Principal principal = null;
+
+        try {
+
+            // Ensure that we have a directory context available
+            context = open();
+
+            // Occasionally the directory context will timeout.  Try one more
+            // time before giving up.
+            try {
+
+                // Authenticate the specified username if possible
+                principal = getPrincipal(context, username, gssCredential);
+
+            } catch (CommunicationException e) {
+
+                // log the exception so we know it's there.
+                containerLog.warn(sm.getString("jndiRealm.exception"), e);
+
+                // close the connection so we know it will be reopened.
+                if (context != null)
+                    close(context);
+
+                // open a new directory context.
+                context = open();
+
+                // Try the authentication again.
+                principal = getPrincipal(context, username, gssCredential);
+
+            } catch (ServiceUnavailableException e) {
+
+                // log the exception so we know it's there.
+                containerLog.warn(sm.getString("jndiRealm.exception"), e);
+
+                // close the connection so we know it will be reopened.
+                if (context != null)
+                    close(context);
+
+                // open a new directory context.
+                context = open();
+
+                // Try the authentication again.
+                principal = getPrincipal(context, username, gssCredential);
+
+            }
+
+
+            // Release this context
+            release(context);
+
+            // Return the authenticated Principal (if any)
+            return (principal);
+
+        } catch (NamingException e) {
+
+            // Log the problem for posterity
+            containerLog.error(sm.getString("jndiRealm.exception"), e);
+
+            // Close the connection so that it gets reopened next time
+            if (context != null)
+                close(context);
+
+            // Return "not authenticated" for this request
+            return (null);
+
+        }
+
+
+    }
+
+
+    /**
+     * Return the Principal associated with the given user name.
+     */
+    protected synchronized Principal getPrincipal(DirContext context,
+            String username, GSSCredential gssCredential)
+        throws NamingException {
+
+        User user = null;
+        List<String> roles = null;
+
+        try {
+            if (gssCredential != null && isUseDelegatedCredential()) {
+                // Set up context
+                context.addToEnvironment(
+                        Context.SECURITY_AUTHENTICATION, "GSSAPI");
+                context.addToEnvironment(
+                        "javax.security.sasl.server.authentication", "true");
+                context.addToEnvironment(
+                        "javax.security.sasl.qop", "auth-conf");
+                // Note: Subject already set in SPNEGO authenticator so no need
+                //       for Subject.doAs() here
+            }
+            user = getUser(context, username);
+            if (user != null) {
+                roles = getRoles(context, user);
+            }
+        } finally {
+            try {
+                context.removeFromEnvironment(
+                        Context.SECURITY_AUTHENTICATION);
+            } catch (NamingException e) {
+                // Ignore
+            }
+            try {
+                context.removeFromEnvironment(
+                        "javax.security.sasl.server.authentication");
+            } catch (NamingException e) {
+                // Ignore
+            }
+            try {
+                context.removeFromEnvironment(
+                        "javax.security.sasl.qop");
+            } catch (NamingException e) {
+                // Ignore
+            }
+        }
+
+        if (user != null) {
+            return new GenericPrincipal(user.getUserName(), user.getPassword(),
+                    roles, null, null, gssCredential);
+        }
+        
+        return null;
+    }
+
+    /**
+     * Open (if necessary) and return a connection to the configured
+     * directory server for this Realm.
+     *
+     * @exception NamingException if a directory server error occurs
+     */
+    protected DirContext open() throws NamingException {
+
+        // Do nothing if there is a directory server connection already open
+        if (context != null)
+            return (context);
+
+        try {
+
+            // Ensure that we have a directory context available
+            context = new InitialDirContext(getDirectoryContextEnvironment());
+
+        } catch (Exception e) {
+
+            connectionAttempt = 1;
+
+            // log the first exception.
+            containerLog.warn(sm.getString("jndiRealm.exception"), e);
+
+            // Try connecting to the alternate url.
+            context = new InitialDirContext(getDirectoryContextEnvironment());
+
+        } finally {
+
+            // reset it in case the connection times out.
+            // the primary may come back.
+            connectionAttempt = 0;
+
+        }
+
+        return (context);
+
+    }
+
+    /**
+     * Create our directory context configuration.
+     *
+     * @return java.util.Hashtable the configuration for the directory context.
+     */
+    protected Hashtable<String,String> getDirectoryContextEnvironment() {
+
+        Hashtable<String,String> env = new Hashtable<String,String>();
+
+        // Configure our directory context environment.
+        if (containerLog.isDebugEnabled() && connectionAttempt == 0)
+            containerLog.debug("Connecting to URL " + connectionURL);
+        else if (containerLog.isDebugEnabled() && connectionAttempt > 0)
+            containerLog.debug("Connecting to URL " + alternateURL);
+        env.put(Context.INITIAL_CONTEXT_FACTORY, contextFactory);
+        if (connectionName != null)
+            env.put(Context.SECURITY_PRINCIPAL, connectionName);
+        if (connectionPassword != null)
+            env.put(Context.SECURITY_CREDENTIALS, connectionPassword);
+        if (connectionURL != null && connectionAttempt == 0)
+            env.put(Context.PROVIDER_URL, connectionURL);
+        else if (alternateURL != null && connectionAttempt > 0)
+            env.put(Context.PROVIDER_URL, alternateURL);
+        if (authentication != null)
+            env.put(Context.SECURITY_AUTHENTICATION, authentication);
+        if (protocol != null)
+            env.put(Context.SECURITY_PROTOCOL, protocol);
+        if (referrals != null)
+            env.put(Context.REFERRAL, referrals);
+        if (derefAliases != null)
+            env.put(JNDIRealm.DEREF_ALIASES, derefAliases);
+        if (connectionTimeout != null)
+            env.put("com.sun.jndi.ldap.connect.timeout", connectionTimeout);
+
+        return env;
+
+    }
+
+
+    /**
+     * Release our use of this connection so that it can be recycled.
+     *
+     * @param context The directory context to release
+     */
+    protected void release(DirContext context) {
+
+        // NO-OP since we are not pooling anything
+
+    }
+
+
+    // ------------------------------------------------------ Lifecycle Methods
+
+
+    /**
+     * Prepare for the beginning of active use of the public methods of 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 void startInternal() throws LifecycleException {
+
+        // Validate that we can open our connection
+        try {
+            open();
+        } catch (NamingException e) {
+            throw new LifecycleException(sm.getString("jndiRealm.open"), e);
+        }
+
+        super.startInternal();
+    }
+
+
+    /**
+     * Gracefully terminate the active use of the public methods of this
+     * component and implement the requirements of
+     * {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
+     *
+     * @exception LifecycleException if this component detects a fatal error
+     *  that needs to be reported
+     */
+     @Override
+    protected void stopInternal() throws LifecycleException {
+
+        super.stopInternal();
+
+        // Close any open directory server connection
+        close(this.context);
+
+    }
+
+    /**
+     * Given a string containing LDAP patterns for user locations (separated by
+     * parentheses in a pseudo-LDAP search string format -
+     * "(location1)(location2)", returns an array of those paths.  Real LDAP
+     * search strings are supported as well (though only the "|" "OR" type).
+     *
+     * @param userPatternString - a string LDAP search paths surrounded by
+     * parentheses
+     */
+    protected String[] parseUserPatternString(String userPatternString) {
+
+        if (userPatternString != null) {
+            ArrayList<String> pathList = new ArrayList<String>();
+            int startParenLoc = userPatternString.indexOf('(');
+            if (startParenLoc == -1) {
+                // no parens here; return whole thing
+                return new String[] {userPatternString};
+            }
+            int startingPoint = 0;
+            while (startParenLoc > -1) {
+                int endParenLoc = 0;
+                // weed out escaped open parens and parens enclosing the
+                // whole statement (in the case of valid LDAP search
+                // strings: (|(something)(somethingelse))
+                while ( (userPatternString.charAt(startParenLoc + 1) == '|') ||
+                        (startParenLoc != 0 && userPatternString.charAt(startParenLoc - 1) == '\\') ) {
+                    startParenLoc = userPatternString.indexOf("(", startParenLoc+1);
+                }
+                endParenLoc = userPatternString.indexOf(")", startParenLoc+1);
+                // weed out escaped end-parens
+                while (userPatternString.charAt(endParenLoc - 1) == '\\') {
+                    endParenLoc = userPatternString.indexOf(")", endParenLoc+1);
+                }
+                String nextPathPart = userPatternString.substring
+                    (startParenLoc+1, endParenLoc);
+                pathList.add(nextPathPart);
+                startingPoint = endParenLoc+1;
+                startParenLoc = userPatternString.indexOf('(', startingPoint);
+            }
+            return pathList.toArray(new String[] {});
+        }
+        return null;
+
+    }
+
+
+    /**
+     * Given an LDAP search string, returns the string with certain characters
+     * escaped according to RFC 2254 guidelines.
+     * The character mapping is as follows:
+     *     char ->  Replacement
+     *    ---------------------------
+     *     *  -> \2a
+     *     (  -> \28
+     *     )  -> \29
+     *     \  -> \5c
+     *     \0 -> \00
+     * @param inString string to escape according to RFC 2254 guidelines
+     * @return String the escaped/encoded result
+     */
+    protected String doRFC2254Encoding(String inString) {
+        StringBuilder buf = new StringBuilder(inString.length());
+        for (int i = 0; i < inString.length(); i++) {
+            char c = inString.charAt(i);
+            switch (c) {
+                case '\\':
+                    buf.append("\\5c");
+                    break;
+                case '*':
+                    buf.append("\\2a");
+                    break;
+                case '(':
+                    buf.append("\\28");
+                    break;
+                case ')':
+                    buf.append("\\29");
+                    break;
+                case '\0':
+                    buf.append("\\00");
+                    break;
+                default:
+                    buf.append(c);
+                    break;
+            }
+        }
+        return buf.toString();
+    }
+
+
+    /**
+     * Returns the distinguished name of a search result.
+     *
+     * @param context Our DirContext
+     * @param base The base DN
+     * @param result The search result
+     * @return String containing the distinguished name
+     */
+    protected String getDistinguishedName(DirContext context, String base,
+            SearchResult result) throws NamingException {
+        // Get the entry's distinguished name.  For relative results, this means
+        // we need to composite a name with the base name, the context name, and
+        // the result name.  For non-relative names, use the returned name.
+        if (result.isRelative()) {
+           if (containerLog.isTraceEnabled()) {
+               containerLog.trace("  search returned relative name: " +
+                       result.getName());
+           }
+           NameParser parser = context.getNameParser("");
+           Name contextName = parser.parse(context.getNameInNamespace());
+           Name baseName = parser.parse(base);
+   
+           // Bugzilla 32269
+           Name entryName =
+               parser.parse(new CompositeName(result.getName()).get(0));
+   
+           Name name = contextName.addAll(baseName);
+           name = name.addAll(entryName);
+           return name.toString();
+        } else {
+           String absoluteName = result.getName();
+           if (containerLog.isTraceEnabled())
+               containerLog.trace("  search returned absolute name: " +
+                       result.getName());
+           try {
+               // Normalize the name by running it through the name parser.
+               NameParser parser = context.getNameParser("");
+               URI userNameUri = new URI(absoluteName);
+               String pathComponent = userNameUri.getPath();
+               // Should not ever have an empty path component, since that is /{DN}
+               if (pathComponent.length() < 1 ) {
+                   throw new InvalidNameException(
+                           "Search returned unparseable absolute name: " +
+                           absoluteName );
+               }
+               Name name = parser.parse(pathComponent.substring(1));
+               return name.toString();
+           } catch ( URISyntaxException e ) {
+               throw new InvalidNameException(
+                       "Search returned unparseable absolute name: " +
+                       absoluteName );
+           }
+        }
+    }
+
+
+    // ------------------------------------------------------ Private Classes
+
+    /**
+     * A protected class representing a User
+     */
+    protected static class User {
+         
+        private final String username;
+        private final String dn;
+        private final String password;
+        private final List<String> roles;
+
+        public User(String username, String dn, String password,
+                List<String> roles) {
+            this.username = username;
+            this.dn = dn;
+            this.password = password;
+            if (roles == null) {
+                this.roles = Collections.emptyList();
+            } else {
+                this.roles = Collections.unmodifiableList(roles);
+            }
+        }
+    
+        public String getUserName() {
+            return username;
+        }
+         
+        public String getDN() {
+            return dn;
+        }
+        
+        public String getPassword() {
+            return password;
+        }
+         
+        public List<String> getRoles() {
+            return roles;
+        }
+    }
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/realm/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/LocalStrings.properties
new file mode 100644
index 0000000..1db02ec
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/LocalStrings.properties
@@ -0,0 +1,100 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# $Id: LocalStrings.properties,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+
+# language
+
+# package org.apache.catalina.realm
+
+jaasRealm.beginLogin=JAASRealm login requested for username "{0}" using LoginContext for application "{1}"
+jaasRealm.accountExpired=Username "{0}" NOT authenticated due to expired account
+jaasRealm.authenticateFailure=Username "{0}" NOT successfully authenticated
+jaasRealm.authenticateSuccess=Username "{0}" successfully authenticated as Principal "{1}" -- Subject was created too
+jaasRealm.credentialExpired=Username "{0}" NOT authenticated due to expired credential
+jaasRealm.failedLogin=Username "{0}" NOT authenticated due to failed login
+jaasRealm.loginException=Login exception authenticating username "{0}"
+jaasRealm.unexpectedError=Unexpected error
+jaasRealm.loginContextCreated=JAAS LoginContext created for username "{0}"
+jaasRealm.cachePrincipal=User Principal "{0}" cached; has {1} role Principal(s)
+jaasRealm.checkPrincipal=Checking Principal "{0}" [{1}]
+jaasRealm.userPrincipalSuccess=Principal "{0}" is a valid user class. We will use this as the user Principal.
+jaasRealm.userPrincipalFailure=No valid user Principal found
+jaasRealm.rolePrincipalAdd=Adding role Principal "{0}" to this user Principal''s roles
+jaasRealm.rolePrincipalSuccess={0} role Principal(s) found
+jaasRealm.rolePrincipalFailure=No valid role Principals found.
+jaasRealm.isInRole.start=Checking if user Principal "{0}" possesses role "{1}"
+jaasRealm.isInRole.noPrincipalOrRole=No roles Principals found. User Principal or Subject is null, or user Principal not in cache
+jaasRealm.isInRole.principalCached=User Principal has {0} role Principal(s)
+jaasRealm.isInRole.possessesRole=User Principal has a role Principal called "{0}"
+jaasRealm.isInRole.match=Matching role Principal found.
+jaasRealm.isInRole.noMatch=Matching role Principal NOT found.
+jaasCallback.digestpassword=Digested password "{0}" as "{1}"
+jaasCallback.username=Returned username "{0}"
+jaasCallback.password=Returned password "{0}"
+jdbcRealm.authenticateFailure=Username {0} NOT successfully authenticated
+jdbcRealm.authenticateSuccess=Username {0} successfully authenticated
+jdbcRealm.close=Exception closing database connection
+jdbcRealm.exception=Exception performing authentication
+jdbcRealm.getPassword.exception=Exception retrieving password for "{0}"
+jdbcRealm.getRoles.exception=Exception retrieving roles for "{0}"
+jdbcRealm.open=Exception opening database connection
+jdbcRealm.open.invalidurl=Driver "{0}" does not support the url "{1}"
+jndiRealm.authenticateFailure=Username {0} NOT successfully authenticated
+jndiRealm.authenticateSuccess=Username {0} successfully authenticated
+jndiRealm.close=Exception closing directory server connection
+jndiRealm.exception=Exception performing authentication
+jndiRealm.open=Exception opening directory server connection
+memoryRealm.authenticateFailure=Username {0} NOT successfully authenticated
+memoryRealm.authenticateSuccess=Username {0} successfully authenticated
+memoryRealm.loadExist=Memory database file {0} cannot be read
+memoryRealm.loadPath=Loading users from memory database file {0}
+memoryRealm.readXml=Exception while reading memory database file
+memoryRealm.xmlFeatureEncoding=Exception configuring digester to permit java encoding names in XML files. Only IANA encoding names will be supported.
+realmBase.algorithm=Invalid message digest algorithm {0} specified
+realmBase.alreadyStarted=This Realm has already been started
+realmBase.delegatedCredentialFail=Unable to obtain delegated credentials for user [{0}
+realmBase.digest=Error digesting user credentials
+realmBase.forbidden=Access to the requested resource has been denied
+realmBase.hasRoleFailure=Username {0} does NOT have role {1}
+realmBase.hasRoleSuccess=Username {0} has role {1}
+realmBase.notAuthenticated=Configuration error:  Cannot perform access control without an authenticated principal
+realmBase.notStarted=This Realm has not yet been started
+realmBase.authenticateFailure=Username {0} NOT successfully authenticated
+realmBase.authenticateSuccess=Username {0} successfully authenticated
+realmBase.gssNameFail=Failed to extract name from established GSSContext
+userDatabaseRealm.authenticateError=Login configuration error authenticating username {0}
+userDatabaseRealm.lookup=Exception looking up UserDatabase under key {0}
+userDatabaseRealm.noDatabase=No UserDatabase component found under key {0}
+userDatabaseRealm.noEngine=No Engine component found in container hierarchy
+userDatabaseRealm.noGlobal=No global JNDI resources context found
+dataSourceRealm.authenticateFailure=Username {0} NOT successfully authenticated
+dataSourceRealm.authenticateSuccess=Username {0} successfully authenticated
+dataSourceRealm.close=Exception closing database connection
+dataSourceRealm.exception=Exception performing authentication
+dataSourceRealm.getPassword.exception=Exception retrieving password for "{0}"
+dataSourceRealm.getRoles.exception=Exception retrieving roles for "{0}"
+dataSourceRealm.open=Exception opening database connection
+combinedRealm.unexpectedMethod=An unexpected call was made to a method on the combined realm
+combinedRealm.getName=The getName() method should never be called
+combinedRealm.getPassword=The getPassword() method should never be called
+combinedRealm.getPrincipal=The getPrincipal() method should never be called
+combinedRealm.authStart=Attempting to authenticate user "{0}" with realm "{1}"
+combinedRealm.authFailed=Failed to authenticate user "{0}" with realm "{1}"
+combinedRealm.authSucess=Authenticated user "{0}" with realm "{1}"
+combinedRealm.addRealm=Add "{0}" realm, making a total of "{1}" realms
+combinedRealm.realmStartFail=Failed to start "{0}" realm
+lockOutRealm.authLockedUser=An attempt was made to authenticate the locked user "{0}"
+lockOutRealm.removeWarning=User "{0}" was removed from the failed users cache after {1} seconds to keep the cache size within the limit set
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/realm/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/LocalStrings_es.properties
new file mode 100644
index 0000000..ec8ace2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/LocalStrings_es.properties
@@ -0,0 +1,81 @@
+jaasRealm.beginLogin = JAASRealm login requerido para nombre de usuario "{0}" usando LoginContext para aplicaci\u00F3n "{1}"
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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:08:20 rherrmann Exp $
+# language es
+# package org.apache.catalina.realm
+jaasRealm.accountExpired = El usuario {0} NO ha sido autentificado porque ha expirado su cuenta
+jaasRealm.authenticateFailure = Nombre de usuario "{0}" NO autenticado con \u00E9xito
+jaasRealm.authenticateSuccess = Nombre de usuario "{0}" autenticado con \u00E9xito como Principal "{1}" -- Tambi\u00E9n se ha creado el Asunto
+jaasRealm.credentialExpired = El usuario {0} NO ha sido autentificado porque ha expirado su credencial
+jaasRealm.failedLogin = El usuario {0} NO ha sido autentificado porque ha fallado el login
+jaasRealm.loginException = Excepci\u00F3n de Login autenticando nombre de usuario {0}
+jaasRealm.unexpectedError = Error inesperado
+jaasRealm.loginContextCreated = JAAS LoginContext creado para nombre de usuario "{0}"
+jaasRealm.cachePrincipal = Usuario Principal "{0}" en cach\u00E9; tiene {1} papel de Principal
+jaasRealm.checkPrincipal = Revisando Principal "{0}" [{1}]
+jaasRealm.userPrincipalSuccess = El Principal "{0}" es una clase v\u00E1lida de usuario. La vamos a usar como usuario Principal.
+jaasRealm.userPrincipalFailure = No se ha hallado usuario Principal
+jaasRealm.rolePrincipalAdd = A\u00F1adiend papel Principal "{0}" a los papeles de este usuario Principal
+jaasRealm.rolePrincipalSuccess = hallado {0} papeles de Principal
+jaasRealm.rolePrincipalFailure = No se han hallado papeles de Principal v\u00E1lidos.
+jaasRealm.isInRole.start = Revisando si el usuario Principal "{0}" tiene el papel "{1}"
+jaasRealm.isInRole.noPrincipalOrRole = No se han hallado papeles de Principal. El usuario Principal o el Asunto es nulo o el usuario Principal no est\u00E1 en cach\u00E9
+jaasRealm.isInRole.principalCached = El Usuario Principal tiene {0} papeles de Principal
+jaasRealm.isInRole.possessesRole = El Usuario Principal tiene un papele de Principal llamado "{0}"
+jaasRealm.isInRole.match = Hallado papel de Principal coincidente.
+jaasRealm.isInRole.noMatch = NO hallado papel de Principal coincidente.
+jaasCallback.digestpassword = Resumida contrase\u00F1a "{0}" como "{1}"
+jaasCallback.username = Devuelto nombre de usuario "{0}"
+jaasCallback.password = Devuelta contrase\u00F1a "{0}"
+jdbcRealm.authenticateFailure = El usuario {0} NO ha sido autentificado correctamente
+jdbcRealm.authenticateSuccess = El usuario {0} ha sido autentificado correctamente
+jdbcRealm.close = Excepci\u00F3n al cerrar la conexi\u00F3n a la base de datos
+jdbcRealm.exception = Excepci\u00F3n al realizar la autentificaci\u00F3n
+jdbcRealm.getPassword.exception = Excepci\u00F3n recuperando contrase\u00F1a para "{0}"
+jdbcRealm.getRoles.exception = Excepci\u00F3n recuperando papeles para "{0}"
+jdbcRealm.open = Excepci\u00F3n abriendo la conexi\u00F3n a la base de datos
+jndiRealm.authenticateFailure = Autentificaci\u00F3n fallida para el usuario {0}
+jndiRealm.authenticateSuccess = Autentificaci\u00F3n correcta para el usuario {0}
+jndiRealm.close = Excepci\u00F3n al cerrar la conexi\u00F3n al servidor de directorio
+jndiRealm.exception = Excepci\u00F3n al realizar la autentificaci\u00F3n
+jndiRealm.open = Excepci\u00F3n al abrir la conexi\u00F3n al servidor de directorio
+memoryRealm.authenticateFailure = Autentificaci\u00F3n fallida para el usuario {0}
+memoryRealm.authenticateSuccess = Autentificaci\u00F3n correcta para el usuario {0}
+memoryRealm.loadExist = El fichero de usuarios {0} no ha podido ser le\u00EDdo
+memoryRealm.loadPath = Cargando los usuarios desde el fichero {0}
+memoryRealm.readXml = Excepci\u00F3n mientras se le\u00EDa el fichero de usuarios
+realmBase.algorithm = El algoritmo resumen (digest) {0} es inv\u00E1lido
+realmBase.alreadyStarted = Este dominio ya ha sido inicializado
+realmBase.digest = Error procesando las credenciales del usuario
+realmBase.forbidden = El acceso al recurso pedido ha sido denegado
+realmBase.hasRoleFailure = El usuario {0} NO desempe\u00F1a el papel de {1}
+realmBase.hasRoleSuccess = El usuario {0} desempe\u00F1a el papel de {1}
+realmBase.notAuthenticated = Error de Configuraci\u00F3n\: No se pueden realizar funciones de control de acceso sin un principal autentificado
+realmBase.notStarted = Este dominio a\u00FAn no ha sido inicializado
+realmBase.authenticateFailure = Nombre de usuario {0} NO autenticado con \u00E9xito
+realmBase.authenticateSuccess = Nombre de usuario {0} autenticado con \u00E9xito
+userDatabaseRealm.authenticateError = Error de configuraci\u00F3n de Login autenticando nombre de usuario {0}
+userDatabaseRealm.lookup = Excepci\u00F3n buscando en Base de datos de Usuario mediante la clave {0}
+userDatabaseRealm.noDatabase = No se ha hallado componente de Base de datos de Usuario mediante la clave {0}
+userDatabaseRealm.noEngine = No se ha hallado componente de Motor en jerarqu\u00EDa de contenedor
+userDatabaseRealm.noGlobal = No se ha hallado contexto de recursos globales JNDI
+dataSourceRealm.authenticateFailure = Nombre de usuario {0} NO autenticado con \u00E9xito
+dataSourceRealm.authenticateSuccess = Nombre de usuario {0} autenticado con \u00E9xito
+dataSourceRealm.close = Excepci\u00F3n cerrando conexi\u00F3n a base de datos
+dataSourceRealm.exception = Excepci\u00F3n realizando autenticaci\u00F3n
+dataSourceRealm.getPassword.exception = Excepci\u00F3n recuperando contrase\u00F1a para "{0}"
+dataSourceRealm.getRoles.exception = Excepci\u00F3n recuperando papeles para "{0}"
+dataSourceRealm.open = Excepci\u00F3n abriendo conexi\u00F3n a base de datos
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/realm/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/LocalStrings_fr.properties
new file mode 100644
index 0000000..8cdd0af
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/LocalStrings_fr.properties
@@ -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.
+
+# $Id: LocalStrings_fr.properties,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+
+# language
+
+# package org.apache.catalina.realm
+
+jaasRealm.accountExpired=le nom d''utilisateur {0} N''A PAS \u00e9t\u00e9 authentifi\u00e9 car le compte a expir\u00e9
+jaasRealm.authenticateSuccess=le nom d''utilisateur {0} a \u00e9t\u00e9 authentifi\u00e9 avec succ\u00e8s
+jaasRealm.credentialExpired=le nom d''utilisateur {0} N''A PAS \u00e9t\u00e9 authentifi\u00e9 car son cr\u00e9dit a expir\u00e9 (expired credential)
+jaasRealm.failedLogin=le nom d''utilisateur {0} N''A PAS \u00e9t\u00e9 authentifi\u00e9 car son contr\u00f4le d''acc\u00e8s (login) a \u00e9chou\u00e9
+jaasRealm.loginException=Exception lors de l''authentification par login du nom d''utilisateur {0}
+jdbcRealm.authenticateFailure=le nom d''utilisateur {0} N''A PAS \u00e9t\u00e9 authentifi\u00e9
+jdbcRealm.authenticateSuccess=le nom d''utilisateur {0} a \u00e9t\u00e9 authentifi\u00e9 avec succ\u00e8s
+jdbcRealm.close=Exception lors de la fermeture de la connexion \u00e0 la base de donn\u00e9es
+jdbcRealm.exception=Exception pendant le traitement de l''authentification
+jdbcRealm.open=Exception lors de l''ouverture de la base de donn\u00e9es
+jndiRealm.authenticateFailure=Le nom d''utilisateur {0} N''A PAS \u00e9t\u00e9 authentifi\u00e9
+jndiRealm.authenticateSuccess=Le nom d''utilisateur {0} a \u00e9t\u00e9 authentifi\u00e9 avec succ\u00e8s
+jndiRealm.close=Exception lors de la fermeture de la connexion au serveur d''acc\u00e8s (directory server)
+jndiRealm.exception=Exception pendant le traitement de l''authentification
+jndiRealm.open=Exception lors de l''ouverture de la connexion au serveur d''acc\u00e8s (directory server)
+memoryRealm.authenticateFailure=le nom d''utilisateur {0} N''A PAS \u00e9t\u00e9 authentifi\u00e9
+memoryRealm.authenticateSuccess=le nom d''utilisateur {0} a \u00e9t\u00e9 authentifi\u00e9 avec succ\u00e8s
+memoryRealm.loadExist=Le fichier base de donn\u00e9es m\u00e9moire (memory database) {0} ne peut \u00eatre lu
+memoryRealm.loadPath=Chargement des utilisateurs depuis le fichier base de donn\u00e9es m\u00e9moire (memory database) {0}
+memoryRealm.readXml=Exception lors de la lecture du fichier base de donn\u00e9es m\u00e9moire (memory database)
+realmBase.algorithm=L''algorithme d''empreinte de message (message digest) {0} indiqu\u00e9 est invalide
+realmBase.alreadyStarted=Ce royaume (Realm) a d\u00e9j\u00e0 \u00e9t\u00e9 d\u00e9marr\u00e9
+realmBase.digest=Erreur lors du traitement des empreintes (digest) des cr\u00e9dits utilisateur (user credentials)
+realmBase.forbidden=L''acc\u00e8s \u00e0 la ressource demand\u00e9e a \u00e9t\u00e9 interdit
+realmBase.hasRoleFailure=Le nom d''utilisateur {0} N''A PAS de r\u00f4le {1}
+realmBase.hasRoleSuccess=Le nom d''utilisateur {0} a pour r\u00f4le {1}
+realmBase.notAuthenticated=Erreur de configuration:  Impossible de conduire un contr\u00f4le d''acc\u00e8s sans un principal authentifi\u00e9 (authenticated principal)
+realmBase.notStarted=Ce royaume (Realm) n''a pas encore \u00e9t\u00e9 d\u00e9marr\u00e9
+realmBase.authenticateFailure=Le nom d''utilisateur {0} N''A PAS \u00e9t\u00e9 authentifi\u00e9
+realmBase.authenticateSuccess=Le nom d''utilisateur {0} a \u00e9t\u00e9 authentifi\u00e9 avec succ\u00e8s
+userDatabaseRealm.authenticateError=Erreur de configuration du contr\u00f4le d''acc\u00e8s (login) lors de l''authentification du nom d''utilisateur {0}
+userDatabaseRealm.lookup=Exception lors de la recherche dans la base de donn\u00e9es utilisateurs avec la cl\u00e9 {0}
+userDatabaseRealm.noDatabase=Aucun composant base de donn\u00e9es utilisateurs trouv\u00e9 pour la cl\u00e9 {0}
+userDatabaseRealm.noEngine=Aucun composant moteur (engine component) trouv\u00e9 dans la hi\u00e9rarchie des conteneurs
+userDatabaseRealm.noGlobal=Aucune ressource globale JNDI trouv\u00e9e
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/realm/LocalStrings_ja.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/LocalStrings_ja.properties
new file mode 100644
index 0000000..82bf844
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/LocalStrings_ja.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.
+
+# $Id: LocalStrings_ja.properties,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+
+# language ja
+
+# package org.apache.catalina.realm
+
+jaasRealm.accountExpired=\u30e6\u30fc\u30b6\u540d {0} \u306f\u30a2\u30ab\u30a6\u30f3\u30c8\u306e\u671f\u9650\u304c\u5207\u308c\u3066\u3044\u308b\u305f\u3081\u306b\u8a8d\u8a3c\u3055\u308c\u307e\u305b\u3093
+jaasRealm.authenticateSuccess=\u30e6\u30fc\u30b6\u540d {0} \u306f\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f
+jaasRealm.credentialExpired=\u30e6\u30fc\u30b6\u540d {0} \u306f\u8a3c\u660e\u66f8\u306e\u671f\u9650\u304c\u5207\u308c\u305f\u305f\u3081\u306b\u8a8d\u8a3c\u3055\u308c\u307e\u305b\u3093
+jaasRealm.failedLogin=\u30e6\u30fc\u30b6\u540d {0} \u306f\u30ed\u30b0\u30a4\u30f3\u306b\u5931\u6557\u3057\u305f\u305f\u3081\u306b\u8a8d\u8a3c\u3055\u308c\u307e\u305b\u3093\u3067\u3057\u305f
+jaasRealm.loginException=\u30e6\u30fc\u30b6\u540d {0} \u306e\u8a8d\u8a3c\u4e2d\u306b\u30ed\u30b0\u30a4\u30f3\u4f8b\u5916\u304c\u767a\u751f\u3057\u307e\u3057\u305f
+jaasRealm.unexpectedError=\u4e88\u6e2c\u3057\u306a\u3044\u30a8\u30e9\u30fc\u3067\u3059
+jdbcRealm.authenticateFailure=\u30e6\u30fc\u30b6\u540d {0} \u306f\u8a8d\u8a3c\u306b\u5931\u6557\u3057\u307e\u3057\u305f
+jdbcRealm.authenticateSuccess=\u30e6\u30fc\u30b6\u540d {0} \u306f\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f
+jdbcRealm.close=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u63a5\u7d9a\u30af\u30ed\u30fc\u30ba\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+jdbcRealm.exception=\u8a8d\u8a3c\u5b9f\u884c\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+jdbcRealm.open=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u63a5\u7d9a\u30aa\u30fc\u30d7\u30f3\u4e2d\u306b\u4f8b\u5916\u304c\u767a\u751f\u3057\u307e\u3057\u305f
+jndiRealm.authenticateFailure=\u30e6\u30fc\u30b6\u540d {0} \u306f\u8a8d\u8a3c\u306b\u5931\u6557\u3057\u307e\u3057\u305f
+jndiRealm.authenticateSuccess=\u30e6\u30fc\u30b6\u540d {0} \u306f\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f
+jndiRealm.close=\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30b5\u30fc\u30d0\u63a5\u7d9a\u30af\u30ed\u30fc\u30ba\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+jndiRealm.exception=\u8a8d\u8a3c\u5b9f\u884c\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+jndiRealm.open=\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30b5\u30fc\u30d0\u63a5\u7d9a\u30aa\u30fc\u30d7\u30f3\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+memoryRealm.authenticateFailure=\u30e6\u30fc\u30b6\u540d {0} \u306f\u8a8d\u8a3c\u306b\u5931\u6557\u3057\u307e\u3057\u305f
+memoryRealm.authenticateSuccess=\u30e6\u30fc\u30b6\u540d {0} \u306f\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f
+memoryRealm.loadExist=\u30e1\u30e2\u30ea\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30d5\u30a1\u30a4\u30eb {0} \u3092\u8aad\u3080\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093
+memoryRealm.loadPath=\u30e1\u30e2\u30ea\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30d5\u30a1\u30a4\u30eb {0} \u304b\u3089\u30e6\u30fc\u30b6\u3092\u30ed\u30fc\u30c9\u3057\u307e\u3059
+memoryRealm.readXml=\u30e1\u30e2\u30ea\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30d5\u30a1\u30a4\u30eb\u3092\u8aad\u307f\u8fbc\u307f\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+realmBase.algorithm=\u7121\u52b9\u306a\u30e1\u30c3\u30bb\u30fc\u30b8\u30c0\u30a4\u30b8\u30a7\u30b9\u30c8\u30a2\u30eb\u30b4\u30ea\u30ba\u30e0 {0} \u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059
+realmBase.alreadyStarted=\u3053\u306e\u30ec\u30eb\u30e0\u306f\u65e2\u306b\u8d77\u52d5\u3055\u308c\u3066\u3044\u307e\u3059
+realmBase.digest=\u30e6\u30fc\u30b6\u306e\u8a3c\u660e\u66f8\u306e\u30c0\u30a4\u30b8\u30a7\u30b9\u30c8\u30a8\u30e9\u30fc
+realmBase.forbidden=\u8981\u6c42\u3055\u308c\u305f\u30ea\u30bd\u30fc\u30b9\u3078\u306e\u30a2\u30af\u30bb\u30b9\u304c\u62d2\u5426\u3055\u308c\u307e\u3057\u305f
+realmBase.hasRoleFailure=\u30e6\u30fc\u30b6\u540d {0} \u306f\u30ed\u30fc\u30eb {1} \u3092\u6301\u3063\u3066\u3044\u307e\u305b\u3093
+realmBase.hasRoleSuccess=\u30e6\u30fc\u30b6\u540d {0} \u306f\u30ed\u30fc\u30eb {1} \u3092\u6301\u3063\u3066\u3044\u307e\u3059
+realmBase.notAuthenticated=\u8a2d\u5b9a\u30a8\u30e9\u30fc:  \u8a8d\u8a3c\u3055\u308c\u305f\u4e3b\u4f53\u306a\u3057\u3067\u306f\u30a2\u30af\u30bb\u30b9\u5236\u5fa1\u304c\u5b9f\u884c\u3067\u304d\u307e\u305b\u3093
+realmBase.notStarted=\u3053\u306e\u30ec\u30eb\u30e0\u306f\u307e\u3060\u8d77\u52d5\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+realmBase.authenticateFailure=\u30e6\u30fc\u30b6\u540d {0} \u306f\u8a8d\u8a3c\u306b\u5931\u6557\u3057\u307e\u3057\u305f
+realmBase.authenticateSuccess=\u30e6\u30fc\u30b6\u540d {0} \u306f\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f
+userDatabaseRealm.authenticateError=\u30e6\u30fc\u30b6\u540d {0} \u3092\u8a8d\u8a3c\u4e2d\u306b\u30ed\u30b0\u30a4\u30f3\u8a2d\u5b9a\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
+userDatabaseRealm.lookup=\u30ad\u30fc {0} \u3067\u30e6\u30fc\u30b6\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u691c\u7d22\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+userDatabaseRealm.noDatabase=\u30ad\u30fc {0} \u3067\u30e6\u30fc\u30b6\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+userDatabaseRealm.noEngine=\u30b3\u30f3\u30c6\u30ca\u968e\u5c64\u4e2d\u306b\u30a8\u30f3\u30b8\u30f3\u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+userDatabaseRealm.noGlobal=\u30b0\u30ed\u30fc\u30d0\u30eb\u306aJNDI\u30ea\u30bd\u30fc\u30b9\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093
+dataSourceRealm.authenticateFailure=\u30e6\u30fc\u30b6\u540d {0} \u306f\u8a8d\u8a3c\u306b\u5931\u6557\u3057\u307e\u3057\u305f
+dataSourceRealm.authenticateSuccess=\u30e6\u30fc\u30b6\u540d {0} \u306f\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f
+dataSourceRealm.close=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u63a5\u7d9a\u3092\u30af\u30ed\u30fc\u30ba\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+dataSourceRealm.exception=\u8a8d\u8a3c\u3092\u5b9f\u884c\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+dataSourceRealm.open=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u63a5\u7d9a\u3092\u30aa\u30fc\u30d7\u30f3\u4e2d\u306e\u4f8b\u5916\u3067\u3059
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/realm/LockOutRealm.java b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/LockOutRealm.java
new file mode 100644
index 0000000..194e42c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/LockOutRealm.java
@@ -0,0 +1,447 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.realm;
+
+import java.security.Principal;
+import java.security.cert.X509Certificate;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSName;
+
+/**
+ * This class extends the CombinedRealm (hence it can wrap other Realms) to
+ * provide a user lock out mechanism if there are too many failed
+ * authentication attempts in a given period of time. To ensure correct
+ * operation, there is a reasonable degree of synchronisation in this Realm.
+ * This Realm does not require modification to the underlying Realms or the
+ * associated user storage mechanisms. It achieves this by recording all failed
+ * logins, including those for users that do not exist. To prevent a DOS by
+ * deliberating making requests with invalid users (and hence causing this cache
+ * to grow) the size of the list of users that have failed authentication is
+ * limited.
+ */
+public class LockOutRealm extends CombinedRealm {
+
+    private static final Log log = LogFactory.getLog(LockOutRealm.class);
+
+    /**
+     * Descriptive information about this Realm implementation.
+     */
+    protected static final String name = "LockOutRealm";
+
+    /**
+     * The number of times in a row a user has to fail authentication to be
+     * locked out. Defaults to 5.
+     */
+    protected int failureCount = 5;
+    
+    /**
+     * The time (in seconds) a user is locked out for after too many
+     * authentication failures. Defaults to 300 (5 minutes). 
+     */
+    protected int lockOutTime = 300;
+
+    /**
+     * Number of users that have failed authentication to keep in cache. Over
+     * time the cache will grow to this size and may not shrink. Defaults to
+     * 1000.
+     */
+    protected int cacheSize = 1000;
+
+    /**
+     * If a failed user is removed from the cache because the cache is too big
+     * before it has been in the cache for at least this period of time (in
+     * seconds) a warning message will be logged. Defaults to 3600 (1 hour).
+     */
+    protected int cacheRemovalWarningTime = 3600;
+
+    /**
+     * Users whose last authentication attempt failed. Entries will be ordered
+     * in access order from least recent to most recent.
+     */
+    protected Map<String,LockRecord> failedUsers = null;
+
+
+    /**
+     * Prepare for the beginning of active use of the public methods of 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 void startInternal() throws LifecycleException {
+        // Configure the list of failed users to delete the oldest entry once it
+        // exceeds the specified size
+        failedUsers = new LinkedHashMap<String, LockRecord>(cacheSize, 0.75f,
+                true) {
+            private static final long serialVersionUID = 1L;
+            @Override
+            protected boolean removeEldestEntry(
+                    Map.Entry<String, LockRecord> eldest) {
+                if (size() > cacheSize) {
+                    // Check to see if this element has been removed too quickly
+                    long timeInCache = (System.currentTimeMillis() -
+                            eldest.getValue().getLastFailureTime())/1000;
+                    
+                    if (timeInCache < cacheRemovalWarningTime) {
+                        log.warn(sm.getString("lockOutRealm.removeWarning",
+                                eldest.getKey(), Long.valueOf(timeInCache)));
+                    }
+                    return true;
+                }
+                return false;
+            }
+        };
+
+        super.startInternal();
+    }
+
+
+    /**
+     * Return the Principal associated with the specified username, which
+     * matches the digest calculated using the given parameters using the
+     * method described in RFC 2069; otherwise return <code>null</code>.
+     *
+     * @param username Username of the Principal to look up
+     * @param clientDigest Digest which has been submitted by the client
+     * @param nonce Unique (or supposedly unique) token which has been used
+     * for this request
+     * @param realmName Realm name
+     * @param md5a2 Second MD5 digest used to calculate the digest :
+     * MD5(Method + ":" + uri)
+     */
+    @Override
+    public Principal authenticate(String username, String clientDigest,
+            String nonce, String nc, String cnonce, String qop,
+            String realmName, String md5a2) {
+
+        if (isLocked(username)) {
+            // Trying to authenticate a locked user is an automatic failure
+            registerAuthFailure(username);
+            
+            log.warn(sm.getString("lockOutRealm.authLockedUser", username));
+            return null;
+        }
+
+        Principal authenticatedUser = super.authenticate(username, clientDigest,
+                nonce, nc, cnonce, qop, realmName, md5a2);
+        
+        if (authenticatedUser == null) {
+            registerAuthFailure(username);
+        } else {
+            registerAuthSuccess(username);
+        }
+        return authenticatedUser;
+    }
+
+
+    /**
+     * Return the Principal associated with the specified username and
+     * credentials, if there is one; otherwise return <code>null</code>.
+     *
+     * @param username Username of the Principal to look up
+     * @param credentials Password or other credentials to use in
+     *  authenticating this username
+     */
+    @Override
+    public Principal authenticate(String username, String credentials) {
+        if (isLocked(username)) {
+            // Trying to authenticate a locked user is an automatic failure
+            registerAuthFailure(username);
+            
+            log.warn(sm.getString("lockOutRealm.authLockedUser", username));
+            return null;
+        }
+
+        Principal authenticatedUser = super.authenticate(username, credentials);
+        
+        if (authenticatedUser == null) {
+            registerAuthFailure(username);
+        } else {
+            registerAuthSuccess(username);
+        }
+        return authenticatedUser;
+    }
+
+
+    /**
+     * Return the Principal associated with the specified chain of X509
+     * client certificates.  If there is none, return <code>null</code>.
+     *
+     * @param certs Array of client certificates, with the first one in
+     *  the array being the certificate of the client itself.
+     */
+    @Override
+    public Principal authenticate(X509Certificate[] certs) {
+        String username = null;
+        if (certs != null && certs.length >0) {
+            username = certs[0].getSubjectDN().getName();
+        }
+
+        if (isLocked(username)) {
+            // Trying to authenticate a locked user is an automatic failure
+            registerAuthFailure(username);
+            
+            log.warn(sm.getString("lockOutRealm.authLockedUser", username));
+            return null;
+        }
+
+        Principal authenticatedUser = super.authenticate(certs);
+        
+        if (authenticatedUser == null) {
+            registerAuthFailure(username);
+        } else {
+            registerAuthSuccess(username);
+        }
+        return authenticatedUser;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Principal authenticate(GSSContext gssContext, boolean storeCreds) {
+        if (gssContext.isEstablished()) {
+            String username = null;
+            GSSName name = null;
+            try {
+                name = gssContext.getSrcName();
+            } catch (GSSException e) {
+                log.warn(sm.getString("realmBase.gssNameFail"), e);
+                return null;
+            }
+            
+            username = name.toString();
+            
+            if (isLocked(username)) {
+                // Trying to authenticate a locked user is an automatic failure
+                registerAuthFailure(username);
+                
+                log.warn(sm.getString("lockOutRealm.authLockedUser", username));
+                return null;
+            }
+
+            Principal authenticatedUser =
+                    super.authenticate(gssContext, storeCreds);
+            
+            if (authenticatedUser == null) {
+                registerAuthFailure(username);
+            } else {
+                registerAuthSuccess(username);
+            }
+            return authenticatedUser;
+        }
+        
+        // Fail in all other cases
+        return null;
+    }
+
+
+    /**
+     * Unlock the specified username. This will remove all records of
+     * authentication failures for this user.
+     * 
+     * @param username The user to unlock
+     */
+    public void unlock(String username) {
+        // Auth success clears the lock record so... 
+        registerAuthSuccess(username);
+    }
+    
+    /*
+     * Checks to see if the current user is locked. If this is associated with
+     * a login attempt, then the last access time will be recorded and any
+     * attempt to authenticated a locked user will log a warning.
+     */
+    private boolean isLocked(String username) {
+        LockRecord lockRecord = null;
+        synchronized (this) {
+            lockRecord = failedUsers.get(username);
+        }
+        
+        // No lock record means user can't be locked
+        if (lockRecord == null) {
+            return false;
+        }
+        
+        // Check to see if user is locked
+        if (lockRecord.getFailures() >= failureCount &&
+                (System.currentTimeMillis() -
+                        lockRecord.getLastFailureTime())/1000 < lockOutTime) {
+            return true;
+        }
+        
+        // User has not, yet, exceeded lock thresholds
+        return false;
+    }
+
+
+    /*
+     * After successful authentication, any record of previous authentication
+     * failure is removed.
+     */
+    private synchronized void registerAuthSuccess(String username) {
+        // Successful authentication means removal from the list of failed users
+        failedUsers.remove(username);
+    }
+
+
+    /*
+     * After a failed authentication, add the record of the failed
+     * authentication. 
+     */
+    private void registerAuthFailure(String username) {
+        LockRecord lockRecord = null;
+        synchronized (this) {
+            if (!failedUsers.containsKey(username)) {
+                lockRecord = new LockRecord(); 
+                failedUsers.put(username, lockRecord);
+            } else {
+                lockRecord = failedUsers.get(username);
+                if (lockRecord.getFailures() >= failureCount &&
+                        ((System.currentTimeMillis() -
+                                lockRecord.getLastFailureTime())/1000)
+                                > lockOutTime) {
+                    // User was previously locked out but lockout has now
+                    // expired so reset failure count
+                    lockRecord.setFailures(0);
+                }
+            }
+        }
+        lockRecord.registerFailure();
+    }
+
+    
+    /**
+     * Get the number of failed authentication attempts required to lock the
+     * user account.
+     * @return the failureCount
+     */
+    public int getFailureCount() {
+        return failureCount;
+    }
+
+
+    /**
+     * Set the number of failed authentication attempts required to lock the
+     * user account.
+     * @param failureCount the failureCount to set
+     */
+    public void setFailureCount(int failureCount) {
+        this.failureCount = failureCount;
+    }
+
+
+    /**
+     * Get the period for which an account will be locked.
+     * @return the lockOutTime
+     */
+    public int getLockOutTime() {
+        return lockOutTime;
+    }
+
+
+    @Override
+    protected String getName() {
+        return name;
+    }
+
+
+    /**
+     * Set the period for which an account will be locked.
+     * @param lockOutTime the lockOutTime to set
+     */
+    public void setLockOutTime(int lockOutTime) {
+        this.lockOutTime = lockOutTime;
+    }
+
+
+    /**
+     * Get the maximum number of users for which authentication failure will be
+     * kept in the cache.
+     * @return the cacheSize
+     */
+    public int getCacheSize() {
+        return cacheSize;
+    }
+
+
+    /**
+     * Set the maximum number of users for which authentication failure will be
+     * kept in the cache.
+     * @param cacheSize the cacheSize to set
+     */
+    public void setCacheSize(int cacheSize) {
+        this.cacheSize = cacheSize;
+    }
+
+
+    /**
+     * Get the minimum period a failed authentication must remain in the cache
+     * to avoid generating a warning if it is removed from the cache to make
+     * space for a new entry.
+     * @return the cacheRemovalWarningTime
+     */
+    public int getCacheRemovalWarningTime() {
+        return cacheRemovalWarningTime;
+    }
+
+
+    /**
+     * Set the minimum period a failed authentication must remain in the cache
+     * to avoid generating a warning if it is removed from the cache to make
+     * space for a new entry.
+     * @param cacheRemovalWarningTime the cacheRemovalWarningTime to set
+     */
+    public void setCacheRemovalWarningTime(int cacheRemovalWarningTime) {
+        this.cacheRemovalWarningTime = cacheRemovalWarningTime;
+    }
+
+
+    protected static class LockRecord {
+        private AtomicInteger failures = new AtomicInteger(0);
+        private long lastFailureTime = 0;
+        
+        public int getFailures() {
+            return failures.get();
+        }
+        
+        public void setFailures(int theFailures) {
+            failures.set(theFailures);
+        }
+
+        public long getLastFailureTime() {
+            return lastFailureTime;
+        }
+        
+        public void registerFailure() {
+            failures.incrementAndGet();
+            lastFailureTime = System.currentTimeMillis();
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/realm/MemoryRealm.java b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/MemoryRealm.java
new file mode 100644
index 0000000..7b6cdc0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/MemoryRealm.java
@@ -0,0 +1,317 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.realm;
+
+
+import java.io.File;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.catalina.Globals;
+import org.apache.catalina.LifecycleException;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.digester.Digester;
+
+
+/**
+ * Simple implementation of <b>Realm</b> that reads an XML file to configure
+ * the valid users, passwords, and roles.  The file format (and default file
+ * location) are identical to those currently supported by Tomcat 3.X.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong>: It is assumed that the in-memory
+ * collection representing our defined users (and their roles) is initialized
+ * at application startup and never modified again.  Therefore, no thread
+ * synchronization is performed around accesses to the principals collection.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: MemoryRealm.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ */
+
+public class MemoryRealm  extends RealmBase {
+
+    private static final Log log = LogFactory.getLog(MemoryRealm.class);
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The Digester we will use to process in-memory database files.
+     */
+    private static Digester digester = null;
+
+
+    /**
+     * Descriptive information about this Realm implementation.
+     */
+    protected static final String info =
+        "org.apache.catalina.realm.MemoryRealm/1.0";
+
+
+    /**
+     * Descriptive information about this Realm implementation.
+     */
+
+    protected static final String name = "MemoryRealm";
+
+
+    /**
+     * The pathname (absolute or relative to Catalina's current working
+     * directory) of the XML file containing our database information.
+     */
+    private String pathname = "conf/tomcat-users.xml";
+
+
+    /**
+     * The set of valid Principals for this Realm, keyed by user name.
+     */
+    private Map<String,GenericPrincipal> principals =
+        new HashMap<String,GenericPrincipal>();
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return descriptive information about this Realm 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 pathname of our XML file containing user definitions.
+     */
+    public String getPathname() {
+
+        return pathname;
+
+    }
+
+
+    /**
+     * Set the pathname of our XML file containing user definitions.  If a
+     * relative pathname is specified, it is resolved against "catalina.base".
+     *
+     * @param pathname The new pathname
+     */
+    public void setPathname(String pathname) {
+
+        this.pathname = pathname;
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return the Principal associated with the specified username and
+     * credentials, if there is one; otherwise return <code>null</code>.
+     *
+     * @param username Username of the Principal to look up
+     * @param credentials Password or other credentials to use in
+     *  authenticating this username
+     */
+    @Override
+    public Principal authenticate(String username, String credentials) {
+
+        GenericPrincipal principal = principals.get(username);
+
+        boolean validated = false;
+        if (principal != null && credentials != null) {
+            if (hasMessageDigest()) {
+                // Hex hashes should be compared case-insensitive
+                validated = (digest(credentials)
+                             .equalsIgnoreCase(principal.getPassword()));
+            } else {
+                validated =
+                    (digest(credentials).equals(principal.getPassword()));
+            }
+        }
+
+        if (validated) {
+            if (log.isDebugEnabled())
+                log.debug(sm.getString("memoryRealm.authenticateSuccess", username));
+            return (principal);
+        } else {
+            if (log.isDebugEnabled())
+                log.debug(sm.getString("memoryRealm.authenticateFailure", username));
+            return (null);
+        }
+
+    }
+
+
+    // -------------------------------------------------------- Package Methods
+
+
+    /**
+     * Add a new user to the in-memory database.
+     *
+     * @param username User's username
+     * @param password User's password (clear text)
+     * @param roles Comma-delimited set of roles associated with this user
+     */
+    void addUser(String username, String password, String roles) {
+
+        // Accumulate the list of roles for this user
+        ArrayList<String> list = new ArrayList<String>();
+        roles += ",";
+        while (true) {
+            int comma = roles.indexOf(',');
+            if (comma < 0)
+                break;
+            String role = roles.substring(0, comma).trim();
+            list.add(role);
+            roles = roles.substring(comma + 1);
+        }
+
+        // Construct and cache the Principal for this user
+        GenericPrincipal principal =
+            new GenericPrincipal(username, password, list);
+        principals.put(username, principal);
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Return a configured <code>Digester</code> to use for processing
+     * the XML input file, creating a new one if necessary.
+     */
+    protected synchronized Digester getDigester() {
+
+        if (digester == null) {
+            digester = new Digester();
+            digester.setValidating(false);
+            try {
+                digester.setFeature(
+                        "http://apache.org/xml/features/allow-java-encodings",
+                        true);
+            } catch (Exception e) {
+                log.warn(sm.getString("memoryRealm.xmlFeatureEncoding"), e);
+            }
+            digester.addRuleSet(new MemoryRuleSet());
+        }
+        return (digester);
+
+    }
+
+
+    /**
+     * Return a short name for this Realm implementation.
+     */
+    @Override
+    protected String getName() {
+
+        return (name);
+
+    }
+
+
+    /**
+     * Return the password associated with the given principal's user name.
+     */
+    @Override
+    protected String getPassword(String username) {
+
+        GenericPrincipal principal = principals.get(username);
+        if (principal != null) {
+            return (principal.getPassword());
+        } else {
+            return (null);
+        }
+
+    }
+
+
+    /**
+     * Return the Principal associated with the given user name.
+     */
+    @Override
+    protected Principal getPrincipal(String username) {
+
+        return principals.get(username);
+
+    }
+
+    /**
+     * Returns the principals for this realm.
+     *
+     * @return The principals, keyed by user name (a String)
+     */
+    protected Map<String,GenericPrincipal> getPrincipals() {
+        return principals;
+    }
+
+
+    // ------------------------------------------------------ Lifecycle Methods
+
+
+    /**
+     * Prepare for the beginning of active use of the public methods of 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 void startInternal() throws LifecycleException {
+
+        // Validate the existence of our database file
+        File file = new File(pathname);
+        if (!file.isAbsolute())
+            file = new File(System.getProperty(Globals.CATALINA_BASE_PROP), pathname);
+        if (!file.exists() || !file.canRead())
+            throw new LifecycleException
+                (sm.getString("memoryRealm.loadExist",
+                              file.getAbsolutePath()));
+
+        // Load the contents of the database file
+        if (log.isDebugEnabled())
+            log.debug(sm.getString("memoryRealm.loadPath",
+                             file.getAbsolutePath()));
+        Digester digester = getDigester();
+        try {
+            synchronized (digester) {
+                digester.push(this);
+                digester.parse(file);
+            }
+        } catch (Exception e) {
+            throw new LifecycleException
+                (sm.getString("memoryRealm.readXml"), e);
+        } finally {
+            digester.reset();
+        }
+
+        super.startInternal();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/realm/MemoryRuleSet.java b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/MemoryRuleSet.java
new file mode 100644
index 0000000..20f457a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/MemoryRuleSet.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.realm;
+
+
+import org.apache.tomcat.util.digester.Digester;
+import org.apache.tomcat.util.digester.Rule;
+import org.apache.tomcat.util.digester.RuleSetBase;
+import org.xml.sax.Attributes;
+
+
+/**
+ * <p><strong>RuleSet</strong> for recognizing the users defined in the
+ * XML file processed by <code>MemoryRealm</code>.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: MemoryRuleSet.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ */
+
+public class MemoryRuleSet 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 MemoryRuleSet() {
+
+        this("tomcat-users/");
+
+    }
+
+
+    /**
+     * 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 MemoryRuleSet(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) {
+
+        digester.addRule(prefix + "user", new MemoryUserRule());
+
+    }
+
+
+}
+
+
+/**
+ * Private class used when parsing the XML database file.
+ */
+final class MemoryUserRule extends Rule {
+
+
+    /**
+     * Construct a new instance of this <code>Rule</code>.
+     */
+    public MemoryUserRule() {
+        // No initialisation required
+    }
+
+
+    /**
+     * Process a <code>&lt;user&gt;</code> element from the XML database file.
+     *
+     * @param attributes The attribute list for this element
+     */
+    @Override
+    public void begin(String namespace, String name, Attributes attributes)
+        throws Exception {
+
+        String username = attributes.getValue("name");
+        if (username == null) {
+            username = attributes.getValue("username");
+        }
+        String password = attributes.getValue("password");
+        String roles = attributes.getValue("roles");
+
+        MemoryRealm realm =
+            (MemoryRealm) digester.peek(digester.getCount() - 1);
+        realm.addUser(username, password, roles);
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/realm/RealmBase.java b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/RealmBase.java
new file mode 100644
index 0000000..f0c3be9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/RealmBase.java
@@ -0,0 +1,1419 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.realm;
+
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Host;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.Realm;
+import org.apache.catalina.Server;
+import org.apache.catalina.Service;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.core.ApplicationSessionCookieConfig;
+import org.apache.catalina.deploy.LoginConfig;
+import org.apache.catalina.deploy.SecurityCollection;
+import org.apache.catalina.deploy.SecurityConstraint;
+import org.apache.catalina.mbeans.MBeanUtils;
+import org.apache.catalina.util.HexUtils;
+import org.apache.catalina.util.LifecycleMBeanBase;
+import org.apache.catalina.util.MD5Encoder;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.res.StringManager;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSName;
+
+/**
+ * Simple implementation of <b>Realm</b> that reads an XML file to configure
+ * the valid users, passwords, and roles.  The file format (and default file
+ * location) are identical to those currently supported by Tomcat 3.X.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: RealmBase.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ */
+
+public abstract class RealmBase extends LifecycleMBeanBase implements Realm {
+
+    private static final Log log = LogFactory.getLog(RealmBase.class);
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The Container with which this Realm is associated.
+     */
+    protected Container container = null;
+
+
+    /**
+     * Container log
+     */
+    protected Log containerLog = null;
+
+
+    /**
+     * Digest algorithm used in storing passwords in a non-plaintext format.
+     * Valid values are those accepted for the algorithm name by the
+     * MessageDigest class, or <code>null</code> if no digesting should
+     * be performed.
+     */
+    protected String digest = null;
+
+    /**
+     * The encoding charset for the digest.
+     */
+    protected String digestEncoding = null;
+
+
+    /**
+     * Descriptive information about this Realm implementation.
+     */
+    protected static final String info =
+        "org.apache.catalina.realm.RealmBase/1.0";
+
+
+    /**
+     * The MessageDigest object for digesting user credentials (passwords).
+     */
+    protected volatile MessageDigest md = null;
+
+
+    /**
+     * The MD5 helper object for this class.
+     */
+    protected static final MD5Encoder md5Encoder = new MD5Encoder();
+
+
+    /**
+     * MD5 message digest provider.
+     */
+    protected static volatile MessageDigest md5Helper;
+
+
+    /**
+     * 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);
+
+
+    /**
+     * Should we validate client certificate chains when they are presented?
+     */
+    protected boolean validate = true;
+
+    
+    /**
+     * The all role mode.
+     */
+    protected AllRolesMode allRolesMode = AllRolesMode.STRICT_MODE;
+    
+
+    /**
+     * When processing users authenticated via the GSS-API, should any
+     * &quot;@...&quot; be stripped from the end of the user name?
+     */
+    protected boolean stripRealmForGss = true;
+
+    
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the Container with which this Realm has been associated.
+     */
+    @Override
+    public Container getContainer() {
+
+        return (container);
+
+    }
+
+
+    /**
+     * Set the Container with which this Realm has been associated.
+     *
+     * @param container The associated Container
+     */
+    @Override
+    public void setContainer(Container container) {
+
+        Container oldContainer = this.container;
+        this.container = container;
+        support.firePropertyChange("container", oldContainer, this.container);
+
+    }
+
+    /**
+     * Return the all roles mode.
+     */
+    public String getAllRolesMode() {
+
+        return allRolesMode.toString();
+
+    }
+
+
+    /**
+     * Set the all roles mode.
+     */
+    public void setAllRolesMode(String allRolesMode) {
+
+        this.allRolesMode = AllRolesMode.toMode(allRolesMode);
+
+    }
+
+    /**
+     * Return the digest algorithm  used for storing credentials.
+     */
+    public String getDigest() {
+
+        return digest;
+
+    }
+
+
+    /**
+     * Set the digest algorithm used for storing credentials.
+     *
+     * @param digest The new digest algorithm
+     */
+    public void setDigest(String digest) {
+
+        this.digest = digest;
+
+    }
+
+    /**
+     * Returns the digest encoding charset.
+     *
+     * @return The charset (may be null) for platform default
+     */
+    public String getDigestEncoding() {
+        return digestEncoding;
+    }
+
+    /**
+     * Sets the digest encoding charset.
+     *
+     * @param charset The charset (null for platform default)
+     */
+    public void setDigestEncoding(String charset) {
+        digestEncoding = charset;
+    }
+
+    /**
+     * Return descriptive information about this Realm 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 "validate certificate chains" flag.
+     */
+    public boolean getValidate() {
+
+        return (this.validate);
+
+    }
+
+
+    /**
+     * Set the "validate certificate chains" flag.
+     *
+     * @param validate The new validate certificate chains flag
+     */
+    public void setValidate(boolean validate) {
+
+        this.validate = validate;
+
+    }
+
+
+    public boolean isStripRealmForGss() {
+        return stripRealmForGss;
+    }
+
+
+    public void setStripRealmForGss(boolean stripRealmForGss) {
+        this.stripRealmForGss = stripRealmForGss;
+    }
+
+
+    // --------------------------------------------------------- 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);
+
+    }
+
+
+    /**
+     * Return the Principal associated with the specified username and
+     * credentials, if there is one; otherwise return <code>null</code>.
+     *
+     * @param username Username of the Principal to look up
+     * @param credentials Password or other credentials to use in
+     *  authenticating this username
+     */
+    @Override
+    public Principal authenticate(String username, String credentials) {
+
+        String serverCredentials = getPassword(username);
+
+        boolean validated ;
+        if ( serverCredentials == null ) {
+            validated = false;
+        } else if(hasMessageDigest()) {
+            validated = serverCredentials.equalsIgnoreCase(digest(credentials));
+        } else {
+            validated = serverCredentials.equals(credentials);
+        }
+        if(! validated ) {
+            if (containerLog.isTraceEnabled()) {
+                containerLog.trace(sm.getString("realmBase.authenticateFailure",
+                                                username));
+            }
+            return null;
+        }
+        if (containerLog.isTraceEnabled()) {
+            containerLog.trace(sm.getString("realmBase.authenticateSuccess",
+                                            username));
+        }
+
+        return getPrincipal(username);
+    }
+
+
+    /**
+     * Return the Principal associated with the specified username, which
+     * matches the digest calculated using the given parameters using the
+     * method described in RFC 2069; otherwise return <code>null</code>.
+     *
+     * @param username Username of the Principal to look up
+     * @param clientDigest Digest which has been submitted by the client
+     * @param nOnce Unique (or supposedly unique) token which has been used
+     * for this request
+     * @param realm Realm name
+     * @param md5a2 Second MD5 digest used to calculate the digest :
+     * MD5(Method + ":" + uri)
+     */
+    @Override
+    public Principal authenticate(String username, String clientDigest,
+                                  String nOnce, String nc, String cnonce,
+                                  String qop, String realm,
+                                  String md5a2) {
+
+        String md5a1 = getDigest(username, realm);
+        if (md5a1 == null)
+            return null;
+        String serverDigestValue;
+        if (qop == null) {
+            serverDigestValue = md5a1 + ":" + nOnce + ":" + md5a2;
+        } else {
+            serverDigestValue = md5a1 + ":" + nOnce + ":" + nc + ":" +
+                    cnonce + ":" + qop + ":" + md5a2;
+        }
+
+        byte[] valueBytes = null;
+        if(getDigestEncoding() == null) {
+            valueBytes = serverDigestValue.getBytes();
+        } else {
+            try {
+                valueBytes = serverDigestValue.getBytes(getDigestEncoding());
+            } catch (UnsupportedEncodingException uee) {
+                log.error("Illegal digestEncoding: " + getDigestEncoding(), uee);
+                throw new IllegalArgumentException(uee.getMessage());
+            }
+        }
+
+        String serverDigest = null;
+        // Bugzilla 32137
+        synchronized(md5Helper) {
+            serverDigest = md5Encoder.encode(md5Helper.digest(valueBytes));
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("Digest : " + clientDigest + " Username:" + username 
+                    + " ClientSigest:" + clientDigest + " nOnce:" + nOnce 
+                    + " nc:" + nc + " cnonce:" + cnonce + " qop:" + qop 
+                    + " realm:" + realm + "md5a2:" + md5a2 
+                    + " Server digest:" + serverDigest);
+        }
+        
+        if (serverDigest.equals(clientDigest)) {
+            return getPrincipal(username);
+        }
+
+        return null;
+    }
+
+
+    /**
+     * Return the Principal associated with the specified chain of X509
+     * client certificates.  If there is none, return <code>null</code>.
+     *
+     * @param certs Array of client certificates, with the first one in
+     *  the array being the certificate of the client itself.
+     */
+    @Override
+    public Principal authenticate(X509Certificate certs[]) {
+
+        if ((certs == null) || (certs.length < 1))
+            return (null);
+
+        // Check the validity of each certificate in the chain
+        if (log.isDebugEnabled())
+            log.debug("Authenticating client certificate chain");
+        if (validate) {
+            for (int i = 0; i < certs.length; i++) {
+                if (log.isDebugEnabled())
+                    log.debug(" Checking validity for '" +
+                        certs[i].getSubjectDN().getName() + "'");
+                try {
+                    certs[i].checkValidity();
+                } catch (Exception e) {
+                    if (log.isDebugEnabled())
+                        log.debug("  Validity exception", e);
+                    return (null);
+                }
+            }
+        }
+
+        // Check the existence of the client Principal in our database
+        return (getPrincipal(certs[0]));
+
+    }
+
+    
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Principal authenticate(GSSContext gssContext, boolean storeCred) {
+        if (gssContext.isEstablished()) {
+            GSSName gssName = null;
+            try {
+                gssName = gssContext.getSrcName();
+            } catch (GSSException e) {
+                log.warn(sm.getString("realmBase.gssNameFail"), e);
+            }
+            
+            if (gssName!= null) {
+                String name = gssName.toString();
+                
+                if (isStripRealmForGss()) {
+                    int i = name.indexOf('@');
+                    if (i > 0) {
+                        // Zero so we don;t leave a zero length name
+                        name = name.substring(0, i);
+                    }
+                }
+                GSSCredential gssCredential = null;
+                if (storeCred && gssContext.getCredDelegState()) {
+                    try {
+                        gssCredential = gssContext.getDelegCred();
+                    } catch (GSSException e) {
+                        e.printStackTrace();
+                        if (log.isDebugEnabled()) {
+                            log.debug(sm.getString(
+                                    "realmBase.delegatedCredentialFail", name),
+                                    e);
+                        }
+                    }
+                }
+                return getPrincipal(name, gssCredential);
+            }
+        }
+        
+        // Fail in all other cases
+        return null;
+    }
+
+    
+    /**
+     * 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() {
+        // NOOP in base class
+    }
+
+
+    /**
+     * Return the SecurityConstraints configured to guard the request URI for
+     * this request, or <code>null</code> if there is no such constraint.
+     *
+     * @param request Request we are processing
+     * @param context Context the Request is mapped to
+     */
+    @Override
+    public SecurityConstraint [] findSecurityConstraints(Request request,
+                                                         Context context) {
+
+        ArrayList<SecurityConstraint> results = null;
+        // Are there any defined security constraints?
+        SecurityConstraint constraints[] = context.findConstraints();
+        if ((constraints == null) || (constraints.length == 0)) {
+            if (log.isDebugEnabled())
+                log.debug("  No applicable constraints defined");
+            return (null);
+        }
+
+        // Check each defined security constraint
+        String uri = request.getRequestPathMB().toString();
+        // Bug47080 - in rare cases this may be null
+        // Mapper treats as '/' do the same to prevent NPE
+        if (uri == null) {
+            uri = "/";
+        }
+        
+        String method = request.getMethod();
+        int i;
+        boolean found = false;
+        for (i = 0; i < constraints.length; i++) {
+            SecurityCollection [] collection = constraints[i].findCollections();
+                     
+            // If collection is null, continue to avoid an NPE
+            // See Bugzilla 30624
+            if ( collection == null) {
+                continue;
+            }
+
+            if (log.isDebugEnabled()) {
+                log.debug("  Checking constraint '" + constraints[i] +
+                    "' against " + method + " " + uri + " --> " +
+                    constraints[i].included(uri, method));
+            }
+
+            for(int j=0; j < collection.length; j++){
+                String [] patterns = collection[j].findPatterns();
+ 
+                // If patterns is null, continue to avoid an NPE
+                // See Bugzilla 30624
+                if ( patterns == null) {
+                    continue;
+                }
+
+                for(int k=0; k < patterns.length; k++) {
+                    if(uri.equals(patterns[k])) {
+                        found = true;
+                        if(collection[j].findMethod(method)) {
+                            if(results == null) {
+                                results = new ArrayList<SecurityConstraint>();
+                            }
+                            results.add(constraints[i]);
+                        }
+                    }
+                }
+            }
+        }
+
+        if(found) {
+            return resultsToArray(results);
+        }
+
+        int longest = -1;
+
+        for (i = 0; i < constraints.length; i++) {
+            SecurityCollection [] collection = constraints[i].findCollections();
+            
+            // If collection is null, continue to avoid an NPE
+            // See Bugzilla 30624
+            if ( collection == null) {
+                continue;
+            }
+
+            if (log.isDebugEnabled()) {
+                log.debug("  Checking constraint '" + constraints[i] +
+                    "' against " + method + " " + uri + " --> " +
+                    constraints[i].included(uri, method));
+            }
+
+            for(int j=0; j < collection.length; j++){
+                String [] patterns = collection[j].findPatterns();
+
+                // If patterns is null, continue to avoid an NPE
+                // See Bugzilla 30624
+                if ( patterns == null) {
+                    continue;
+                }
+
+                boolean matched = false;
+                int length = -1;
+                for(int k=0; k < patterns.length; k++) {
+                    String pattern = patterns[k];
+                    if(pattern.startsWith("/") && pattern.endsWith("/*") && 
+                       pattern.length() >= longest) {
+                            
+                        if(pattern.length() == 2) {
+                            matched = true;
+                            length = pattern.length();
+                        } else if(pattern.regionMatches(0,uri,0,
+                                                        pattern.length()-1) ||
+                                  (pattern.length()-2 == uri.length() &&
+                                   pattern.regionMatches(0,uri,0,
+                                                        pattern.length()-2))) {
+                            matched = true;
+                            length = pattern.length();
+                        }
+                    }
+                }
+                if(matched) {
+                    found = true;
+                    if(length > longest) {
+                        if(results != null) {
+                            results.clear();
+                        }
+                        longest = length;
+                    }
+                    if(collection[j].findMethod(method)) {
+                        if(results == null) {
+                            results = new ArrayList<SecurityConstraint>();
+                        }
+                        results.add(constraints[i]);
+                    }
+                }
+            }
+        }
+
+        if(found) {
+            return  resultsToArray(results);
+        }
+
+        for (i = 0; i < constraints.length; i++) {
+            SecurityCollection [] collection = constraints[i].findCollections();
+
+            // If collection is null, continue to avoid an NPE
+            // See Bugzilla 30624
+            if ( collection == null) {
+                continue;
+            }
+            
+            if (log.isDebugEnabled()) {
+                log.debug("  Checking constraint '" + constraints[i] +
+                    "' against " + method + " " + uri + " --> " +
+                    constraints[i].included(uri, method));
+            }
+
+            boolean matched = false;
+            int pos = -1;
+            for(int j=0; j < collection.length; j++){
+                String [] patterns = collection[j].findPatterns();
+
+                // If patterns is null, continue to avoid an NPE
+                // See Bugzilla 30624
+                if ( patterns == null) {
+                    continue;
+                }
+
+                for(int k=0; k < patterns.length && !matched; k++) {
+                    String pattern = patterns[k];
+                    if(pattern.startsWith("*.")){
+                        int slash = uri.lastIndexOf("/");
+                        int dot = uri.lastIndexOf(".");
+                        if(slash >= 0 && dot > slash &&
+                           dot != uri.length()-1 &&
+                           uri.length()-dot == pattern.length()-1) {
+                            if(pattern.regionMatches(1,uri,dot,uri.length()-dot)) {
+                                matched = true;
+                                pos = j;
+                            }
+                        }
+                    }
+                }
+            }
+            if(matched) {
+                found = true;
+                if(collection[pos].findMethod(method)) {
+                    if(results == null) {
+                        results = new ArrayList<SecurityConstraint>();
+                    }
+                    results.add(constraints[i]);
+                }
+            }
+        }
+
+        if(found) {
+            return resultsToArray(results);
+        }
+
+        for (i = 0; i < constraints.length; i++) {
+            SecurityCollection [] collection = constraints[i].findCollections();
+            
+            // If collection is null, continue to avoid an NPE
+            // See Bugzilla 30624
+            if ( collection == null) {
+                continue;
+            }
+
+            if (log.isDebugEnabled()) {
+                log.debug("  Checking constraint '" + constraints[i] +
+                    "' against " + method + " " + uri + " --> " +
+                    constraints[i].included(uri, method));
+            }
+
+            for(int j=0; j < collection.length; j++){
+                String [] patterns = collection[j].findPatterns();
+
+                // If patterns is null, continue to avoid an NPE
+                // See Bugzilla 30624
+                if ( patterns == null) {
+                    continue;
+                }
+
+                boolean matched = false;
+                for(int k=0; k < patterns.length && !matched; k++) {
+                    String pattern = patterns[k];
+                    if(pattern.equals("/")){
+                        matched = true;
+                    }
+                }
+                if(matched) {
+                    if(results == null) {
+                        results = new ArrayList<SecurityConstraint>();
+                    }                    
+                    results.add(constraints[i]);
+                }
+            }
+        }
+
+        if(results == null) {
+            // No applicable security constraint was found
+            if (log.isDebugEnabled())
+                log.debug("  No applicable constraint located");
+        }
+        return resultsToArray(results);
+    }
+ 
+    /**
+     * Convert an ArrayList to a SecurityContraint [].
+     */
+    private SecurityConstraint [] resultsToArray(
+            ArrayList<SecurityConstraint> results) {
+        if(results == null) {
+            return null;
+        }
+        SecurityConstraint [] array = new SecurityConstraint[results.size()];
+        results.toArray(array);
+        return array;
+    }
+
+    
+    /**
+     * Perform access control based on the specified authorization constraint.
+     * Return <code>true</code> if this constraint is satisfied and processing
+     * should continue, or <code>false</code> otherwise.
+     *
+     * @param request Request we are processing
+     * @param response Response we are creating
+     * @param constraints Security constraint we are enforcing
+     * @param context The Context to which client of this class is attached.
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public boolean hasResourcePermission(Request request,
+                                         Response response,
+                                         SecurityConstraint []constraints,
+                                         Context context)
+        throws IOException {
+
+        if (constraints == null || constraints.length == 0)
+            return (true);
+
+        // Specifically allow access to the form login and form error pages
+        // and the "j_security_check" action
+        LoginConfig config = context.getLoginConfig();
+        if ((config != null) &&
+            (Constants.FORM_METHOD.equals(config.getAuthMethod()))) {
+            String requestURI = request.getRequestPathMB().toString();
+            String loginPage = config.getLoginPage();
+            if (loginPage.equals(requestURI)) {
+                if (log.isDebugEnabled())
+                    log.debug(" Allow access to login page " + loginPage);
+                return (true);
+            }
+            String errorPage = config.getErrorPage();
+            if (errorPage.equals(requestURI)) {
+                if (log.isDebugEnabled())
+                    log.debug(" Allow access to error page " + errorPage);
+                return (true);
+            }
+            if (requestURI.endsWith(Constants.FORM_ACTION)) {
+                if (log.isDebugEnabled())
+                    log.debug(" Allow access to username/password submission");
+                return (true);
+            }
+        }
+
+        // Which user principal have we already authenticated?
+        Principal principal = request.getPrincipal();
+        boolean status = false;
+        boolean denyfromall = false;
+        for(int i=0; i < constraints.length; i++) {
+            SecurityConstraint constraint = constraints[i];
+
+            String roles[];
+            if (constraint.getAllRoles()) {
+                // * means all roles defined in web.xml
+                roles = request.getContext().findSecurityRoles();
+            } else {
+                roles = constraint.findAuthRoles();
+            }
+
+            if (roles == null)
+                roles = new String[0];
+
+            if (log.isDebugEnabled())
+                log.debug("  Checking roles " + principal);
+
+            if (roles.length == 0 && !constraint.getAllRoles()) {
+                if(constraint.getAuthConstraint()) {
+                    if( log.isDebugEnabled() )
+                        log.debug("No role)s ");
+                    status = false; // No listed roles means no access at all
+                    denyfromall = true;
+                    break;
+                }
+                
+                if(log.isDebugEnabled())
+                    log.debug("Passing all access");
+                status = true;
+            } else if (principal == null) {
+                if (log.isDebugEnabled())
+                    log.debug("  No user authenticated, cannot grant access");
+            } else {
+                for (int j = 0; j < roles.length; j++) {
+                    if (hasRole(null, principal, roles[j])) {
+                        status = true;
+                        if( log.isDebugEnabled() )
+                            log.debug( "Role found:  " + roles[j]);
+                    }
+                    else if( log.isDebugEnabled() )
+                        log.debug( "No role found:  " + roles[j]);
+                }
+            }
+        }
+
+        if (!denyfromall && allRolesMode != AllRolesMode.STRICT_MODE &&
+                !status && principal != null) {
+            if (log.isDebugEnabled()) {
+                log.debug("Checking for all roles mode: " + allRolesMode);
+            }
+            // Check for an all roles(role-name="*")
+            for (int i = 0; i < constraints.length; i++) {
+                SecurityConstraint constraint = constraints[i];
+                String roles[];
+                // If the all roles mode exists, sets
+                if (constraint.getAllRoles()) {
+                    if (allRolesMode == AllRolesMode.AUTH_ONLY_MODE) {
+                        if (log.isDebugEnabled()) {
+                            log.debug("Granting access for role-name=*, auth-only");
+                        }
+                        status = true;
+                        break;
+                    }
+                    
+                    // For AllRolesMode.STRICT_AUTH_ONLY_MODE there must be zero roles
+                    roles = request.getContext().findSecurityRoles();
+                    if (roles.length == 0 && allRolesMode == AllRolesMode.STRICT_AUTH_ONLY_MODE) {
+                        if (log.isDebugEnabled()) {
+                            log.debug("Granting access for role-name=*, strict auth-only");
+                        }
+                        status = true;
+                        break;
+                    }
+                }
+            }
+        }
+        
+        // Return a "Forbidden" message denying access to this resource
+        if(!status) {
+            response.sendError
+                (HttpServletResponse.SC_FORBIDDEN,
+                 sm.getString("realmBase.forbidden"));
+        }
+        return status;
+
+    }
+    
+    
+    /**
+     * Return <code>true</code> if the specified Principal has the specified
+     * security role, within the context of this Realm; otherwise return
+     * <code>false</code>.  This method can be overridden by Realm
+     * implementations, but the default is adequate when an instance of
+     * <code>GenericPrincipal</code> is used to represent authenticated
+     * Principals from this Realm.
+     *
+     * @param principal Principal for whom the role is to be checked
+     * @param role Security role to be checked
+     */
+    @Override
+    public boolean hasRole(Wrapper wrapper, Principal principal, String role) {
+        // Check for a role alias defined in a <security-role-ref> element
+        if (wrapper != null) {
+            String realRole = wrapper.findSecurityReference(role);
+            if (realRole != null)
+                role = realRole;
+        }
+
+        // Should be overridden in JAASRealm - to avoid pretty inefficient conversions
+        if ((principal == null) || (role == null) ||
+            !(principal instanceof GenericPrincipal))
+            return (false);
+
+        GenericPrincipal gp = (GenericPrincipal) principal;
+        boolean result = gp.hasRole(role);
+        if (log.isDebugEnabled()) {
+            String name = principal.getName();
+            if (result)
+                log.debug(sm.getString("realmBase.hasRoleSuccess", name, role));
+            else
+                log.debug(sm.getString("realmBase.hasRoleFailure", name, role));
+        }
+        return (result);
+
+    }
+
+    
+    /**
+     * Enforce any user data constraint required by the security constraint
+     * guarding this request URI.  Return <code>true</code> if this constraint
+     * was not violated and processing should continue, or <code>false</code>
+     * if we have created a response already.
+     *
+     * @param request Request we are processing
+     * @param response Response we are creating
+     * @param constraints Security constraint being checked
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public boolean hasUserDataPermission(Request request,
+                                         Response response,
+                                         SecurityConstraint []constraints)
+        throws IOException {
+
+        // Is there a relevant user data constraint?
+        if (constraints == null || constraints.length == 0) {
+            if (log.isDebugEnabled())
+                log.debug("  No applicable security constraint defined");
+            return (true);
+        }
+        for(int i=0; i < constraints.length; i++) {
+            SecurityConstraint constraint = constraints[i];
+            String userConstraint = constraint.getUserConstraint();
+            if (userConstraint == null) {
+                if (log.isDebugEnabled())
+                    log.debug("  No applicable user data constraint defined");
+                return (true);
+            }
+            if (userConstraint.equals(Constants.NONE_TRANSPORT)) {
+                if (log.isDebugEnabled())
+                    log.debug("  User data constraint has no restrictions");
+                return (true);
+            }
+
+        }
+        // Validate the request against the user data constraint
+        if (request.getRequest().isSecure()) {
+            if (log.isDebugEnabled())
+                log.debug("  User data constraint already satisfied");
+            return (true);
+        }
+        // Initialize variables we need to determine the appropriate action
+        int redirectPort = request.getConnector().getRedirectPort();
+
+        // Is redirecting disabled?
+        if (redirectPort <= 0) {
+            if (log.isDebugEnabled())
+                log.debug("  SSL redirect is disabled");
+            response.sendError
+                (HttpServletResponse.SC_FORBIDDEN,
+                 request.getRequestURI());
+            return (false);
+        }
+
+        // Redirect to the corresponding SSL port
+        StringBuilder file = new StringBuilder();
+        String protocol = "https";
+        String host = request.getServerName();
+        // Protocol
+        file.append(protocol).append("://").append(host);
+        // Host with port
+        if(redirectPort != 443) {
+            file.append(":").append(redirectPort);
+        }
+        // URI
+        file.append(request.getRequestURI());
+        String requestedSessionId = request.getRequestedSessionId();
+        if ((requestedSessionId != null) &&
+            request.isRequestedSessionIdFromURL()) {
+            file.append(";");
+            file.append(ApplicationSessionCookieConfig.getSessionUriParamName(
+                    request.getContext()));
+            file.append("=");
+            file.append(requestedSessionId);
+        }
+        String queryString = request.getQueryString();
+        if (queryString != null) {
+            file.append('?');
+            file.append(queryString);
+        }
+        if (log.isDebugEnabled())
+            log.debug("  Redirecting to " + file.toString());
+        response.sendRedirect(file.toString());
+        return (false);
+
+    }
+    
+    
+    /**
+     * Remove a property change listener from this component.
+     *
+     * @param listener The listener to remove
+     */
+    @Override
+    public void removePropertyChangeListener(PropertyChangeListener listener) {
+
+        support.removePropertyChangeListener(listener);
+
+    }
+
+
+    @Override
+    protected void initInternal() throws LifecycleException {
+
+        super.initInternal();
+
+        // We want logger as soon as possible
+        if (container != null) {
+            this.containerLog = container.getLogger();
+        }
+    }
+        
+    /**
+     * Prepare for the beginning of active use of the public methods of 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 void startInternal() throws LifecycleException {
+
+        // Create a MessageDigest instance for credentials, if desired
+        if (digest != null) {
+            try {
+                md = MessageDigest.getInstance(digest);
+            } catch (NoSuchAlgorithmException e) {
+                throw new LifecycleException
+                    (sm.getString("realmBase.algorithm", digest), e);
+            }
+        }
+
+        setState(LifecycleState.STARTING);
+    }
+
+
+    /**
+     * Gracefully terminate the active use of the public methods of this
+     * component and implement the requirements of
+     * {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
+     *
+     * @exception LifecycleException if this component detects a fatal error
+     *  that needs to be reported
+     */
+    @Override
+    protected void stopInternal() throws LifecycleException {
+
+        setState(LifecycleState.STOPPING);
+        
+        // Clean up allocated resources
+        md = null;
+    }
+    
+    
+    /**
+     * Return a String representation of this component.
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder("Realm[");
+        sb.append(getName());
+        sb.append(']');
+        return sb.toString();
+    }
+    
+    
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Digest the password using the specified algorithm and
+     * convert the result to a corresponding hexadecimal string.
+     * If exception, the plain credentials string is returned.
+     *
+     * @param credentials Password or other credentials to use in
+     *  authenticating this username
+     */
+    protected String digest(String credentials)  {
+
+        // If no MessageDigest instance is specified, return unchanged
+        if (hasMessageDigest() == false)
+            return (credentials);
+
+        // Digest the user credentials and return as hexadecimal
+        synchronized (this) {
+            try {
+                md.reset();
+    
+                byte[] bytes = null;
+                if(getDigestEncoding() == null) {
+                    bytes = credentials.getBytes();
+                } else {
+                    try {
+                        bytes = credentials.getBytes(getDigestEncoding());
+                    } catch (UnsupportedEncodingException uee) {
+                        log.error("Illegal digestEncoding: " + getDigestEncoding(), uee);
+                        throw new IllegalArgumentException(uee.getMessage());
+                    }
+                }
+                md.update(bytes);
+
+                return (HexUtils.convert(md.digest()));
+            } catch (Exception e) {
+                log.error(sm.getString("realmBase.digest"), e);
+                return (credentials);
+            }
+        }
+
+    }
+
+    protected boolean hasMessageDigest() {
+        return !(md == null);
+    }
+
+    /**
+     * Return the digest associated with given principal's user name.
+     */
+    protected String getDigest(String username, String realmName) {
+        if (md5Helper == null) {
+            try {
+                md5Helper = MessageDigest.getInstance("MD5");
+            } catch (NoSuchAlgorithmException e) {
+                log.error("Couldn't get MD5 digest: ", e);
+                throw new IllegalStateException(e.getMessage());
+            }
+        }
+
+        if (hasMessageDigest()) {
+            // Use pre-generated digest
+            return getPassword(username);
+        }
+            
+        String digestValue = username + ":" + realmName + ":"
+            + getPassword(username);
+
+        byte[] valueBytes = null;
+        if(getDigestEncoding() == null) {
+            valueBytes = digestValue.getBytes();
+        } else {
+            try {
+                valueBytes = digestValue.getBytes(getDigestEncoding());
+            } catch (UnsupportedEncodingException uee) {
+                log.error("Illegal digestEncoding: " + getDigestEncoding(), uee);
+                throw new IllegalArgumentException(uee.getMessage());
+            }
+        }
+
+        byte[] digest = null;
+        // Bugzilla 32137
+        synchronized(md5Helper) {
+            digest = md5Helper.digest(valueBytes);
+        }
+
+        return md5Encoder.encode(digest);
+    }
+
+
+    /**
+     * Return a short name for this Realm implementation, for use in
+     * log messages.
+     */
+    protected abstract String getName();
+
+
+    /**
+     * Return the password associated with the given principal's user name.
+     */
+    protected abstract String getPassword(String username);
+
+
+    /**
+     * Return the Principal associated with the given certificate.
+     */
+    protected Principal getPrincipal(X509Certificate usercert) {
+        return(getPrincipal(usercert.getSubjectDN().getName()));
+    }
+    
+
+    /**
+     * Return the Principal associated with the given user name.
+     */
+    protected abstract Principal getPrincipal(String username);
+
+
+    protected Principal getPrincipal(String username,
+            GSSCredential gssCredential) {
+        Principal p = getPrincipal(username);
+        
+        if (p instanceof GenericPrincipal) {
+            ((GenericPrincipal) p).setGssCredential(gssCredential);
+        }
+        
+        return p;
+    }
+
+    /**
+     * Return the Server object that is the ultimate parent for the container
+     * with which this Realm is associated. If the server cannot be found (eg
+     * because the container hierarchy is not complete), <code>null</code> is
+     * returned.
+     */
+    protected Server getServer() {
+        Container c = container;
+        if (c instanceof Context) {
+            c = c.getParent();
+        }
+        if (c instanceof Host) {
+            c = c.getParent();
+        }
+        if (c instanceof Engine) {
+            Service s = ((Engine)c).getService();
+            if (s != null) {
+                return s.getServer();
+            }
+        }
+        return null;
+    }
+
+    
+    // --------------------------------------------------------- Static Methods
+
+
+    /**
+     * Digest password using the algorithm specified and
+     * convert the result to a corresponding hex string.
+     * If exception, the plain credentials string is returned
+     *
+     * @param credentials Password or other credentials to use in
+     *  authenticating this username
+     * @param algorithm Algorithm used to do the digest
+     * @param encoding Character encoding of the string to digest
+     */
+    public static final String Digest(String credentials, String algorithm,
+                                      String encoding) {
+
+        try {
+            // Obtain a new message digest with "digest" encryption
+            MessageDigest md =
+                (MessageDigest) MessageDigest.getInstance(algorithm).clone();
+
+            // encode the credentials
+            // Should use the digestEncoding, but that's not a static field
+            if (encoding == null) {
+                md.update(credentials.getBytes());
+            } else {
+                md.update(credentials.getBytes(encoding));                
+            }
+
+            // Digest the credentials and return as hexadecimal
+            return (HexUtils.convert(md.digest()));
+        } catch(Exception ex) {
+            log.error(ex);
+            return credentials;
+        }
+
+    }
+
+
+    /**
+     * Digest password using the algorithm specified and
+     * convert the result to a corresponding hex string.
+     * If exception, the plain credentials string is returned
+     */
+    public static void main(String args[]) {
+
+        String encoding = null;
+        int firstCredentialArg = 2;
+        
+        if (args.length > 4 && args[2].equalsIgnoreCase("-e")) {
+            encoding = args[3];
+            firstCredentialArg = 4;
+        }
+        
+        if(args.length > firstCredentialArg && args[0].equalsIgnoreCase("-a")) {
+            for(int i=firstCredentialArg; i < args.length ; i++){
+                System.out.print(args[i]+":");
+                System.out.println(Digest(args[i], args[1], encoding));
+            }
+        } else {
+            System.out.println
+                ("Usage: RealmBase -a <algorithm> [-e <encoding>] <credentials>");
+        }
+
+    }
+
+
+    // -------------------- JMX and Registration  --------------------
+
+    @Override
+    public String getObjectNameKeyProperties() {
+        
+        StringBuilder keyProperties = new StringBuilder("type=Realm");
+        keyProperties.append(getRealmSuffix());
+        keyProperties.append(MBeanUtils.getContainerKeyProperties(container));
+
+        return keyProperties.toString();
+    }
+
+    @Override
+    public String getDomainInternal() {
+        return MBeanUtils.getDomain(container);
+    }
+
+    protected String realmPath = "/realm0";
+
+    public String getRealmPath() {
+        return realmPath;
+    }
+    
+    public void setRealmPath(String theRealmPath) {
+        realmPath = theRealmPath;
+    }
+
+    protected String getRealmSuffix() {
+        return ",realmPath=" + getRealmPath();
+    }
+
+
+    protected static class AllRolesMode {
+        
+        private String name;
+        /** Use the strict servlet spec interpretation which requires that the user
+         * have one of the web-app/security-role/role-name 
+         */
+        public static final AllRolesMode STRICT_MODE = new AllRolesMode("strict");
+        /** Allow any authenticated user
+         */
+        public static final AllRolesMode AUTH_ONLY_MODE = new AllRolesMode("authOnly");
+        /** Allow any authenticated user only if there are no web-app/security-roles
+         */
+        public static final AllRolesMode STRICT_AUTH_ONLY_MODE = new AllRolesMode("strictAuthOnly");
+        
+        static AllRolesMode toMode(String name)
+        {
+            AllRolesMode mode;
+            if( name.equalsIgnoreCase(STRICT_MODE.name) )
+                mode = STRICT_MODE;
+            else if( name.equalsIgnoreCase(AUTH_ONLY_MODE.name) )
+                mode = AUTH_ONLY_MODE;
+            else if( name.equalsIgnoreCase(STRICT_AUTH_ONLY_MODE.name) )
+                mode = STRICT_AUTH_ONLY_MODE;
+            else
+                throw new IllegalStateException("Unknown mode, must be one of: strict, authOnly, strictAuthOnly");
+            return mode;
+        }
+        
+        private AllRolesMode(String name)
+        {
+            this.name = name;
+        }
+        
+        @Override
+        public boolean equals(Object o)
+        {
+            boolean equals = false;
+            if( o instanceof AllRolesMode )
+            {
+                AllRolesMode mode = (AllRolesMode) o;
+                equals = name.equals(mode.name);
+            }
+            return equals;
+        }
+        @Override
+        public int hashCode()
+        {
+            return name.hashCode();
+        }
+        @Override
+        public String toString()
+        {
+            return name;
+        }
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/realm/UserDatabaseRealm.java b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/UserDatabaseRealm.java
new file mode 100644
index 0000000..623ddee
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/UserDatabaseRealm.java
@@ -0,0 +1,290 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.realm;
+
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.naming.Context;
+
+import org.apache.catalina.Group;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.Role;
+import org.apache.catalina.User;
+import org.apache.catalina.UserDatabase;
+import org.apache.catalina.Wrapper;
+import org.apache.tomcat.util.ExceptionUtils;
+
+
+/**
+ * <p>Implementation of {@link org.apache.catalina.Realm} that is based on an implementation of
+ * {@link UserDatabase} made available through the global JNDI resources
+ * configured for this instance of Catalina.  Set the <code>resourceName</code>
+ * parameter to the global JNDI resources name for the configured instance
+ * of <code>UserDatabase</code> that we should consult.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: UserDatabaseRealm.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ * @since 4.1
+ */
+
+public class UserDatabaseRealm
+    extends RealmBase {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The <code>UserDatabase</code> we will use to authenticate users
+     * and identify associated roles.
+     */
+    protected UserDatabase database = null;
+
+
+    /**
+     * Descriptive information about this Realm implementation.
+     */
+    protected static final String info =
+        "org.apache.catalina.realm.UserDatabaseRealm/1.0";
+
+
+    /**
+     * Descriptive information about this Realm implementation.
+     */
+    protected static final String name = "UserDatabaseRealm";
+
+
+    /**
+     * The global JNDI name of the <code>UserDatabase</code> resource
+     * we will be utilizing.
+     */
+    protected String resourceName = "UserDatabase";
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return descriptive information about this Realm 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 global JNDI name of the <code>UserDatabase</code> resource
+     * we will be using.
+     */
+    public String getResourceName() {
+
+        return resourceName;
+
+    }
+
+
+    /**
+     * Set the global JNDI name of the <code>UserDatabase</code> resource
+     * we will be using.
+     *
+     * @param resourceName The new global JNDI name
+     */
+    public void setResourceName(String resourceName) {
+
+        this.resourceName = resourceName;
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return <code>true</code> if the specified Principal has the specified
+     * security role, within the context of this Realm; otherwise return
+     * <code>false</code>. This implementation returns <code>true</code>
+     * if the <code>User</code> has the role, or if any <code>Group</code>
+     * that the <code>User</code> is a member of has the role. 
+     *
+     * @param principal Principal for whom the role is to be checked
+     * @param role Security role to be checked
+     */
+    @Override
+    public boolean hasRole(Wrapper wrapper, Principal principal, String role) {
+        // Check for a role alias defined in a <security-role-ref> element
+        if (wrapper != null) {
+            String realRole = wrapper.findSecurityReference(role);
+            if (realRole != null)
+                role = realRole;
+        }
+        if( principal instanceof GenericPrincipal) {
+            GenericPrincipal gp = (GenericPrincipal)principal;
+            if(gp.getUserPrincipal() instanceof User) {
+                principal = gp.getUserPrincipal();
+            }
+        }
+        if(! (principal instanceof User) ) {
+            //Play nice with SSO and mixed Realms
+            return super.hasRole(null, principal, role);
+        }
+        if("*".equals(role)) {
+            return true;
+        } else if(role == null) {
+            return false;
+        }
+        User user = (User)principal;
+        Role dbrole = database.findRole(role);
+        if(dbrole == null) {
+            return false; 
+        }
+        if(user.isInRole(dbrole)) {
+            return true;
+        }
+        Iterator<Group> groups = user.getGroups();
+        while(groups.hasNext()) {
+            Group group = groups.next();
+            if(group.isInRole(dbrole)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Return a short name for this Realm implementation.
+     */
+    @Override
+    protected String getName() {
+
+        return (name);
+
+    }
+
+
+    /**
+     * Return the password associated with the given principal's user name.
+     */
+    @Override
+    protected String getPassword(String username) {
+
+        User user = database.findUser(username);
+
+        if (user == null) {
+            return null;
+        } 
+
+        return (user.getPassword());
+
+    }
+
+
+    /**
+     * Return the Principal associated with the given user name.
+     */
+    @Override
+    protected Principal getPrincipal(String username) {
+
+        User user = database.findUser(username);
+        if(user == null) {
+            return null;
+        }
+
+        List<String> roles = new ArrayList<String>();
+        Iterator<Role> uroles = user.getRoles();
+        while(uroles.hasNext()) {
+            Role role = uroles.next();
+            roles.add(role.getName());
+        }
+        Iterator<Group> groups = user.getGroups();
+        while(groups.hasNext()) {
+            Group group = groups.next();
+            uroles = group.getRoles();
+            while(uroles.hasNext()) {
+                Role role = uroles.next();
+                roles.add(role.getName());
+            }
+        }
+        return new GenericPrincipal(username, user.getPassword(), roles, user);
+    }
+
+
+    // ------------------------------------------------------ Lifecycle Methods
+
+
+    /**
+     * Prepare for the beginning of active use of the public methods of 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 void startInternal() throws LifecycleException {
+
+        try {
+            Context context = getServer().getGlobalNamingContext();
+            database = (UserDatabase) context.lookup(resourceName);
+        } catch (Throwable e) {
+            ExceptionUtils.handleThrowable(e);
+            containerLog.error(sm.getString("userDatabaseRealm.lookup",
+                                            resourceName),
+                               e);
+            database = null;
+        }
+        if (database == null) {
+            throw new LifecycleException
+                (sm.getString("userDatabaseRealm.noDatabase", resourceName));
+        }
+
+        super.startInternal();
+    }
+
+
+    /**
+     * Gracefully terminate the active use of the public methods of this
+     * component and implement the requirements of
+     * {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
+     *
+     * @exception LifecycleException if this component detects a fatal error
+     *  that needs to be reported
+     */
+    @Override
+    protected void stopInternal() throws LifecycleException {
+
+        // Perform normal superclass finalization
+        super.stopInternal();
+
+        // Release reference to our user database
+        database = null;
+
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/realm/mbeans-descriptors.xml b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/mbeans-descriptors.xml
new file mode 100644
index 0000000..de1405d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/mbeans-descriptors.xml
@@ -0,0 +1,591 @@
+<?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="DataSourceRealm"
+            className="org.apache.catalina.mbeans.ClassNameMBean"
+          description="Implementation of Realm that works with any JNDI configured DataSource"
+               domain="Catalina"
+                group="Realm"
+                 type="org.apache.catalina.realm.DataSourceRealm">
+                
+    <attribute   name="allRolesMode"
+          description="The all roles mode."
+                 type="java.lang.String"/>
+
+    <attribute   name="className"
+          description="Fully qualified class name of the managed object"
+                 type="java.lang.String"
+            writeable="false"/>
+
+    <attribute   name="dataSourceName"
+          description="The JNDI named JDBC DataSource for your database"
+                 type="java.lang.String"/>
+
+    <attribute   name="digest"
+          description="Digest algorithm used in storing passwords in a non-plaintext format"
+                 type="java.lang.String"/>
+                 
+    <attribute   name="digestEncoding"
+          description="The digest encoding charset."
+                 type="java.lang.String"/>       
+
+    <attribute   name="localDataSource"
+          description="Configures if the DataSource is local to the webapp"
+                 type="boolean"/>
+                 
+    <attribute   name="realmPath"
+          description="The realm path"
+                 type="java.lang.String"/>
+
+    <attribute   name="roleNameCol"
+          description="The column in the user role table that names a role"
+                 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="userCredCol"
+          description="The column in the user table that holds the user's credentials"
+                 type="java.lang.String"/>
+
+    <attribute   name="userNameCol"
+          description="The column in the user table that holds the user's username"
+                 type="java.lang.String"/>
+
+    <attribute   name="userRoleTable"
+          description="The table that holds the relation between user's and roles"
+                 type="java.lang.String"/>
+
+    <attribute   name="userTable"
+          description="The table that holds user data"
+                 type="java.lang.String"/>
+                 
+    <attribute   name="validate"
+          description="The 'validate certificate chains' flag."
+                 type="boolean"/>
+
+
+    <operation name="start" description="Start" impact="ACTION" returnType="void" />
+    <operation name="stop" description="Stop" impact="ACTION" returnType="void" />
+    <operation name="init" description="Init" impact="ACTION" returnType="void" />
+    <operation name="destroy" description="Destroy" impact="ACTION" returnType="void" />
+
+  </mbean>
+
+  <mbean         name="JAASRealm"
+          description="Implmentation of Realm that authenticates users via the Java Authentication and Authorization Service (JAAS)"
+               domain="Catalina"
+                group="Realm"
+                 type="org.apache.catalina.realm.JAASRealm">
+                 
+    <attribute   name="allRolesMode"
+          description="The all roles mode."
+                 type="java.lang.String"/>
+
+    <attribute   name="appName"
+          description="The application name passed to the JAAS LoginContext, which uses it to select the set of relevant LoginModules"
+                 type="java.lang.String"/>
+
+    <attribute   name="className"
+          description="Fully qualified class name of the managed object"
+                 type="java.lang.String"
+            writeable="false"/>
+
+    <attribute   name="digest"
+          description="Digest algorithm used in storing passwords in a non-plaintext format"
+                 type="java.lang.String"/>
+                 
+    <attribute   name="digestEncoding"
+          description="The digest encoding charset."
+                 type="java.lang.String"/>
+
+    <attribute   name="roleClassNames"
+          description="Comma-delimited list of javax.security.Principal classes that represent security roles"
+                 type="java.lang.String"/>
+                 
+    <attribute   name="realmPath"
+          description="The realm path"
+                 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="userClassNames"
+          description="Comma-delimited list of javax.security.Principal classes that represent individual users"
+                 type="java.lang.String"/>
+                 
+    <attribute   name="useContextClassLoader"
+          description="Sets whether to use the context or default ClassLoader."
+                 type="boolean"/>
+
+    <attribute   name="validate"
+          description="Should we validate client certificate chains when they are presented?"
+                 type="boolean"/>
+
+
+    <operation name="start" description="Start" impact="ACTION" returnType="void" />
+    <operation name="stop" description="Stop" impact="ACTION" returnType="void" />
+    <operation name="init" description="Init" impact="ACTION" returnType="void" />
+    <operation name="destroy" description="Destroy" impact="ACTION" returnType="void" />
+  </mbean>
+
+
+  <mbean         name="JDBCRealm"
+          description="Implementation of Realm that works with any JDBC supported database"
+               domain="Catalina"
+                group="Realm"
+                 type="org.apache.catalina.realm.JDBCRealm">
+                 
+    <attribute   name="allRolesMode"
+          description="The all roles mode."
+                 type="java.lang.String"/>
+
+    <attribute   name="className"
+          description="Fully qualified class name of the managed object"
+                 type="java.lang.String"
+            writeable="false"/>
+
+    <attribute   name="connectionName"
+          description="The connection username to use when trying to connect to the database"
+                 type="java.lang.String"/>
+
+    <attribute   name="connectionPassword"
+          description="The connection password to use when trying to connect to the database"
+                 type="java.lang.String"/>
+
+    <attribute   name="connectionURL"
+          description="The connection URL to use when trying to connect to the database"
+                 type="java.lang.String"/>
+
+    <attribute   name="digest"
+          description="Digest algorithm used in storing passwords in a non-plaintext format"
+                 type="java.lang.String"/>
+                 
+    <attribute   name="digestEncoding"
+          description="The digest encoding charset."
+                 type="java.lang.String"/>
+
+    <attribute   name="driverName"
+          description="The JDBC driver to use"
+                 type="java.lang.String"/>
+
+    <attribute   name="roleNameCol"
+          description="The column in the user role table that names a role"
+                 type="java.lang.String"/>
+                 
+    <attribute   name="realmPath"
+          description="The realm path"
+                 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="userCredCol"
+          description="The column in the user table that holds the user's credentials"
+                 type="java.lang.String"/>
+
+    <attribute   name="userNameCol"
+          description="The column in the user table that holds the user's username"
+                 type="java.lang.String"/>
+
+    <attribute   name="userRoleTable"
+          description="The table that holds the relation between user's and roles"
+                 type="java.lang.String"/>
+
+    <attribute   name="userTable"
+          description="The table that holds user data"
+                 type="java.lang.String"/>
+                 
+    <attribute   name="validate"
+          description="The 'validate certificate chains' flag."
+                 type="boolean"/>
+
+
+    <operation name="start" description="Start" impact="ACTION" returnType="void" />
+    <operation name="stop" description="Stop" impact="ACTION" returnType="void" />
+    <operation name="init" description="Init" impact="ACTION" returnType="void" />
+    <operation name="destroy" description="Destroy" impact="ACTION" returnType="void" />
+  </mbean>
+
+  <mbean         name="JNDIRealm"
+          description="Implementation of Realm that works with a directory server accessed via the Java Naming and Directory Interface (JNDI) APIs"
+               domain="Catalina"
+                group="Realm"
+                 type="org.apache.catalina.realm.JNDIRealm">
+                 
+    <attribute   name="adCompat"
+          description=" The current settings for handling PartialResultExceptions"
+                 type="boolean"/>
+                 
+    <attribute   name="allRolesMode"
+          description="The all roles mode."
+                 type="java.lang.String"/>
+
+    <attribute   name="alternateURL"
+          description="The Alternate URL"
+                 type="java.lang.String"/>
+                 
+    <attribute   name="authentication"
+          description="The type of authentication to use"
+                 type="java.lang.String"/>
+
+    <attribute   name="className"
+          description="Fully qualified class name of the managed object"
+                 type="java.lang.String"
+            writeable="false"/>
+            
+    <attribute   name="commonRole"
+          description="The common role"
+                 type="java.lang.String"/>
+
+    <attribute   name="connectionName"
+          description="The connection username for the server we will contact"
+                 type="java.lang.String"/>
+
+    <attribute   name="connectionPassword"
+          description="The connection password for the server we will contact"
+                 type="java.lang.String"/>
+                 
+    <attribute   name="connectionTimeout"
+          description="The connection timeout"
+                 type="java.lang.String"/>
+
+    <attribute   name="connectionURL"
+          description="The connection URL for the server we will contact"
+                 type="java.lang.String"/>
+
+    <attribute   name="contextFactory"
+          description="The JNDI context factory for this Realm"
+                 type="java.lang.String"/>
+
+    <attribute   name="digest"
+          description="Digest algorithm used in storing passwords in a non-plaintext format"
+                 type="java.lang.String"/>
+                 
+    <attribute   name="digestEncoding"
+          description="The digest encoding charset."
+                 type="java.lang.String"/>
+                 
+    <attribute   name="protocol"
+          description="The protocol to be used"
+                 type="java.lang.String"/>
+                 
+    <attribute   name="realmPath"
+          description="The realm path"
+                 type="java.lang.String"/>
+                 
+    <attribute   name="referrals"
+          description="The current setting for handling JNDI referrals."
+                 type="java.lang.String"/>
+
+    <attribute   name="roleBase"
+          description="The base element for role searches"
+                 type="java.lang.String"/>
+
+    <attribute   name="roleName"
+          description="The name of the attribute containing roles held elsewhere"
+                 type="java.lang.String"/>
+                 
+    <attribute   name="roleNested"
+          description="The 'The nested group search flag' flag"
+                 type="boolean"/>
+
+    <attribute   name="roleSearch"
+          description="The message format used to select roles for a user"
+                 type="java.lang.String"/>
+
+    <attribute   name="roleSubtree"
+          description="Should we search the entire subtree for matching memberships?"
+                 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="userBase"
+          description="The base element for user searches"
+                 type="java.lang.String"/>
+
+    <attribute   name="userPassword"
+          description="The attribute name used to retrieve the user password"
+                 type="java.lang.String"/>
+
+    <attribute   name="userPattern"
+          description="The message format used to select a user"
+                 type="java.lang.String"/>
+
+     <attribute   name="userRoleName"
+          description="The name of the attribute in the user's entry containing roles for that user"
+                 type="java.lang.String"/>
+
+   <attribute   name="userSearch"
+         description="The message format used to search for a user"
+                type="java.lang.String"/>
+
+    <attribute   name="userSubtree"
+          description="Should we search the entire subtree for matching users?"
+                 type="boolean"/>
+                 
+    <attribute   name="validate"
+          description="The 'validate certificate chains' flag."
+                 type="boolean"/>
+
+
+    <operation name="start" description="Start" impact="ACTION" returnType="void" />
+    <operation name="stop" description="Stop" impact="ACTION" returnType="void" />
+    <operation name="init" description="Init" impact="ACTION" returnType="void" />
+    <operation name="destroy" description="Destroy" impact="ACTION" returnType="void" />
+  </mbean>
+
+  <mbean         name="MemoryRealm"
+          description="Simple implementation of Realm that reads an XML file to configure the valid users, passwords, and roles"
+               domain="Catalina"
+                group="Realm"
+                 type="org.apache.catalina.realm.MemoryRealm">
+                 
+    <attribute   name="allRolesMode"
+          description="The all roles mode."
+                 type="java.lang.String"/>
+
+    <attribute   name="className"
+          description="Fully qualified class name of the managed object"
+                 type="java.lang.String"
+            writeable="false"/>
+            
+    <attribute   name="digest"
+          description="Digest algorithm used in storing passwords in a non-plaintext format"
+                 type="java.lang.String"/>
+                 
+    <attribute   name="digestEncoding"
+          description="The digest encoding charset."
+                 type="java.lang.String"/>
+
+    <attribute   name="pathname"
+          description="The pathname of the XML file containing our database information"
+                 type="java.lang.String"/>
+                 
+    <attribute   name="realmPath"
+          description="The realm path"
+                 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="validate"
+          description="The 'validate certificate chains' flag."
+                 type="boolean"/>
+
+    <operation name="start" description="Start" impact="ACTION" returnType="void" />
+    <operation name="stop" description="Stop" impact="ACTION" returnType="void" />
+    <operation name="init" description="Init" impact="ACTION" returnType="void" />
+    <operation name="destroy" description="Destroy" impact="ACTION" returnType="void" />
+
+  </mbean>
+
+  <mbean         name="UserDatabaseRealm"
+          description="Realm connected to a UserDatabase as a global JNDI resource"
+               domain="Catalina"
+                group="Realm"
+                 type="org.apache.catalina.realm.UserDatabaseRealm">
+                 
+    <attribute   name="allRolesMode"
+          description="The all roles mode."
+                 type="java.lang.String"/>
+
+    <attribute   name="className"
+          description="Fully qualified class name of the managed object"
+                 type="java.lang.String"
+            writeable="false"/>
+            
+    <attribute   name="digest"
+          description="Digest algorithm used in storing passwords in a non-plaintext format"
+                 type="java.lang.String"/>
+                 
+    <attribute   name="digestEncoding"
+          description="The digest encoding charset."
+                 type="java.lang.String"/>
+
+    <attribute   name="resourceName"
+          description="The global JNDI name of the UserDatabase resource to use"
+                 type="java.lang.String"/>
+                 
+    <attribute   name="realmPath"
+          description="The realm path"
+                 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="validate"
+          description="The 'validate certificate chains' flag."
+                 type="boolean"/>
+                 
+    <operation name="start" description="Start" impact="ACTION" returnType="void" />
+    <operation name="stop" description="Stop" impact="ACTION" returnType="void" />
+    <operation name="init" description="Init" impact="ACTION" returnType="void" />
+    <operation name="destroy" description="Destroy" impact="ACTION" returnType="void" />
+
+  </mbean>
+
+  <mbean         name="CombinedRealm"
+          description="Realm implementation that can be used to chain multiple realms"
+               domain="Catalina"
+                group="Realm"
+                 type="org.apache.catalina.realm.CombinedRealm">
+                 
+    <attribute   name="allRolesMode"
+          description="The all roles mode."
+                 type="java.lang.String"/>
+
+    <attribute   name="className"
+          description="Fully qualified class name of the managed object"
+                 type="java.lang.String"
+            writeable="false"/>
+            
+    <attribute   name="digest"
+          description="Digest algorithm used in storing passwords in a non-plaintext format"
+                 type="java.lang.String"/>
+                 
+    <attribute   name="digestEncoding"
+          description="The digest encoding charset."
+                 type="java.lang.String"/>
+
+    <attribute   name="realms"
+          description="The set of realms that the combined realm is wrapping"
+                 type="[Ljavax.management.ObjectName;"
+            writeable="false"/>
+            
+    <attribute   name="realmPath"
+          description="The realm path"
+                 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="validate"
+          description="The 'validate certificate chains' flag."
+                 type="boolean"/>
+
+    <operation   name="addRealm"
+          description="Add a new Realm to the set of Realms wrapped by this realm"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="theRealm"
+                 description="New Realm to add"
+                 type="org.apache.catalina.Realm"/>
+    </operation>
+
+    <operation name="start" description="Start" impact="ACTION" returnType="void" />
+    <operation name="stop" description="Stop" impact="ACTION" returnType="void" />
+    <operation name="init" description="Init" impact="ACTION" returnType="void" />
+    <operation name="destroy" description="Destroy" impact="ACTION" returnType="void" />
+
+  </mbean>
+
+  <mbean         name="LockOutRealm"
+          description="Realm implementation that can be used to wrap existing realms to provide a user lock-out capability"
+               domain="Catalina"
+                group="Realm"
+                 type="org.apache.catalina.realm.LockOutRealm">
+                 
+    <attribute   name="allRolesMode"
+          description="The all roles mode."
+                 type="java.lang.String"/>
+                
+    <attribute   name="cacheSize"
+          description="Number of users that have failed authentication to keep in cache. Over time the cache will grow to this size and may not shrink. Defaults to 1000."
+                 type="int" />
+                 
+    <attribute   name="cacheRemovalWarningTime"
+          description="If a failed user is removed from the cache because the cache is too big before it has been in the cache for at least this period of time (in seconds) a warning message will be logged. Defaults to 3600 (1 hour)."
+                 type="int" />
+
+    <attribute   name="className"
+          description="Fully qualified class name of the managed object"
+                 type="java.lang.String"
+            writeable="false"/>
+            
+    <attribute   name="digest"
+          description="Digest algorithm used in storing passwords in a non-plaintext format"
+                 type="java.lang.String"/>
+                 
+    <attribute   name="failureCount"
+          description="The number of times in a row a user has to fail authentication to be locked out. Defaults to 5."
+                 type="int" />
+
+    <attribute   name="lockOutTime"
+          description="The time (in seconds) a user is locked out for after too many authentication failures. Defaults to 300 (5 minutes)."
+                 type="int" />
+                 
+    <attribute   name="digestEncoding"
+          description="The digest encoding charset."
+                 type="java.lang.String"/>
+
+    <attribute   name="realms"
+          description="The set of realms that the lockout realm is wrapping"
+                 type="[Ljavax.management.ObjectName;"
+            writeable="false"/>
+            
+    <attribute   name="realmPath"
+          description="The realm path"
+                 type="java.lang.String"/>
+
+    <attribute   name="validate"
+          description="The 'validate certificate chains' flag."
+                 type="boolean"/>
+                 
+    <operation   name="addRealm"
+          description="Add a new Realm to the set of Realms wrapped by this realm"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="theRealm"
+                 description="New Realm to add"
+                 type="org.apache.catalina.Realm"/>
+    </operation>
+
+    <operation   name="unlock"
+          description="Unlock the specified user"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="username"
+                 description="User to unlock"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation name="start" description="Start" impact="ACTION" returnType="void" />
+    <operation name="stop" description="Stop" impact="ACTION" returnType="void" />
+    <operation name="init" description="Init" impact="ACTION" returnType="void" />
+    <operation name="destroy" description="Destroy" impact="ACTION" returnType="void" />
+
+  </mbean>
+
+</mbeans-descriptors>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/realm/package.html b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/package.html
new file mode 100644
index 0000000..fdbb97e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/realm/package.html
@@ -0,0 +1,80 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<body>
+
+<p>This package contains <code>Realm</code> implementations for the
+various supported realm technologies for authenticating users and
+identifying their associated roles.  The <code>Realm</code> that is
+associated with a web application's <code>Context</code> (or a hierarchically
+superior Container) is used to resolve authentication and role presence
+questions when a web application uses container managed security as described
+in the Servlet API Specification, version 2.2.</p>
+
+<p>The implementations share a common base class that supports basic
+functionality for all of the standard <code>Realm</code> implementations,
+and can be configured by setting the following properties (default values
+are in square brackets):</p>
+<ul>
+<li><b>debug</b> - Debugging detail level for this component. [0]</li>
+</ul>
+
+<p>The standard <code>Realm</code> implementations that are currently
+available include the following (with additional configuration properties
+as specified):</p>
+<ul>
+<li><b>JDBCRealm</b> - Implementation of <code>Realm</code> that operates
+    from data stored in a relational database that is accessed via a JDBC
+    driver.  The name of the driver, database connection information, and
+    the names of the relevant tables and columns are configured with the
+    following additional properties:
+    <ul>
+    <li><b>connectionURL</b> - The URL to use when connecting to this database.
+        [REQUIRED - NO DEFAULT]</li>
+    <li><b>driverName</b> - Fully qualified Java class name of the JDBC driver
+        to be used.  [REQUIRED - NO DEFAULT]</li>
+    <li><b>roleNameCol</b> - Name of the database column that contains role
+        names.  [REQUIRED - NO DEFAULT]</li>
+    <li><b>userCredCol</b> - Name of the database column that contains the
+        user's credentials (i.e. password) in cleartext.  [REQUIRED -
+        NO DEFAULT]</li>
+    <li><b>userNameCol</b> - Name of the database column that contains the
+        user's logon username.  [REQUIRED - NO DEFAULT]</li>
+    <li><b>userRoleTable</b> - Name of the database table containing user
+        role information.  This table must include the columns specified by
+        the <code>userNameCol</code> and <code>roleNameCol</code> properties.
+        [REQUIRED - NO DEFAULT]</li>
+    <li><b>userTable</b> - Name of the database table containing user
+        information.  This table must include the columns specified by the
+        <code>userNameCol</code> and <code>userCredCol</code> properties.
+        [REQUIRED - NO DEFAULT]</li>
+    </ul>
+    </li>
+<li><b>MemoryRealm</b> - Implementation of <code>Realm</code> that uses the
+    contents of a simple XML file (<code>conf/tomcat-users.xml</code>) as the
+    list of valid users and their roles.  This implementation is primarily to
+    demonstrate that the authentication technology functions correctly, and is
+    not anticipated as adequate for general purpose use.  This component
+    supports the following additional properties:
+    <ul>
+    <li><b>pathname</b> - Pathname of the XML file containing our user and
+        role information.  If a relative pathname is specified, it is resolved
+        against the pathname specified by the "catalina.home" system property.
+        [conf/tomcat-users.xml]</li>
+    </ul>
+</ul>
+
+</body>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/security/Constants.java b/bundles/org.apache.tomcat/src/org/apache/catalina/security/Constants.java
new file mode 100644
index 0000000..ac42683
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/security/Constants.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.catalina.security;
+
+public class Constants {
+
+    public static final String PACKAGE = "org.apache.catalina.security";
+
+    public static final String LINE_SEP = System.getProperty("line.separator");
+    public static final String CRLF = "\r\n";
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/security/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/security/LocalStrings.properties
new file mode 100644
index 0000000..a8439bf
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/security/LocalStrings.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.
+
+SecurityUtil.doAsPrivilege=An exception occurs when running the PrivilegedExceptionAction block.
+SecurityListener.checkUmaskFail=Start attempted with umask setting of [{0}]. Running Tomcat without a umask at least as restrictive as [{1}] has been blocked by the Lifecycle listener org.apache.catalina.security.SecurityListener (usually configured in CATALINA_BASE/conf/server.xml)
+SecurityListener.checkUmaskNone=No umask setting was found in system property [{0}]. However, it appears Tomcat is running on a platform that supports umask. The system property is typically set in CATALINA_HOME/bin/catalina.sh. The Lifecycle listener org.apache.catalina.security.SecurityListener (usually configured in CATALINA_BASE/conf/server.xml) expects a umask at least as restrictive as [{1}]
+SecurityListener.checkUmaskParseFail=Failed to parse value [{0}] as a valid umask.
+SecurityListener.checkUmaskSkip=Unable to determine umask. It appears Tomcat is running on Windows so skip the umask check.
+SecurityListener.checkUserWarning=Start attempted while running as user [{0}]. Running Tomcat as this user has been blocked by the Lifecycle listener org.apache.catalina.security.SecurityListener (usually configured in CATALINA_BASE/conf/server.xml)
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/security/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/security/LocalStrings_es.properties
new file mode 100644
index 0000000..593f847
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/security/LocalStrings_es.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.
+
+SecurityUtil.doAsPrivilege=Ha tenido lugar una excepci\u00f3n al ejecutar el bloque PrivilegedExceptionAction.
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/security/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/security/LocalStrings_fr.properties
new file mode 100644
index 0000000..fe6388e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/security/LocalStrings_fr.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.
+
+SecurityUtil.doAsPrivilege=Une exception s''est produite lors de l''ex\u00e9cution du bloc "PrivilegedExceptionAction".
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/security/LocalStrings_ja.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/security/LocalStrings_ja.properties
new file mode 100644
index 0000000..34fd2ba
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/security/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.
+
+SecurityUtil.doAsPrivilege=PrivilegedExceptionAction\u30d6\u30ed\u30c3\u30af\u3092\u5b9f\u884c\u4e2d\u306b\u4f8b\u5916\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/security/SecurityClassLoad.java b/bundles/org.apache.tomcat/src/org/apache/catalina/security/SecurityClassLoad.java
new file mode 100644
index 0000000..057f35c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/security/SecurityClassLoad.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.catalina.security;
+
+/**
+ * Static class used to preload java classes when using the
+ * Java SecurityManager so that the defineClassInPackage
+ * RuntimePermission does not trigger an AccessControlException.
+ *
+ * @author Glenn L. Nielsen
+ * @author Jean-Francois Arcand
+ * @version $Id: SecurityClassLoad.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public final class SecurityClassLoad {
+
+    public static void securityClassLoad(ClassLoader loader)
+        throws Exception {
+
+        if( System.getSecurityManager() == null ){
+            return;
+        }
+        
+        loadCorePackage(loader);
+        loadCoyotePackage(loader);
+        loadLoaderPackage(loader);
+        loadRealmPackage(loader);
+        loadSessionPackage(loader);
+        loadUtilPackage(loader);
+        loadJavaxPackage(loader);
+        loadConnectorPackage(loader);        
+        loadTomcatPackage(loader);
+    }
+    
+    
+    private static final void loadCorePackage(ClassLoader loader)
+        throws Exception {
+        final String basePackage = "org.apache.catalina.core.";
+        loader.loadClass
+            (basePackage +
+             "ApplicationContextFacade$1");
+        loader.loadClass
+            (basePackage +
+             "ApplicationDispatcher$PrivilegedForward");
+        loader.loadClass
+            (basePackage +
+             "ApplicationDispatcher$PrivilegedInclude");
+        loader.loadClass
+            (basePackage +
+            "AsyncContextImpl");
+        loader.loadClass
+            (basePackage +
+            "AsyncContextImpl$DebugException");
+        loader.loadClass
+            (basePackage +
+            "AsyncContextImpl$1");
+        loader.loadClass
+            (basePackage +
+            "AsyncListenerWrapper");
+        loader.loadClass
+            (basePackage +
+             "ContainerBase$PrivilegedAddChild");
+        loader.loadClass
+            (basePackage +
+             "DefaultInstanceManager$1");
+        loader.loadClass
+            (basePackage +
+             "DefaultInstanceManager$2");
+        loader.loadClass
+            (basePackage +
+             "DefaultInstanceManager$3");
+        loader.loadClass
+            (basePackage +
+             "DefaultInstanceManager$4");
+        loader.loadClass
+            (basePackage +
+             "DefaultInstanceManager$5");
+        loader.loadClass
+            (basePackage +
+             "ApplicationHttpRequest$AttributeNamesEnumerator");
+    }
+    
+    
+    private static final void loadLoaderPackage(ClassLoader loader)
+        throws Exception {
+        final String basePackage = "org.apache.catalina.loader.";
+        loader.loadClass
+            (basePackage +
+             "WebappClassLoader$PrivilegedFindResourceByName");
+    }
+    
+    
+    private static final void loadRealmPackage(ClassLoader loader)
+            throws Exception {
+        final String basePackage = "org.apache.catalina.realm.";
+        loader.loadClass
+            (basePackage + "LockOutRealm$LockRecord");
+    }
+
+
+    private static final void loadSessionPackage(ClassLoader loader)
+        throws Exception {
+        final String basePackage = "org.apache.catalina.session.";
+        loader.loadClass
+            (basePackage + "StandardSession");
+        loader.loadClass
+            (basePackage + "StandardSession$PrivilegedSetTccl");
+        loader.loadClass
+            (basePackage + "StandardSession$1");
+        loader.loadClass
+            (basePackage + "StandardManager$PrivilegedDoUnload");
+    }
+    
+    
+    private static final void loadUtilPackage(ClassLoader loader)
+        throws Exception {
+        final String basePackage = "org.apache.catalina.util.";
+        loader.loadClass(basePackage + "Enumerator");
+        loader.loadClass(basePackage + "ParameterMap");
+    }
+    
+    
+    private static final void loadCoyotePackage(ClassLoader loader)
+            throws Exception {
+        final String basePackage = "org.apache.coyote.";
+        loader.loadClass(basePackage + "http11.AbstractOutputBuffer$1");
+        // Make sure system property is read at this point
+        Class<?> clazz = loader.loadClass(basePackage + "Constants");
+        clazz.newInstance();
+    }
+
+
+    private static final void loadJavaxPackage(ClassLoader loader)
+        throws Exception {
+        loader.loadClass("javax.servlet.http.Cookie");
+    }
+    
+
+    private static final void loadConnectorPackage(ClassLoader loader)
+        throws Exception {
+        final String basePackage = "org.apache.catalina.connector.";
+        loader.loadClass
+            (basePackage +
+             "RequestFacade$GetAttributePrivilegedAction");
+        loader.loadClass
+            (basePackage +
+             "RequestFacade$GetParameterMapPrivilegedAction");
+        loader.loadClass
+            (basePackage +
+             "RequestFacade$GetRequestDispatcherPrivilegedAction");
+        loader.loadClass
+            (basePackage +
+             "RequestFacade$GetParameterPrivilegedAction");
+        loader.loadClass
+            (basePackage +
+             "RequestFacade$GetParameterNamesPrivilegedAction");
+        loader.loadClass
+            (basePackage +
+             "RequestFacade$GetParameterValuePrivilegedAction");
+        loader.loadClass
+            (basePackage +
+             "RequestFacade$GetCharacterEncodingPrivilegedAction");
+        loader.loadClass
+            (basePackage +
+             "RequestFacade$GetHeadersPrivilegedAction");
+        loader.loadClass
+            (basePackage +
+             "RequestFacade$GetHeaderNamesPrivilegedAction");  
+        loader.loadClass
+            (basePackage +
+             "RequestFacade$GetCookiesPrivilegedAction");
+        loader.loadClass
+            (basePackage +
+             "RequestFacade$GetLocalePrivilegedAction");
+        loader.loadClass
+            (basePackage +
+             "RequestFacade$GetLocalesPrivilegedAction");
+        loader.loadClass
+            (basePackage +
+             "ResponseFacade$SetContentTypePrivilegedAction");
+        loader.loadClass
+            (basePackage + 
+             "ResponseFacade$DateHeaderPrivilegedAction");
+        loader.loadClass
+            (basePackage +
+             "RequestFacade$GetSessionPrivilegedAction");
+        loader.loadClass
+            (basePackage +
+             "ResponseFacade$1");
+        loader.loadClass
+            (basePackage +
+             "OutputBuffer$1");
+        loader.loadClass
+            (basePackage +
+             "CoyoteInputStream$1");
+        loader.loadClass
+            (basePackage +
+             "CoyoteInputStream$2");
+        loader.loadClass
+            (basePackage +
+             "CoyoteInputStream$3");
+        loader.loadClass
+            (basePackage +
+             "CoyoteInputStream$4");
+        loader.loadClass
+            (basePackage +
+             "CoyoteInputStream$5");
+        loader.loadClass
+            (basePackage +
+             "InputBuffer$1");
+        loader.loadClass
+            (basePackage +
+             "Response$1");
+        loader.loadClass
+            (basePackage +
+             "Response$2");
+        loader.loadClass
+            (basePackage +
+             "Response$3");
+    }
+
+    private static final void loadTomcatPackage(ClassLoader loader)
+        throws Exception {
+        final String basePackage = "org.apache.tomcat.";
+        loader.loadClass(basePackage + "util.buf.StringCache");
+        loader.loadClass(basePackage + "util.buf.StringCache$ByteEntry");
+        loader.loadClass(basePackage + "util.buf.StringCache$CharEntry");
+        loader.loadClass(basePackage + "util.http.HttpMessages");
+        // Make sure system property is read at this point
+        Class<?> clazz = loader.loadClass(
+                basePackage + "util.http.FastHttpDateFormat");
+        clazz.newInstance();
+        loader.loadClass(basePackage + "util.http.HttpMessages");
+        loader.loadClass(basePackage + "util.net.Constants");
+        loader.loadClass(basePackage + "util.net.SSLSupport$CipherData");
+        loader.loadClass
+            (basePackage + "util.net.JIoEndpoint$PrivilegedSetTccl");
+        loader.loadClass
+            (basePackage + "util.net.AprEndpoint$PrivilegedSetTccl");
+    }
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/security/SecurityConfig.java b/bundles/org.apache.tomcat/src/org/apache/catalina/security/SecurityConfig.java
new file mode 100644
index 0000000..64488d6
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/security/SecurityConfig.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.catalina.security;
+
+import java.security.Security;
+
+import org.apache.catalina.startup.CatalinaProperties;
+
+/**
+ * Util class to protect Catalina against package access and insertion.
+ * The code are been moved from Catalina.java
+ * @author the Catalina.java authors
+ * @author Jean-Francois Arcand
+ */
+public final class SecurityConfig{
+    private static SecurityConfig singleton = null;
+
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( SecurityConfig.class );
+
+    
+    private static final String PACKAGE_ACCESS =  "sun.,"
+                                                + "org.apache.catalina." 
+                                                + ",org.apache.jasper."
+                                                + ",org.apache.coyote."
+                                                + ",org.apache.tomcat.";
+    
+    private static final String PACKAGE_DEFINITION= "java.,sun."
+                                                + ",org.apache.catalina." 
+                                                + ",org.apache.coyote."
+                                                + ",org.apache.tomcat."
+                                                + ",org.apache.jasper.";
+    /**
+     * List of protected package from conf/catalina.properties
+     */
+    private String packageDefinition;
+    
+    
+    /**
+     * List of protected package from conf/catalina.properties
+     */
+    private String packageAccess; 
+    
+    
+    /**
+     * Create a single instance of this class.
+     */
+    private SecurityConfig(){  
+        try{
+            packageDefinition = CatalinaProperties.getProperty("package.definition");
+            packageAccess = CatalinaProperties.getProperty("package.access");
+        } catch (java.lang.Exception ex){
+            if (log.isDebugEnabled()){
+                log.debug("Unable to load properties using CatalinaProperties", ex); 
+            }            
+        }
+    }
+    
+    
+    /**
+     * Returns the singleton instance of that class.
+     * @return an instance of that class.
+     */
+    public static SecurityConfig newInstance(){
+        if (singleton == null){
+            singleton = new SecurityConfig();
+        }
+        return singleton;
+    }
+    
+    
+    /**
+     * Set the security package.access value.
+     */
+    public void setPackageAccess(){
+        // If catalina.properties is missing, protect all by default.
+        if (packageAccess == null){
+            setSecurityProperty("package.access", PACKAGE_ACCESS);   
+        } else {
+            setSecurityProperty("package.access", packageAccess);   
+        }
+    }
+    
+    
+    /**
+     * Set the security package.definition value.
+     */
+     public void setPackageDefinition(){
+        // If catalina.properties is missing, protect all by default.
+         if (packageDefinition == null){
+            setSecurityProperty("package.definition", PACKAGE_DEFINITION);
+         } else {
+            setSecurityProperty("package.definition", packageDefinition);
+         }
+    }
+     
+     
+    /**
+     * Set the proper security property
+     * @param properties the package.* property.
+     */
+    private final void setSecurityProperty(String properties, String packageList){
+        if (System.getSecurityManager() != null){
+            String definition = Security.getProperty(properties);
+            if( definition != null && definition.length() > 0 ){
+                definition += ",";
+            }
+
+            Security.setProperty(properties,
+                // FIX ME package "javax." was removed to prevent HotSpot
+                // fatal internal errors
+                definition + packageList);      
+        }
+    }
+    
+    
+}
+
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/security/SecurityListener.java b/bundles/org.apache.tomcat/src/org/apache/catalina/security/SecurityListener.java
new file mode 100644
index 0000000..fe4e2b1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/security/SecurityListener.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.security;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+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;
+
+public class SecurityListener implements LifecycleListener {
+
+    private static final Log log = LogFactory.getLog(SecurityListener.class);
+
+    private static final StringManager sm =
+        StringManager.getManager(Constants.PACKAGE);    
+
+    private static final String UMASK_PROPERTY_NAME =
+        Constants.PACKAGE + ".SecurityListener.UMASK";
+
+    private static final String UMASK_FORMAT = "%04o";
+
+    /**
+     * The list of operating system users not permitted to run Tomcat.
+     */
+    private Set<String> checkedOsUsers = new HashSet<String>();
+
+    /**
+     * The minimum umask that must be configured for the operating system user
+     * running Tomcat. The umask is handled as an octal.
+     */
+    private Integer minimumUmask = Integer.valueOf(7);
+
+
+    public SecurityListener() {
+        checkedOsUsers.add("root");
+    }
+
+
+    @Override
+    public void lifecycleEvent(LifecycleEvent event) {
+        // This is the earliest event in Lifecycle
+        if (event.getType().equals(Lifecycle.BEFORE_INIT_EVENT)) {
+            doChecks();
+        }
+    }
+
+
+    /**
+     * Set the list of operating system users not permitted to run Tomcat. By
+     * default, only root is prevented from running Tomcat. Calling this method
+     * with null or the empty string will clear the list of users and
+     * effectively disables this check. User names will always be checked in a
+     * case insensitive manner.
+     * 
+     * @param userNameList  A comma separated list of operating system users not
+     *                      permitted to run Tomcat
+     */
+    public void setCheckedOsUsers(String userNameList) {
+        if (userNameList == null || userNameList.length() == 0) {
+            checkedOsUsers.clear();
+        } else {
+            String[] userNames = userNameList.split(",");
+            for (String userName : userNames) {
+                if (userName.length() > 0) {
+                    checkedOsUsers.add(userName);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Returns the current list of operating system users not permitted to run
+     * Tomcat.
+     * 
+     * @return  A comma separated list of operating sytem user names.
+     */
+    public String getCheckedOsUsers() {
+        if (checkedOsUsers.size() == 0) {
+            return "";
+        }
+
+        StringBuilder result = new StringBuilder();
+        Iterator<String> iter = checkedOsUsers.iterator();
+        result.append(iter.next());
+        while (iter.hasNext()) {
+            result.append(',');
+            result.append(iter.next());
+        }
+        return result.toString();
+    }
+
+
+    /**
+     * Set the minimum umask that must be configured before Tomcat will start.
+     * 
+     * @param umask The 4-digit umask as returned by the OS command <i>umask</i>
+     */
+    public void setMinimumUmask(String umask) {
+        if (umask == null || umask.length() == 0) {
+            minimumUmask = Integer.valueOf(0);
+        } else {
+            minimumUmask = Integer.valueOf(umask, 8);
+        }
+    }
+
+
+    /**
+     * Get the minimum umask that must be configured before Tomcat will start.
+     * 
+     * @return  The 4-digit umask as used by the OS command <i>umask</i>
+     */
+    public String getMinimumUmask() {
+        return String.format(UMASK_FORMAT, minimumUmask);
+    }
+
+
+    /**
+     * Execute the security checks. Each check should be in a separate method.
+     */
+    protected void doChecks() {
+        checkOsUser();
+        checkUmask();
+    }
+    
+
+    protected void checkOsUser() {
+        String userName = System.getProperty("user.name");
+        if (userName != null) {
+            String userNameLC = userName.toLowerCase();
+        
+            if (checkedOsUsers.contains(userNameLC)) {
+                // Have to throw Error to force start process to be aborted
+                throw new Error(sm.getString(
+                        "SecurityListener.checkUserWarning", userName));
+            }
+        }
+    }
+    
+
+    protected void checkUmask() {
+        String prop = System.getProperty(UMASK_PROPERTY_NAME);
+        Integer umask = null;
+        if (prop != null) {
+            try {
+                 umask = Integer.valueOf(prop, 8);
+            } catch (NumberFormatException nfe) {
+                log.warn(sm.getString("SecurityListener.checkUmaskParseFail",
+                        prop));
+            }
+        }
+        if (umask == null) {
+            if (Constants.CRLF.equals(Constants.LINE_SEP)) {
+                // Probably running on Windows so no umask
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString("SecurityListener.checkUmaskSkip"));
+                }
+                return;
+            } else {
+                if (minimumUmask.intValue() > 0) {
+                    log.warn(sm.getString(
+                            "SecurityListener.checkUmaskNone",
+                            UMASK_PROPERTY_NAME, getMinimumUmask()));
+                }
+                return;
+            }
+        }
+        
+        if ((umask.intValue() & minimumUmask.intValue()) !=
+                minimumUmask.intValue()) {
+            throw new Error(sm.getString("SecurityListener.checkUmaskFail",
+                    String.format(UMASK_FORMAT, umask), getMinimumUmask()));
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/security/SecurityUtil.java b/bundles/org.apache.tomcat/src/org/apache/catalina/security/SecurityUtil.java
new file mode 100644
index 0000000..0e1eddb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/security/SecurityUtil.java
@@ -0,0 +1,429 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.security;
+
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.Principal;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.security.auth.Subject;
+import javax.servlet.Filter;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.apache.catalina.Globals;
+import org.apache.tomcat.util.res.StringManager;
+/**
+ * This utility class associates a <code>Subject</code> to the current 
+ * <code>AccessControlContext</code>. When a <code>SecurityManager</code> is
+ * used, * the container will always associate the called thread with an 
+ * AccessControlContext * containing only the principal of the requested
+ * Servlet/Filter.
+ *
+ * This class uses reflection to invoke the invoke methods.
+ *
+ * @author Jean-Francois Arcand
+ */
+
+public final class SecurityUtil{
+    
+    private static final int INIT= 0;
+    private static final int SERVICE = 1;
+    private static final int DOFILTER = 1;
+    private static final int EVENT = 2;
+    private static final int DOFILTEREVENT = 2;
+    private static final int DESTROY = 3;
+    
+    private static final String INIT_METHOD = "init";
+    private static final String DOFILTER_METHOD = "doFilter";
+    private static final String SERVICE_METHOD = "service";
+    private static final String EVENT_METHOD = "event";
+    private static final String DOFILTEREVENT_METHOD = "doFilterEvent";
+    private static final String DESTROY_METHOD = "destroy";
+   
+    /**
+     * Cache every object for which we are creating method on it.
+     */
+    private static final Map<Object,Method[]> objectCache =
+        new ConcurrentHashMap<Object,Method[]>();
+        
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( SecurityUtil.class );
+    
+    private static boolean packageDefinitionEnabled =  
+         (System.getProperty("package.definition") == null && 
+           System.getProperty("package.access")  == null) ? false : true;
+    
+    /**
+     * The string resources for this package.
+     */
+    private static final StringManager sm =
+        StringManager.getManager(Constants.PACKAGE);    
+    
+    
+    /**
+     * Perform work as a particular </code>Subject</code>. Here the work
+     * will be granted to a <code>null</code> subject. 
+     *
+     * @param methodName the method to apply the security restriction
+     * @param targetObject the <code>Servlet</code> on which the method will
+     * be called.
+     */
+    public static void doAsPrivilege(final String methodName, 
+                                     final Servlet targetObject) throws java.lang.Exception{
+         doAsPrivilege(methodName, targetObject, null, null, null);                                
+    }
+
+    
+    /**
+     * Perform work as a particular </code>Subject</code>. Here the work
+     * will be granted to a <code>null</code> subject. 
+     *
+     * @param methodName the method to apply the security restriction
+     * @param targetObject the <code>Servlet</code> on which the method will
+     * be called.
+     * @param targetType <code>Class</code> array used to instantiate a
+     * <code>Method</code> object.
+     * @param targetArguments <code>Object</code> array contains the runtime 
+     * parameters instance.
+     */
+    public static void doAsPrivilege(final String methodName, 
+                                     final Servlet targetObject, 
+                                     final Class<?>[] targetType,
+                                     final Object[] targetArguments) 
+        throws java.lang.Exception{    
+
+         doAsPrivilege(methodName, 
+                       targetObject, 
+                       targetType, 
+                       targetArguments, 
+                       null);                                
+    }
+    
+    
+    /**
+     * Perform work as a particular </code>Subject</code>. Here the work
+     * will be granted to a <code>null</code> subject. 
+     *
+     * @param methodName the method to apply the security restriction
+     * @param targetObject the <code>Servlet</code> on which the method will
+     * be called.
+     * @param targetType <code>Class</code> array used to instantiate a 
+     * <code>Method</code> object.
+     * @param targetArguments <code>Object</code> array contains the 
+     * runtime parameters instance.
+     * @param principal the <code>Principal</code> to which the security 
+     * privilege apply..
+     */    
+    public static void doAsPrivilege(final String methodName, 
+                                     final Servlet targetObject, 
+                                     final Class<?>[] targetType,
+                                     final Object[] targetArguments,
+                                     Principal principal) 
+        throws java.lang.Exception{
+
+        Method method = null;
+        Method[] methodsCache = objectCache.get(targetObject);
+        if(methodsCache == null) {
+            method = createMethodAndCacheIt(methodsCache,
+                                            methodName,
+                                            targetObject,
+                                            targetType);                     
+        } else {
+            method = findMethod(methodsCache, methodName);
+            if (method == null) {
+                method = createMethodAndCacheIt(methodsCache,
+                                                methodName,
+                                                targetObject,
+                                                targetType);
+            }
+        }
+
+        execute(method, targetObject, targetArguments, principal);
+    }
+ 
+    
+    /**
+     * Perform work as a particular </code>Subject</code>. Here the work
+     * will be granted to a <code>null</code> subject. 
+     *
+     * @param methodName the method to apply the security restriction
+     * @param targetObject the <code>Filter</code> on which the method will 
+     * be called.
+     */    
+    public static void doAsPrivilege(final String methodName, 
+                                     final Filter targetObject) 
+        throws java.lang.Exception{
+
+         doAsPrivilege(methodName, targetObject, null, null);                                
+    }
+ 
+    
+    /**
+     * Perform work as a particular <code>Subject</code>. Here the work
+     * will be granted to a <code>null</code> subject. 
+     *
+     * @param methodName the method to apply the security restriction
+     * @param targetObject the <code>Filter</code> on which the method will 
+     * be called.
+     * @param targetType <code>Class</code> array used to instantiate a
+     * <code>Method</code> object.
+     * @param targetArguments <code>Object</code> array contains the 
+     * runtime parameters instance.
+     */    
+    public static void doAsPrivilege(final String methodName, 
+                                     final Filter targetObject, 
+                                     final Class<?>[] targetType,
+                                     final Object[] targetArguments) 
+        throws java.lang.Exception{
+
+        doAsPrivilege(
+                methodName, targetObject, targetType, targetArguments, null);
+    }
+    
+    /**
+     * Perform work as a particular <code>Subject</code>. Here the work
+     * will be granted to a <code>null</code> subject. 
+     *
+     * @param methodName the method to apply the security restriction
+     * @param targetObject the <code>Filter</code> on which the method will 
+     * be called.
+     * @param targetType <code>Class</code> array used to instantiate a
+     * <code>Method</code> object.
+     * @param targetArguments <code>Object</code> array contains the 
+     * runtime parameters instance.
+     * @param principal the <code>Principal</code> to which the security 
+     * privilege apply
+     */    
+    public static void doAsPrivilege(final String methodName, 
+                                     final Filter targetObject, 
+                                     final Class<?>[] targetType,
+                                     final Object[] targetArguments,
+                                     Principal principal) 
+        throws java.lang.Exception{
+        
+        Method method = null;
+        Method[] methodsCache = objectCache.get(targetObject);
+        if(methodsCache == null) {
+            method = createMethodAndCacheIt(methodsCache,
+                                            methodName,
+                                            targetObject,
+                                            targetType);                     
+        } else {
+            method = findMethod(methodsCache, methodName);
+            if (method == null) {
+                method = createMethodAndCacheIt(methodsCache,
+                                                methodName,
+                                                targetObject,
+                                                targetType);
+            }
+        }
+
+        execute(method, targetObject, targetArguments, principal);
+    }
+    
+    
+    /**
+     * Perform work as a particular </code>Subject</code>. Here the work
+     * will be granted to a <code>null</code> subject. 
+     *
+     * @param methodName the method to apply the security restriction
+     * @param targetObject the <code>Servlet</code> on which the method will
+     * be called.
+     * @param targetArguments <code>Object</code> array contains the 
+     * runtime parameters instance.
+     * @param principal the <code>Principal</code> to which the security 
+     * privilege applies
+     */    
+    private static void execute(final Method method,
+                                final Object targetObject, 
+                                final Object[] targetArguments,
+                                Principal principal) 
+        throws java.lang.Exception{
+       
+        try{   
+            Subject subject = null;
+            PrivilegedExceptionAction<Void> pea =
+                new PrivilegedExceptionAction<Void>(){
+                    @Override
+                    public Void run() throws Exception{
+                       method.invoke(targetObject, targetArguments);
+                       return null;
+                    }
+            };
+
+            // The first argument is always the request object
+            if (targetArguments != null 
+                    && targetArguments[0] instanceof HttpServletRequest){
+                HttpServletRequest request = 
+                    (HttpServletRequest)targetArguments[0];
+
+                boolean hasSubject = false;
+                HttpSession session = request.getSession(false);
+                if (session != null){
+                    subject = 
+                        (Subject)session.getAttribute(Globals.SUBJECT_ATTR);
+                    hasSubject = (subject != null);
+                }
+
+                if (subject == null){
+                    subject = new Subject();
+                    
+                    if (principal != null){
+                        subject.getPrincipals().add(principal);
+                    }
+                }
+
+                if (session != null && !hasSubject) {
+                    session.setAttribute(Globals.SUBJECT_ATTR, subject);
+                }
+            }
+
+            Subject.doAsPrivileged(subject, pea, null);       
+        } catch( PrivilegedActionException pe) {
+            Throwable e;
+            if (pe.getException() instanceof InvocationTargetException) {
+                e = ((InvocationTargetException)pe.getException())
+                                .getTargetException();
+            } else {
+                e = pe;
+            }
+            
+            if (log.isDebugEnabled()){
+                log.debug(sm.getString("SecurityUtil.doAsPrivilege"), e); 
+            }
+            
+            if (e instanceof UnavailableException)
+                throw (UnavailableException) e;
+            else if (e instanceof ServletException)
+                throw (ServletException) e;
+            else if (e instanceof IOException)
+                throw (IOException) e;
+            else if (e instanceof RuntimeException)
+                throw (RuntimeException) e;
+            else
+                throw new ServletException(e.getMessage(), e);
+        }  
+    }
+    
+    
+    /**
+     * Find a method stored within the cache.
+     * @param methodsCache the cache used to store method instance
+     * @param methodName the method to apply the security restriction
+     * @return the method instance, null if not yet created.
+     */
+    private static Method findMethod(Method[] methodsCache,
+                                     String methodName){
+        if (methodName.equalsIgnoreCase(INIT_METHOD) 
+                && methodsCache[INIT] != null){
+            return methodsCache[INIT];
+        } else if (methodName.equalsIgnoreCase(DESTROY_METHOD) 
+                && methodsCache[DESTROY] != null){
+            return methodsCache[DESTROY];            
+        } else if (methodName.equalsIgnoreCase(SERVICE_METHOD) 
+                && methodsCache[SERVICE] != null){
+            return methodsCache[SERVICE];
+        } else if (methodName.equalsIgnoreCase(DOFILTER_METHOD) 
+                && methodsCache[DOFILTER] != null){
+            return methodsCache[DOFILTER];          
+        } else if (methodName.equalsIgnoreCase(EVENT_METHOD) 
+                && methodsCache[EVENT] != null){
+            return methodsCache[EVENT];          
+        } else if (methodName.equalsIgnoreCase(DOFILTEREVENT_METHOD) 
+                && methodsCache[DOFILTEREVENT] != null){
+            return methodsCache[DOFILTEREVENT];          
+        } 
+        return null;
+    }
+    
+    
+    /**
+     * Create the method and cache it for further re-use.
+     * @param methodsCache the cache used to store method instance
+     * @param methodName the method to apply the security restriction
+     * @param targetObject the <code>Servlet</code> on which the method will
+     * be called.
+     * @param targetType <code>Class</code> array used to instantiate a 
+     * <code>Method</code> object.
+     * @return the method instance.
+     */
+    private static Method createMethodAndCacheIt(Method[] methodsCache,
+                                                 String methodName,
+                                                 Object targetObject,
+                                                 Class<?>[] targetType) 
+            throws Exception{
+        
+        if ( methodsCache == null){
+            methodsCache = new Method[4];
+        }               
+                
+        Method method = 
+            targetObject.getClass().getMethod(methodName, targetType); 
+
+        if (methodName.equalsIgnoreCase(INIT_METHOD)){
+            methodsCache[INIT] = method;
+        } else if (methodName.equalsIgnoreCase(DESTROY_METHOD)){
+            methodsCache[DESTROY] = method;
+        } else if (methodName.equalsIgnoreCase(SERVICE_METHOD)){
+            methodsCache[SERVICE] = method;
+        } else if (methodName.equalsIgnoreCase(DOFILTER_METHOD)){
+            methodsCache[DOFILTER] = method;
+        } else if (methodName.equalsIgnoreCase(EVENT_METHOD)){
+            methodsCache[EVENT] = method;
+        } else if (methodName.equalsIgnoreCase(DOFILTEREVENT_METHOD)){
+            methodsCache[DOFILTEREVENT] = method;
+        } 
+         
+        objectCache.put(targetObject, methodsCache );
+                                           
+        return method;
+    }
+
+    
+    /**
+     * Remove the object from the cache.
+     *
+     * @param cachedObject The object to remove
+     */
+    public static void remove(Object cachedObject){
+        objectCache.remove(cachedObject);
+    }
+    
+    
+    /**
+     * Return the <code>SecurityManager</code> only if Security is enabled AND
+     * package protection mechanism is enabled.
+     */
+    public static boolean isPackageProtectionEnabled(){
+        if (packageDefinitionEnabled && Globals.IS_SECURITY_ENABLED){
+            return true;
+        }
+        return false;
+    }
+    
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/CGIServlet.java b/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/CGIServlet.java
new file mode 100644
index 0000000..ee88b42
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/CGIServlet.java
@@ -0,0 +1,1916 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.servlets;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Locale;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.apache.catalina.util.IOTools;
+
+
+/**
+ *  CGI-invoking servlet for web applications, used to execute scripts which
+ *  comply to the Common Gateway Interface (CGI) specification and are named
+ *  in the path-info used to invoke this servlet.
+ *
+ * <p>
+ * <i>Note: This code compiles and even works for simple CGI cases.
+ *          Exhaustive testing has not been done.  Please consider it beta
+ *          quality.  Feedback is appreciated to the author (see below).</i>
+ * </p>
+ * <p>
+ *
+ * <b>Example</b>:<br>
+ * If an instance of this servlet was mapped (using
+ *       <code>&lt;web-app&gt;/WEB-INF/web.xml</code>) to:
+ * </p>
+ * <p>
+ * <code>
+ * &lt;web-app&gt;/cgi-bin/*
+ * </code>
+ * </p>
+ * <p>
+ * then the following request:
+ * </p>
+ * <p>
+ * <code>
+ * http://localhost:8080/&lt;web-app&gt;/cgi-bin/dir1/script/pathinfo1
+ * </code>
+ * </p>
+ * <p>
+ * would result in the execution of the script
+ * </p>
+ * <p>
+ * <code>
+ * &lt;web-app-root&gt;/WEB-INF/cgi/dir1/script
+ * </code>
+ * </p>
+ * <p>
+ * with the script's <code>PATH_INFO</code> set to <code>/pathinfo1</code>.
+ * </p>
+ * <p>
+ * Recommendation:  House all your CGI scripts under
+ * <code>&lt;webapp&gt;/WEB-INF/cgi</code>.  This will ensure that you do not
+ * accidentally expose your cgi scripts' code to the outside world and that
+ * your cgis will be cleanly ensconced underneath the WEB-INF (i.e.,
+ * non-content) area.
+ * </p>
+ * <p>
+ * The default CGI location is mentioned above.  You have the flexibility to
+ * put CGIs wherever you want, however:
+ * </p>
+ * <p>
+ *   The CGI search path will start at
+ *   webAppRootDir + File.separator + cgiPathPrefix
+ *   (or webAppRootDir alone if cgiPathPrefix is
+ *   null).
+ * </p>
+ * <p>
+ *   cgiPathPrefix is defined by setting
+ *   this servlet's cgiPathPrefix init parameter
+ * </p>
+ *
+ * <p>
+ *
+ * <B>CGI Specification</B>:<br> derived from
+ * <a href="http://cgi-spec.golux.com">http://cgi-spec.golux.com</a>.
+ * A work-in-progress & expired Internet Draft.  Note no actual RFC describing
+ * the CGI specification exists.  Where the behavior of this servlet differs
+ * from the specification cited above, it is either documented here, a bug,
+ * or an instance where the specification cited differs from Best
+ * Community Practice (BCP).
+ * Such instances should be well-documented here.  Please email the
+ * <a href="mailto:dev@tomcat.apache.org">Tomcat group [dev@tomcat.apache.org]</a>
+ * with amendments.
+ *
+ * </p>
+ * <p>
+ *
+ * <b>Canonical metavariables</b>:<br>
+ * The CGI specification defines the following canonical metavariables:
+ * <br>
+ * [excerpt from CGI specification]
+ * <PRE>
+ *  AUTH_TYPE
+ *  CONTENT_LENGTH
+ *  CONTENT_TYPE
+ *  GATEWAY_INTERFACE
+ *  PATH_INFO
+ *  PATH_TRANSLATED
+ *  QUERY_STRING
+ *  REMOTE_ADDR
+ *  REMOTE_HOST
+ *  REMOTE_IDENT
+ *  REMOTE_USER
+ *  REQUEST_METHOD
+ *  SCRIPT_NAME
+ *  SERVER_NAME
+ *  SERVER_PORT
+ *  SERVER_PROTOCOL
+ *  SERVER_SOFTWARE
+ * </PRE>
+ * <p>
+ * Metavariables with names beginning with the protocol name (<EM>e.g.</EM>,
+ * "HTTP_ACCEPT") are also canonical in their description of request header
+ * fields.  The number and meaning of these fields may change independently
+ * of this specification.  (See also section 6.1.5 [of the CGI specification].)
+ * </p>
+ * [end excerpt]
+ *
+ * </p>
+ * <h2> Implementation notes</h2>
+ * <p>
+ *
+ * <b>standard input handling</b>: If your script accepts standard input,
+ * then the client must start sending input within a certain timeout period,
+ * otherwise the servlet will assume no input is coming and carry on running
+ * the script.  The script's the standard input will be closed and handling of
+ * any further input from the client is undefined.  Most likely it will be
+ * ignored.  If this behavior becomes undesirable, then this servlet needs
+ * to be enhanced to handle threading of the spawned process' stdin, stdout,
+ * and stderr (which should not be too hard).
+ * <br>
+ * If you find your cgi scripts are timing out receiving input, you can set
+ * the init parameter <code></code> of your webapps' cgi-handling servlet
+ * to be
+ * </p>
+ * <p>
+ *
+ * <b>Metavariable Values</b>: According to the CGI specification,
+ * implementations may choose to represent both null or missing values in an
+ * implementation-specific manner, but must define that manner.  This
+ * implementation chooses to always define all required metavariables, but
+ * set the value to "" for all metavariables whose value is either null or
+ * undefined.  PATH_TRANSLATED is the sole exception to this rule, as per the
+ * CGI Specification.
+ *
+ * </p>
+ * <p>
+ *
+ * <b>NPH --  Non-parsed-header implementation</b>:  This implementation does
+ * not support the CGI NPH concept, whereby server ensures that the data
+ * supplied to the script are precisely as supplied by the client and
+ * unaltered by the server.
+ * </p>
+ * <p>
+ * The function of a servlet container (including Tomcat) is specifically
+ * designed to parse and possible alter CGI-specific variables, and as
+ * such makes NPH functionality difficult to support.
+ * </p>
+ * <p>
+ * The CGI specification states that compliant servers MAY support NPH output.
+ * It does not state servers MUST support NPH output to be unconditionally
+ * compliant.  Thus, this implementation maintains unconditional compliance
+ * with the specification though NPH support is not present.
+ * </p>
+ * <p>
+ *
+ * The CGI specification is located at
+ * <a href="http://cgi-spec.golux.com">http://cgi-spec.golux.com</a>.
+ *
+ * </p>
+ * <p>
+ * <h3>TODO:</h3>
+ * <ul>
+ * <li> Support for setting headers (for example, Location headers don't work)
+ * <li> Support for collapsing multiple header lines (per RFC 2616)
+ * <li> Ensure handling of POST method does not interfere with 2.3 Filters
+ * <li> Refactor some debug code out of core
+ * <li> Ensure header handling preserves encoding
+ * <li> Possibly rewrite CGIRunner.run()?
+ * <li> Possibly refactor CGIRunner and CGIEnvironment as non-inner classes?
+ * <li> Document handling of cgi stdin when there is no stdin
+ * <li> Revisit IOException handling in CGIRunner.run()
+ * <li> Better documentation
+ * <li> Confirm use of ServletInputStream.available() in CGIRunner.run() is
+ *      not needed
+ * <li> [add more to this TODO list]
+ * </ul>
+ * </p>
+ *
+ * @author Martin T Dengler [root@martindengler.com]
+ * @author Amy Roh
+ * @version $Id: CGIServlet.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @since Tomcat 4.0
+ *
+ */
+
+
+public final class CGIServlet extends HttpServlet {
+
+    /* some vars below copied from Craig R. McClanahan's InvokerServlet */
+
+    private static final long serialVersionUID = 1L;
+
+    /** the debugging detail level for this servlet. */
+    private int debug = 0;
+
+    /**
+     *  The CGI search path will start at
+     *    webAppRootDir + File.separator + cgiPathPrefix
+     *    (or webAppRootDir alone if cgiPathPrefix is
+     *    null)
+     */
+    private String cgiPathPrefix = null;
+
+    /** the executable to use with the script */
+    private String cgiExecutable = "perl";
+
+    /** additional arguments for the executable */
+    private List<String> cgiExecutableArgs = null;
+
+    /** the encoding to use for parameters */
+    private String parameterEncoding =
+        System.getProperty("file.encoding", "UTF-8");
+
+    /**
+     * The time (in milliseconds) to wait for the reading of stderr to complete
+     * before terminating the CGI process.
+     */
+    private long stderrTimeout = 2000;
+
+    /** object used to ensure multiple threads don't try to expand same file */
+    static Object expandFileLock = new Object();
+
+    /** the shell environment variables to be passed to the CGI script */
+    static Hashtable<String,String> shellEnv = new Hashtable<String,String>();
+
+    /**
+     * Sets instance variables.
+     * <P>
+     * Modified from Craig R. McClanahan's InvokerServlet
+     * </P>
+     *
+     * @param config                    a <code>ServletConfig</code> object
+     *                                  containing the servlet's
+     *                                  configuration and initialization
+     *                                  parameters
+     *
+     * @exception ServletException      if an exception has occurred that
+     *                                  interferes with the servlet's normal
+     *                                  operation
+     */
+    @Override
+    public void init(ServletConfig config) throws ServletException {
+
+        super.init(config);
+
+        // Set our properties from the initialization parameters
+        if (getServletConfig().getInitParameter("debug") != null)
+            debug = Integer.parseInt(getServletConfig().getInitParameter("debug"));
+        cgiPathPrefix = getServletConfig().getInitParameter("cgiPathPrefix");
+        boolean passShellEnvironment = 
+            Boolean.valueOf(getServletConfig().getInitParameter("passShellEnvironment")).booleanValue();
+
+        if (passShellEnvironment) {
+            shellEnv.putAll(System.getenv());
+        }
+
+        if (getServletConfig().getInitParameter("executable") != null) {
+            cgiExecutable = getServletConfig().getInitParameter("executable");
+        }
+
+        if (getServletConfig().getInitParameter("executable-arg-1") != null) {
+            List<String> args = new ArrayList<String>();
+            for (int i = 1;; i++) {
+                String arg = getServletConfig().getInitParameter(
+                        "executable-arg-" + i);
+                if (arg == null) {
+                    break;
+                }
+                args.add(arg);
+            }
+            cgiExecutableArgs = args;
+        }
+
+        if (getServletConfig().getInitParameter("parameterEncoding") != null) {
+            parameterEncoding = getServletConfig().getInitParameter("parameterEncoding");
+        }
+
+        if (getServletConfig().getInitParameter("stderrTimeout") != null) {
+            stderrTimeout = Long.parseLong(getServletConfig().getInitParameter(
+                    "stderrTimeout"));
+        }
+
+    }
+
+
+    /**
+     * Prints out important Servlet API and container information
+     *
+     * <p>
+     * Copied from SnoopAllServlet by Craig R. McClanahan
+     * </p>
+     *
+     * @param  out    ServletOutputStream as target of the information
+     * @param  req    HttpServletRequest object used as source of information
+     * @param  res    HttpServletResponse object currently not used but could
+     *                provide future information
+     *
+     * @exception  IOException  if a write operation exception occurs
+     *
+     */
+    protected void printServletEnvironment(ServletOutputStream out,
+        HttpServletRequest req,
+        @SuppressWarnings("unused") HttpServletResponse res)
+    throws IOException {
+
+        // Document the properties from ServletRequest
+        out.println("<h1>ServletRequest Properties</h1>");
+        out.println("<ul>");
+        Enumeration<String> attrs = req.getAttributeNames();
+        while (attrs.hasMoreElements()) {
+            String attr = attrs.nextElement();
+            out.println("<li><b>attribute</b> " + attr + " = " +
+                           req.getAttribute(attr));
+        }
+        out.println("<li><b>characterEncoding</b> = " +
+                       req.getCharacterEncoding());
+        out.println("<li><b>contentLength</b> = " +
+                       req.getContentLength());
+        out.println("<li><b>contentType</b> = " +
+                       req.getContentType());
+        Enumeration<Locale> locales = req.getLocales();
+        while (locales.hasMoreElements()) {
+            Locale locale = locales.nextElement();
+            out.println("<li><b>locale</b> = " + locale);
+        }
+        Enumeration<String> params = req.getParameterNames();
+        while (params.hasMoreElements()) {
+            String param = params.nextElement();
+            String values[] = req.getParameterValues(param);
+            for (int i = 0; i < values.length; i++)
+                out.println("<li><b>parameter</b> " + param + " = " +
+                               values[i]);
+        }
+        out.println("<li><b>protocol</b> = " + req.getProtocol());
+        out.println("<li><b>remoteAddr</b> = " + req.getRemoteAddr());
+        out.println("<li><b>remoteHost</b> = " + req.getRemoteHost());
+        out.println("<li><b>scheme</b> = " + req.getScheme());
+        out.println("<li><b>secure</b> = " + req.isSecure());
+        out.println("<li><b>serverName</b> = " + req.getServerName());
+        out.println("<li><b>serverPort</b> = " + req.getServerPort());
+        out.println("</ul>");
+        out.println("<hr>");
+
+        // Document the properties from HttpServletRequest
+        out.println("<h1>HttpServletRequest Properties</h1>");
+        out.println("<ul>");
+        out.println("<li><b>authType</b> = " + req.getAuthType());
+        out.println("<li><b>contextPath</b> = " +
+                       req.getContextPath());
+        Cookie cookies[] = req.getCookies();
+        if (cookies!=null) {
+            for (int i = 0; i < cookies.length; i++)
+                out.println("<li><b>cookie</b> " + cookies[i].getName() +" = " +cookies[i].getValue());
+        }
+        Enumeration<String> headers = req.getHeaderNames();
+        while (headers.hasMoreElements()) {
+            String header = headers.nextElement();
+            out.println("<li><b>header</b> " + header + " = " +
+                           req.getHeader(header));
+        }
+        out.println("<li><b>method</b> = " + req.getMethod());
+        out.println("<li><a name=\"pathInfo\"><b>pathInfo</b></a> = "
+                    + req.getPathInfo());
+        out.println("<li><b>pathTranslated</b> = " +
+                       req.getPathTranslated());
+        out.println("<li><b>queryString</b> = " +
+                       req.getQueryString());
+        out.println("<li><b>remoteUser</b> = " +
+                       req.getRemoteUser());
+        out.println("<li><b>requestedSessionId</b> = " +
+                       req.getRequestedSessionId());
+        out.println("<li><b>requestedSessionIdFromCookie</b> = " +
+                       req.isRequestedSessionIdFromCookie());
+        out.println("<li><b>requestedSessionIdFromURL</b> = " +
+                       req.isRequestedSessionIdFromURL());
+        out.println("<li><b>requestedSessionIdValid</b> = " +
+                       req.isRequestedSessionIdValid());
+        out.println("<li><b>requestURI</b> = " +
+                       req.getRequestURI());
+        out.println("<li><b>servletPath</b> = " +
+                       req.getServletPath());
+        out.println("<li><b>userPrincipal</b> = " +
+                       req.getUserPrincipal());
+        out.println("</ul>");
+        out.println("<hr>");
+
+        // Document the servlet request attributes
+        out.println("<h1>ServletRequest Attributes</h1>");
+        out.println("<ul>");
+        attrs = req.getAttributeNames();
+        while (attrs.hasMoreElements()) {
+            String attr = attrs.nextElement();
+            out.println("<li><b>" + attr + "</b> = " +
+                           req.getAttribute(attr));
+        }
+        out.println("</ul>");
+        out.println("<hr>");
+
+        // Process the current session (if there is one)
+        HttpSession session = req.getSession(false);
+        if (session != null) {
+
+            // Document the session properties
+            out.println("<h1>HttpSession Properties</h1>");
+            out.println("<ul>");
+            out.println("<li><b>id</b> = " +
+                           session.getId());
+            out.println("<li><b>creationTime</b> = " +
+                           new Date(session.getCreationTime()));
+            out.println("<li><b>lastAccessedTime</b> = " +
+                           new Date(session.getLastAccessedTime()));
+            out.println("<li><b>maxInactiveInterval</b> = " +
+                           session.getMaxInactiveInterval());
+            out.println("</ul>");
+            out.println("<hr>");
+
+            // Document the session attributes
+            out.println("<h1>HttpSession Attributes</h1>");
+            out.println("<ul>");
+            attrs = session.getAttributeNames();
+            while (attrs.hasMoreElements()) {
+                String attr = attrs.nextElement();
+                out.println("<li><b>" + attr + "</b> = " +
+                               session.getAttribute(attr));
+            }
+            out.println("</ul>");
+            out.println("<hr>");
+
+        }
+
+        // Document the servlet configuration properties
+        out.println("<h1>ServletConfig Properties</h1>");
+        out.println("<ul>");
+        out.println("<li><b>servletName</b> = " +
+                       getServletConfig().getServletName());
+        out.println("</ul>");
+        out.println("<hr>");
+
+        // Document the servlet configuration initialization parameters
+        out.println("<h1>ServletConfig Initialization Parameters</h1>");
+        out.println("<ul>");
+        params = getServletConfig().getInitParameterNames();
+        while (params.hasMoreElements()) {
+            String param = params.nextElement();
+            String value = getServletConfig().getInitParameter(param);
+            out.println("<li><b>" + param + "</b> = " + value);
+        }
+        out.println("</ul>");
+        out.println("<hr>");
+
+        // Document the servlet context properties
+        out.println("<h1>ServletContext Properties</h1>");
+        out.println("<ul>");
+        out.println("<li><b>majorVersion</b> = " +
+                       getServletContext().getMajorVersion());
+        out.println("<li><b>minorVersion</b> = " +
+                       getServletContext().getMinorVersion());
+        out.println("<li><b>realPath('/')</b> = " +
+                       getServletContext().getRealPath("/"));
+        out.println("<li><b>serverInfo</b> = " +
+                       getServletContext().getServerInfo());
+        out.println("</ul>");
+        out.println("<hr>");
+
+        // Document the servlet context initialization parameters
+        out.println("<h1>ServletContext Initialization Parameters</h1>");
+        out.println("<ul>");
+        params = getServletContext().getInitParameterNames();
+        while (params.hasMoreElements()) {
+            String param = params.nextElement();
+            String value = getServletContext().getInitParameter(param);
+            out.println("<li><b>" + param + "</b> = " + value);
+        }
+        out.println("</ul>");
+        out.println("<hr>");
+
+        // Document the servlet context attributes
+        out.println("<h1>ServletContext Attributes</h1>");
+        out.println("<ul>");
+        attrs = getServletContext().getAttributeNames();
+        while (attrs.hasMoreElements()) {
+            String attr = attrs.nextElement();
+            out.println("<li><b>" + attr + "</b> = " +
+                           getServletContext().getAttribute(attr));
+        }
+        out.println("</ul>");
+        out.println("<hr>");
+
+
+    }
+
+
+    /**
+     * Provides CGI Gateway service -- delegates to <code>doGet</code>
+     *
+     * @param  req   HttpServletRequest passed in by servlet container
+     * @param  res   HttpServletResponse passed in by servlet container
+     *
+     * @exception  ServletException  if a servlet-specific exception occurs
+     * @exception  IOException  if a read/write exception occurs
+     *
+     * @see javax.servlet.http.HttpServlet
+     *
+     */
+    @Override
+    protected void doPost(HttpServletRequest req, HttpServletResponse res)
+        throws IOException, ServletException {
+        doGet(req, res);
+    }
+
+
+    /**
+     * Provides CGI Gateway service
+     *
+     * @param  req   HttpServletRequest passed in by servlet container
+     * @param  res   HttpServletResponse passed in by servlet container
+     *
+     * @exception  ServletException  if a servlet-specific exception occurs
+     * @exception  IOException  if a read/write exception occurs
+     *
+     * @see javax.servlet.http.HttpServlet
+     *
+     */
+    @Override
+    protected void doGet(HttpServletRequest req, HttpServletResponse res)
+        throws ServletException, IOException {
+
+        CGIEnvironment cgiEnv = new CGIEnvironment(req, getServletContext());
+
+        if (cgiEnv.isValid()) {
+            CGIRunner cgi = new CGIRunner(cgiEnv.getCommand(),
+                                          cgiEnv.getEnvironment(),
+                                          cgiEnv.getWorkingDirectory(),
+                                          cgiEnv.getParameters());
+            //if POST, we need to cgi.setInput
+            //REMIND: how does this interact with Servlet API 2.3's Filters?!
+            if ("POST".equals(req.getMethod())) {
+                cgi.setInput(req.getInputStream());
+            }
+            cgi.setResponse(res);
+            cgi.run();
+        }
+
+        if (!cgiEnv.isValid()) {
+            res.setStatus(404);
+        }
+ 
+        if (debug >= 10) {
+
+            ServletOutputStream out = res.getOutputStream();
+            out.println("<HTML><HEAD><TITLE>$Name:  $</TITLE></HEAD>");
+            out.println("<BODY>$Header: /home/mknauer/projects/rap/git/org.eclipse.rap.3rdparty/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/CGIServlet.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $<p>");
+
+            if (cgiEnv.isValid()) {
+                out.println(cgiEnv.toString());
+            } else {
+                out.println("<H3>");
+                out.println("CGI script not found or not specified.");
+                out.println("</H3>");
+                out.println("<H4>");
+                out.println("Check the <b>HttpServletRequest ");
+                out.println("<a href=\"#pathInfo\">pathInfo</a></b> ");
+                out.println("property to see if it is what you meant ");
+                out.println("it to be.  You must specify an existant ");
+                out.println("and executable file as part of the ");
+                out.println("path-info.");
+                out.println("</H4>");
+                out.println("<H4>");
+                out.println("For a good discussion of how CGI scripts ");
+                out.println("work and what their environment variables ");
+                out.println("mean, please visit the <a ");
+                out.println("href=\"http://cgi-spec.golux.com\">CGI ");
+                out.println("Specification page</a>.");
+                out.println("</H4>");
+
+            }
+
+            printServletEnvironment(out, req, res);
+
+            out.println("</BODY></HTML>");
+
+        }
+
+
+    } //doGet
+
+
+    /**
+     * Encapsulates the CGI environment and rules to derive
+     * that environment from the servlet container and request information.
+     *
+     * <p>
+     * </p>
+     *
+     * @version  $Id: CGIServlet.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+     * @since    Tomcat 4.0
+     *
+     */
+    protected class CGIEnvironment {
+
+
+        /** context of the enclosing servlet */
+        private ServletContext context = null;
+
+        /** context path of enclosing servlet */
+        private String contextPath = null;
+
+        /** servlet URI of the enclosing servlet */
+        private String servletPath = null;
+
+        /** pathInfo for the current request */
+        private String pathInfo = null;
+
+        /** real file system directory of the enclosing servlet's web app */
+        private String webAppRootDir = null;
+
+        /** tempdir for context - used to expand scripts in unexpanded wars */
+        private File tmpDir = null;
+
+        /** derived cgi environment */
+        private Hashtable<String, String> env = null;
+
+        /** cgi command to be invoked */
+        private String command = null;
+
+        /** cgi command's desired working directory */
+        private File workingDirectory = null;
+
+        /** cgi command's command line parameters */
+        private ArrayList<String> cmdLineParameters = new ArrayList<String>();
+
+        /** whether or not this object is valid or not */
+        private boolean valid = false;
+
+
+        /**
+         * Creates a CGIEnvironment and derives the necessary environment,
+         * query parameters, working directory, cgi command, etc.
+         *
+         * @param  req       HttpServletRequest for information provided by
+         *                   the Servlet API
+         * @param  context   ServletContext for information provided by the
+         *                   Servlet API
+         *
+         */
+        protected CGIEnvironment(HttpServletRequest req,
+                                 ServletContext context) throws IOException {
+            setupFromContext(context);
+            setupFromRequest(req);
+
+            this.valid = setCGIEnvironment(req);
+
+            if (this.valid) {
+                workingDirectory = new File(command.substring(0,
+                      command.lastIndexOf(File.separator)));
+            }
+
+        }
+
+
+        /**
+         * Uses the ServletContext to set some CGI variables
+         *
+         * @param  context   ServletContext for information provided by the
+         *                   Servlet API
+         */
+        protected void setupFromContext(ServletContext context) {
+            this.context = context;
+            this.webAppRootDir = context.getRealPath("/");
+            this.tmpDir = (File) context.getAttribute(ServletContext.TEMPDIR);
+        }
+
+
+        /**
+         * Uses the HttpServletRequest to set most CGI variables
+         *
+         * @param  req   HttpServletRequest for information provided by
+         *               the Servlet API
+         * @throws UnsupportedEncodingException 
+         */
+        protected void setupFromRequest(HttpServletRequest req)
+                throws UnsupportedEncodingException {
+
+            boolean isIncluded = false;
+
+            // Look to see if this request is an include
+            if (req.getAttribute(
+                    RequestDispatcher.INCLUDE_REQUEST_URI) != null) {
+                isIncluded = true;
+            }
+            if (isIncluded) {
+                this.contextPath = (String) req.getAttribute(
+                        RequestDispatcher.INCLUDE_CONTEXT_PATH);
+                this.servletPath = (String) req.getAttribute(
+                        RequestDispatcher.INCLUDE_SERVLET_PATH);
+                this.pathInfo = (String) req.getAttribute(
+                        RequestDispatcher.INCLUDE_PATH_INFO);
+            } else {
+                this.contextPath = req.getContextPath();
+                this.servletPath = req.getServletPath();
+                this.pathInfo = req.getPathInfo();
+            }
+            // If getPathInfo() returns null, must be using extension mapping
+            // In this case, pathInfo should be same as servletPath
+            if (this.pathInfo == null) {
+                this.pathInfo = this.servletPath;
+            }
+
+            // If the request method is GET, POST or HEAD and the query string
+            // does not contain an unencoded "=" this is an indexed query.
+            // The parsed query string becomes the command line parameters
+            // for the cgi command.
+            if (req.getMethod().equals("GET")
+                || req.getMethod().equals("POST")
+                || req.getMethod().equals("HEAD")) {
+                String qs;
+                if (isIncluded) {
+                    qs = (String) req.getAttribute(
+                            RequestDispatcher.INCLUDE_QUERY_STRING);
+                } else {
+                    qs = req.getQueryString();
+                }
+                if (qs != null && qs.indexOf("=") == -1) {
+                    StringTokenizer qsTokens = new StringTokenizer(qs, "+");
+                    while ( qsTokens.hasMoreTokens() ) {
+                        cmdLineParameters.add(URLDecoder.decode(qsTokens.nextToken(),
+                                              parameterEncoding));
+                    }
+                }
+            }
+        }
+
+
+        /**
+         * Resolves core information about the cgi script.
+         *
+         * <p>
+         * Example URI:
+         * <PRE> /servlet/cgigateway/dir1/realCGIscript/pathinfo1 </PRE>
+         * <ul>
+         * <LI><b>path</b> = $CATALINA_HOME/mywebapp/dir1/realCGIscript
+         * <LI><b>scriptName</b> = /servlet/cgigateway/dir1/realCGIscript
+         * <LI><b>cgiName</b> = /dir1/realCGIscript
+         * <LI><b>name</b> = realCGIscript
+         * </ul>
+         * </p>
+         * <p>
+         * CGI search algorithm: search the real path below
+         *    &lt;my-webapp-root&gt; and find the first non-directory in
+         *    the getPathTranslated("/"), reading/searching from left-to-right.
+         *</p>
+         *<p>
+         *   The CGI search path will start at
+         *   webAppRootDir + File.separator + cgiPathPrefix
+         *   (or webAppRootDir alone if cgiPathPrefix is
+         *   null).
+         *</p>
+         *<p>
+         *   cgiPathPrefix is defined by setting
+         *   this servlet's cgiPathPrefix init parameter
+         *
+         *</p>
+         *
+         * @param pathInfo       String from HttpServletRequest.getPathInfo()
+         * @param webAppRootDir  String from context.getRealPath("/")
+         * @param contextPath    String as from
+         *                       HttpServletRequest.getContextPath()
+         * @param servletPath    String as from
+         *                       HttpServletRequest.getServletPath()
+         * @param cgiPathPrefix  subdirectory of webAppRootDir below which
+         *                       the web app's CGIs may be stored; can be null.
+         *                       The CGI search path will start at
+         *                       webAppRootDir + File.separator + cgiPathPrefix
+         *                       (or webAppRootDir alone if cgiPathPrefix is
+         *                       null).  cgiPathPrefix is defined by setting
+         *                       the servlet's cgiPathPrefix init parameter.
+         *
+         *
+         * @return
+         * <ul>
+         * <li>
+         * <code>path</code> -    full file-system path to valid cgi script,
+         *                        or null if no cgi was found
+         * <li>
+         * <code>scriptName</code> -
+         *                        CGI variable SCRIPT_NAME; the full URL path
+         *                        to valid cgi script or null if no cgi was
+         *                        found
+         * <li>
+         * <code>cgiName</code> - servlet pathInfo fragment corresponding to
+         *                        the cgi script itself, or null if not found
+         * <li>
+         * <code>name</code> -    simple name (no directories) of the
+         *                        cgi script, or null if no cgi was found
+         * </ul>
+         *
+         * @since Tomcat 4.0
+         */
+        protected String[] findCGI(String pathInfo, String webAppRootDir,
+                                   String contextPath, String servletPath,
+                                   String cgiPathPrefix) {
+            String path = null;
+            String name = null;
+            String scriptname = null;
+            String cginame = "";
+
+            if ((webAppRootDir != null)
+                && (webAppRootDir.lastIndexOf(File.separator) ==
+                    (webAppRootDir.length() - 1))) {
+                    //strip the trailing "/" from the webAppRootDir
+                    webAppRootDir =
+                    webAppRootDir.substring(0, (webAppRootDir.length() - 1));
+            }
+
+            if (cgiPathPrefix != null) {
+                webAppRootDir = webAppRootDir + File.separator
+                    + cgiPathPrefix;
+            }
+
+            if (debug >= 2) {
+                log("findCGI: path=" + pathInfo + ", " + webAppRootDir);
+            }
+
+            File currentLocation = new File(webAppRootDir);
+            StringTokenizer dirWalker =
+            new StringTokenizer(pathInfo, "/");
+            if (debug >= 3) {
+                log("findCGI: currentLoc=" + currentLocation);
+            }
+            while (!currentLocation.isFile() && dirWalker.hasMoreElements()) {
+                if (debug >= 3) {
+                    log("findCGI: currentLoc=" + currentLocation);
+                }
+                String nextElement = (String) dirWalker.nextElement();
+                currentLocation = new File(currentLocation, nextElement);
+                cginame = cginame + "/" + nextElement;
+            }
+            if (!currentLocation.isFile()) {
+                return new String[] { null, null, null, null };
+            }
+
+            if (debug >= 2) {
+                log("findCGI: FOUND cgi at " + currentLocation);
+            }
+            path = currentLocation.getAbsolutePath();
+            name = currentLocation.getName();
+
+            if (".".equals(contextPath)) {
+                scriptname = servletPath;
+            } else {
+                scriptname = contextPath + servletPath;
+            }
+            if (!servletPath.equals(cginame)) {
+                scriptname = scriptname + cginame;
+            }
+
+            if (debug >= 1) {
+                log("findCGI calc: name=" + name + ", path=" + path
+                    + ", scriptname=" + scriptname + ", cginame=" + cginame);
+            }
+            return new String[] { path, scriptname, cginame, name };
+        }
+
+        /**
+         * Constructs the CGI environment to be supplied to the invoked CGI
+         * script; relies heavily on Servlet API methods and findCGI
+         *
+         * @param    req request associated with the CGI
+         *           Invocation
+         *
+         * @return   true if environment was set OK, false if there
+         *           was a problem and no environment was set
+         */
+        protected boolean setCGIEnvironment(HttpServletRequest req) throws IOException {
+
+            /*
+             * This method is slightly ugly; c'est la vie.
+             * "You cannot stop [ugliness], you can only hope to contain [it]"
+             * (apologies to Marv Albert regarding MJ)
+             */
+
+            Hashtable<String,String> envp = new Hashtable<String,String>();
+
+            // Add the shell environment variables (if any)
+            envp.putAll(shellEnv);
+
+            // Add the CGI environment variables
+            String sPathInfoOrig = null;
+            String sPathInfoCGI = null;
+            String sPathTranslatedCGI = null;
+            String sCGIFullPath = null;
+            String sCGIScriptName = null;
+            String sCGIFullName = null;
+            String sCGIName = null;
+            String[] sCGINames;
+
+
+            sPathInfoOrig = this.pathInfo;
+            sPathInfoOrig = sPathInfoOrig == null ? "" : sPathInfoOrig;
+
+            if (webAppRootDir == null ) {
+                // The app has not been deployed in exploded form
+                webAppRootDir = tmpDir.toString();
+                expandCGIScript();
+            } 
+            
+            sCGINames = findCGI(sPathInfoOrig,
+                                webAppRootDir,
+                                contextPath,
+                                servletPath,
+                                cgiPathPrefix);
+
+            sCGIFullPath = sCGINames[0];
+            sCGIScriptName = sCGINames[1];
+            sCGIFullName = sCGINames[2];
+            sCGIName = sCGINames[3];
+
+            if (sCGIFullPath == null
+                || sCGIScriptName == null
+                || sCGIFullName == null
+                || sCGIName == null) {
+                return false;
+            }
+
+            envp.put("SERVER_SOFTWARE", "TOMCAT");
+
+            envp.put("SERVER_NAME", nullsToBlanks(req.getServerName()));
+
+            envp.put("GATEWAY_INTERFACE", "CGI/1.1");
+
+            envp.put("SERVER_PROTOCOL", nullsToBlanks(req.getProtocol()));
+
+            int port = req.getServerPort();
+            Integer iPort =
+                (port == 0 ? Integer.valueOf(-1) : Integer.valueOf(port));
+            envp.put("SERVER_PORT", iPort.toString());
+
+            envp.put("REQUEST_METHOD", nullsToBlanks(req.getMethod()));
+
+            envp.put("REQUEST_URI", nullsToBlanks(req.getRequestURI()));
+
+
+            /*-
+             * PATH_INFO should be determined by using sCGIFullName:
+             * 1) Let sCGIFullName not end in a "/" (see method findCGI)
+             * 2) Let sCGIFullName equal the pathInfo fragment which
+             *    corresponds to the actual cgi script.
+             * 3) Thus, PATH_INFO = request.getPathInfo().substring(
+             *                      sCGIFullName.length())
+             *
+             * (see method findCGI, where the real work is done)
+             *
+             */
+            if (pathInfo == null
+                || (pathInfo.substring(sCGIFullName.length()).length() <= 0)) {
+                sPathInfoCGI = "";
+            } else {
+                sPathInfoCGI = pathInfo.substring(sCGIFullName.length());
+            }
+            envp.put("PATH_INFO", sPathInfoCGI);
+
+
+            /*-
+             * PATH_TRANSLATED must be determined after PATH_INFO (and the
+             * implied real cgi-script) has been taken into account.
+             *
+             * The following example demonstrates:
+             *
+             * servlet info   = /servlet/cgigw/dir1/dir2/cgi1/trans1/trans2
+             * cgifullpath    = /servlet/cgigw/dir1/dir2/cgi1
+             * path_info      = /trans1/trans2
+             * webAppRootDir  = servletContext.getRealPath("/")
+             *
+             * path_translated = servletContext.getRealPath("/trans1/trans2")
+             *
+             * That is, PATH_TRANSLATED = webAppRootDir + sPathInfoCGI
+             * (unless sPathInfoCGI is null or blank, then the CGI
+             * specification dictates that the PATH_TRANSLATED metavariable
+             * SHOULD NOT be defined.
+             *
+             */
+            if (sPathInfoCGI != null && !("".equals(sPathInfoCGI))) {
+                sPathTranslatedCGI = context.getRealPath(sPathInfoCGI);
+            }
+            if (sPathTranslatedCGI == null || "".equals(sPathTranslatedCGI)) {
+                //NOOP
+            } else {
+                envp.put("PATH_TRANSLATED", nullsToBlanks(sPathTranslatedCGI));
+            }
+
+
+            envp.put("SCRIPT_NAME", nullsToBlanks(sCGIScriptName));
+
+            envp.put("QUERY_STRING", nullsToBlanks(req.getQueryString()));
+
+            envp.put("REMOTE_HOST", nullsToBlanks(req.getRemoteHost()));
+
+            envp.put("REMOTE_ADDR", nullsToBlanks(req.getRemoteAddr()));
+
+            envp.put("AUTH_TYPE", nullsToBlanks(req.getAuthType()));
+
+            envp.put("REMOTE_USER", nullsToBlanks(req.getRemoteUser()));
+
+            envp.put("REMOTE_IDENT", ""); //not necessary for full compliance
+
+            envp.put("CONTENT_TYPE", nullsToBlanks(req.getContentType()));
+
+
+            /* Note CGI spec says CONTENT_LENGTH must be NULL ("") or undefined
+             * if there is no content, so we cannot put 0 or -1 in as per the
+             * Servlet API spec.
+             */
+            int contentLength = req.getContentLength();
+            String sContentLength = (contentLength <= 0 ? "" :
+                (Integer.valueOf(contentLength)).toString());
+            envp.put("CONTENT_LENGTH", sContentLength);
+
+
+            Enumeration<String> headers = req.getHeaderNames();
+            String header = null;
+            while (headers.hasMoreElements()) {
+                header = null;
+                header = headers.nextElement().toUpperCase(Locale.ENGLISH);
+                //REMIND: rewrite multiple headers as if received as single
+                //REMIND: change character set
+                //REMIND: I forgot what the previous REMIND means
+                if ("AUTHORIZATION".equalsIgnoreCase(header) ||
+                    "PROXY_AUTHORIZATION".equalsIgnoreCase(header)) {
+                    //NOOP per CGI specification section 11.2
+                } else {
+                    envp.put("HTTP_" + header.replace('-', '_'),
+                             req.getHeader(header));
+                }
+            }
+
+            File fCGIFullPath = new File(sCGIFullPath);
+            command = fCGIFullPath.getCanonicalPath();
+
+            envp.put("X_TOMCAT_SCRIPT_PATH", command);  //for kicks
+
+            envp.put("SCRIPT_FILENAME", command);  //for PHP
+
+            this.env = envp;
+
+            return true;
+
+        }
+
+        /**
+         * Extracts requested resource from web app archive to context work 
+         * directory to enable CGI script to be executed.
+         */
+        protected void expandCGIScript() {
+            StringBuilder srcPath = new StringBuilder();
+            StringBuilder destPath = new StringBuilder();
+            InputStream is = null;
+
+            // paths depend on mapping
+            if (cgiPathPrefix == null ) {
+                srcPath.append(pathInfo);
+                is = context.getResourceAsStream(srcPath.toString());
+                destPath.append(tmpDir);
+                destPath.append(pathInfo);
+            } else {
+                // essentially same search algorithm as findCGI()
+                srcPath.append(cgiPathPrefix);
+                StringTokenizer pathWalker =
+                        new StringTokenizer (pathInfo, "/");
+                // start with first element
+                while (pathWalker.hasMoreElements() && (is == null)) {
+                    srcPath.append("/");
+                    srcPath.append(pathWalker.nextElement());
+                    is = context.getResourceAsStream(srcPath.toString());
+                }
+                destPath.append(tmpDir);
+                destPath.append("/");
+                destPath.append(srcPath);
+            }
+
+            if (is == null) {
+                // didn't find anything, give up now
+                if (debug >= 2) {
+                    log("expandCGIScript: source '" + srcPath + "' not found");
+                }
+                 return;
+            }
+
+            File f = new File(destPath.toString());
+            if (f.exists()) {
+                // Don't need to expand if it already exists
+                return;
+            } 
+
+            // create directories
+            String dirPath = destPath.toString().substring(
+                    0,destPath.toString().lastIndexOf("/"));
+            File dir = new File(dirPath);
+            if (!dir.mkdirs() && debug >= 2) {
+                log("expandCGIScript: failed to create directories for '" +
+                        dir.getAbsolutePath() + "'");
+                return;
+            }
+
+            try {
+                synchronized (expandFileLock) {
+                    // make sure file doesn't exist
+                    if (f.exists()) {
+                        return;
+                    }
+
+                    // create file
+                    if (!f.createNewFile()) {
+                        return;
+                    }
+                    FileOutputStream fos = new FileOutputStream(f);
+
+                    // copy data
+                    IOTools.flow(is, fos);
+                    is.close();
+                    fos.close();
+                    if (debug >= 2) {
+                        log("expandCGIScript: expanded '" + srcPath + "' to '" + destPath + "'");
+                    }
+                }
+            } catch (IOException ioe) {
+                // delete in case file is corrupted 
+                if (f.exists()) {
+                    if (!f.delete() && debug >= 2) {
+                        log("expandCGIScript: failed to delete '" +
+                                f.getAbsolutePath() + "'");
+                    }
+                }
+            }
+        }
+
+
+        /**
+         * Print important CGI environment information in a easy-to-read HTML
+         * table
+         *
+         * @return  HTML string containing CGI environment info
+         *
+         */
+        @Override
+        public String toString() {
+
+            StringBuilder sb = new StringBuilder();
+
+            sb.append("<TABLE border=2>");
+
+            sb.append("<tr><th colspan=2 bgcolor=grey>");
+            sb.append("CGIEnvironment Info</th></tr>");
+
+            sb.append("<tr><td>Debug Level</td><td>");
+            sb.append(debug);
+            sb.append("</td></tr>");
+
+            sb.append("<tr><td>Validity:</td><td>");
+            sb.append(isValid());
+            sb.append("</td></tr>");
+
+            if (isValid()) {
+                Enumeration<String> envk = env.keys();
+                while (envk.hasMoreElements()) {
+                    String s = envk.nextElement();
+                    sb.append("<tr><td>");
+                    sb.append(s);
+                    sb.append("</td><td>");
+                    sb.append(blanksToString(env.get(s),
+                                             "[will be set to blank]"));
+                    sb.append("</td></tr>");
+                }
+            }
+
+            sb.append("<tr><td colspan=2><HR></td></tr>");
+
+            sb.append("<tr><td>Derived Command</td><td>");
+            sb.append(nullsToBlanks(command));
+            sb.append("</td></tr>");
+
+            sb.append("<tr><td>Working Directory</td><td>");
+            if (workingDirectory != null) {
+                sb.append(workingDirectory.toString());
+            }
+            sb.append("</td></tr>");
+
+            sb.append("<tr><td>Command Line Params</td><td>");
+            for (int i=0; i < cmdLineParameters.size(); i++) {
+                String param = cmdLineParameters.get(i);
+                sb.append("<p>");
+                sb.append(param);
+                sb.append("</p>");
+            }
+            sb.append("</td></tr>");
+
+            sb.append("</TABLE><p>end.");
+
+            return sb.toString();
+        }
+
+
+        /**
+         * Gets derived command string
+         *
+         * @return  command string
+         *
+         */
+        protected String getCommand() {
+            return command;
+        }
+
+
+        /**
+         * Gets derived CGI working directory
+         *
+         * @return  working directory
+         *
+         */
+        protected File getWorkingDirectory() {
+            return workingDirectory;
+        }
+
+
+        /**
+         * Gets derived CGI environment
+         *
+         * @return   CGI environment
+         *
+         */
+        protected Hashtable<String,String> getEnvironment() {
+            return env;
+        }
+
+
+        /**
+         * Gets derived CGI query parameters
+         *
+         * @return   CGI query parameters
+         *
+         */
+        protected ArrayList<String> getParameters() {
+            return cmdLineParameters;
+        }
+
+
+        /**
+         * Gets validity status
+         *
+         * @return   true if this environment is valid, false
+         *           otherwise
+         *
+         */
+        protected boolean isValid() {
+            return valid;
+        }
+
+
+        /**
+         * Converts null strings to blank strings ("")
+         *
+         * @param    s string to be converted if necessary
+         * @return   a non-null string, either the original or the empty string
+         *           ("") if the original was <code>null</code>
+         */
+        protected String nullsToBlanks(String s) {
+            return nullsToString(s, "");
+        }
+
+
+        /**
+         * Converts null strings to another string
+         *
+         * @param    couldBeNull string to be converted if necessary
+         * @param    subForNulls string to return instead of a null string
+         * @return   a non-null string, either the original or the substitute
+         *           string if the original was <code>null</code>
+         */
+        protected String nullsToString(String couldBeNull,
+                                       String subForNulls) {
+            return (couldBeNull == null ? subForNulls : couldBeNull);
+        }
+
+
+        /**
+         * Converts blank strings to another string
+         *
+         * @param    couldBeBlank string to be converted if necessary
+         * @param    subForBlanks string to return instead of a blank string
+         * @return   a non-null string, either the original or the substitute
+         *           string if the original was <code>null</code> or empty ("")
+         */
+        protected String blanksToString(String couldBeBlank,
+                                      String subForBlanks) {
+            return (("".equals(couldBeBlank) || couldBeBlank == null)
+                    ? subForBlanks
+                    : couldBeBlank);
+        }
+
+
+    } //class CGIEnvironment
+
+
+    /**
+     * Encapsulates the knowledge of how to run a CGI script, given the
+     * script's desired environment and (optionally) input/output streams
+     *
+     * <p>
+     *
+     * Exposes a <code>run</code> method used to actually invoke the
+     * CGI.
+     *
+     * </p>
+     * <p>
+     *
+     * The CGI environment and settings are derived from the information
+     * passed to the constructor.
+     *
+     * </p>
+     * <p>
+     *
+     * The input and output streams can be set by the <code>setInput</code>
+     * and <code>setResponse</code> methods, respectively.
+     * </p>
+     *
+     * @version $Id: CGIServlet.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+     */
+
+    protected class CGIRunner {
+
+        /** script/command to be executed */
+        private String command = null;
+
+        /** environment used when invoking the cgi script */
+        private Hashtable<String,String> env = null;
+
+        /** working directory used when invoking the cgi script */
+        private File wd = null;
+
+        /** command line parameters to be passed to the invoked script */
+        private ArrayList<String> params = null;
+
+        /** stdin to be passed to cgi script */
+        private InputStream stdin = null;
+
+        /** response object used to set headers & get output stream */
+        private HttpServletResponse response = null;
+
+        /** boolean tracking whether this object has enough info to run() */
+        private boolean readyToRun = false;
+
+
+        /**
+         *  Creates a CGIRunner and initializes its environment, working
+         *  directory, and query parameters.
+         *  <BR>
+         *  Input/output streams (optional) are set using the
+         *  <code>setInput</code> and <code>setResponse</code> methods,
+         *  respectively.
+         *
+         * @param  command  string full path to command to be executed
+         * @param  env      Hashtable with the desired script environment
+         * @param  wd       File with the script's desired working directory
+         * @param  params   ArrayList with the script's query command line
+         *                  parameters as strings
+         */
+        protected CGIRunner(String command, Hashtable<String,String> env,
+                            File wd, ArrayList<String> params) {
+            this.command = command;
+            this.env = env;
+            this.wd = wd;
+            this.params = params;
+            updateReadyStatus();
+        }
+
+
+        /**
+         * Checks & sets ready status
+         */
+        protected void updateReadyStatus() {
+            if (command != null
+                && env != null
+                && wd != null
+                && params != null
+                && response != null) {
+                readyToRun = true;
+            } else {
+                readyToRun = false;
+            }
+        }
+
+
+        /**
+         * Gets ready status
+         *
+         * @return   false if not ready (<code>run</code> will throw
+         *           an exception), true if ready
+         */
+        protected boolean isReady() {
+            return readyToRun;
+        }
+
+
+        /**
+         * Sets HttpServletResponse object used to set headers and send
+         * output to
+         *
+         * @param  response   HttpServletResponse to be used
+         *
+         */
+        protected void setResponse(HttpServletResponse response) {
+            this.response = response;
+            updateReadyStatus();
+        }
+
+
+        /**
+         * Sets standard input to be passed on to the invoked cgi script
+         *
+         * @param  stdin   InputStream to be used
+         *
+         */
+        protected void setInput(InputStream stdin) {
+            this.stdin = stdin;
+            updateReadyStatus();
+        }
+
+
+        /**
+         * Converts a Hashtable to a String array by converting each
+         * key/value pair in the Hashtable to a String in the form
+         * "key=value" (hashkey + "=" + hash.get(hashkey).toString())
+         *
+         * @param  h   Hashtable to convert
+         *
+         * @return     converted string array
+         *
+         * @exception  NullPointerException   if a hash key has a null value
+         *
+         */
+        protected String[] hashToStringArray(Hashtable<String,?> h)
+            throws NullPointerException {
+            Vector<String> v = new Vector<String>();
+            Enumeration<String> e = h.keys();
+            while (e.hasMoreElements()) {
+                String k = e.nextElement();
+                v.add(k + "=" + h.get(k).toString());
+            }
+            String[] strArr = new String[v.size()];
+            v.copyInto(strArr);
+            return strArr;
+        }
+
+
+        /**
+         * Executes a CGI script with the desired environment, current working
+         * directory, and input/output streams
+         *
+         * <p>
+         * This implements the following CGI specification recommedations:
+         * <UL>
+         * <LI> Servers SHOULD provide the "<code>query</code>" component of
+         *      the script-URI as command-line arguments to scripts if it
+         *      does not contain any unencoded "=" characters and the
+         *      command-line arguments can be generated in an unambiguous
+         *      manner.
+         * <LI> Servers SHOULD set the AUTH_TYPE metavariable to the value
+         *      of the "<code>auth-scheme</code>" token of the
+         *      "<code>Authorization</code>" if it was supplied as part of the
+         *      request header.  See <code>getCGIEnvironment</code> method.
+         * <LI> Where applicable, servers SHOULD set the current working
+         *      directory to the directory in which the script is located
+         *      before invoking it.
+         * <LI> Server implementations SHOULD define their behavior for the
+         *      following cases:
+         *     <ul>
+         *     <LI> <u>Allowed characters in pathInfo</u>:  This implementation
+         *             does not allow ASCII NUL nor any character which cannot
+         *             be URL-encoded according to internet standards;
+         *     <LI> <u>Allowed characters in path segments</u>: This
+         *             implementation does not allow non-terminal NULL
+         *             segments in the the path -- IOExceptions may be thrown;
+         *     <LI> <u>"<code>.</code>" and "<code>..</code>" path
+         *             segments</u>:
+         *             This implementation does not allow "<code>.</code>" and
+         *             "<code>..</code>" in the the path, and such characters
+         *             will result in an IOException being thrown (this should
+         *             never happen since Tomcat normalises the requestURI
+         *             before determining the contextPath, servletPath and
+         *             pathInfo);
+         *     <LI> <u>Implementation limitations</u>: This implementation
+         *             does not impose any limitations except as documented
+         *             above.  This implementation may be limited by the
+         *             servlet container used to house this implementation.
+         *             In particular, all the primary CGI variable values
+         *             are derived either directly or indirectly from the
+         *             container's implementation of the Servlet API methods.
+         *     </ul>
+         * </UL>
+         * </p>
+         *
+         * @exception IOException if problems during reading/writing occur
+         *
+         * @see    java.lang.Runtime#exec(String command, String[] envp,
+         *                                File dir)
+         */
+        protected void run() throws IOException {
+
+            /*
+             * REMIND:  this method feels too big; should it be re-written?
+             */
+
+            if (!isReady()) {
+                throw new IOException(this.getClass().getName()
+                                      + ": not ready to run.");
+            }
+
+            if (debug >= 1 ) {
+                log("runCGI(envp=[" + env + "], command=" + command + ")");
+            }
+
+            if ((command.indexOf(File.separator + "." + File.separator) >= 0)
+                || (command.indexOf(File.separator + "..") >= 0)
+                || (command.indexOf(".." + File.separator) >= 0)) {
+                throw new IOException(this.getClass().getName()
+                                      + "Illegal Character in CGI command "
+                                      + "path ('.' or '..') detected.  Not "
+                                      + "running CGI [" + command + "].");
+            }
+
+            /* original content/structure of this section taken from
+             * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4216884
+             * with major modifications by Martin Dengler
+             */
+            Runtime rt = null;
+            BufferedReader cgiHeaderReader = null;
+            InputStream cgiOutput = null;
+            BufferedReader commandsStdErr = null;
+            Thread errReaderThread = null;
+            BufferedOutputStream commandsStdIn = null;
+            Process proc = null;
+            int bufRead = -1;
+
+            List<String> cmdAndArgs = new ArrayList<String>();
+            if (cgiExecutable.length() != 0) {
+                cmdAndArgs.add(cgiExecutable);
+            }
+            if (cgiExecutableArgs != null) {
+                cmdAndArgs.addAll(cgiExecutableArgs);
+            }
+            cmdAndArgs.add(command);
+            cmdAndArgs.addAll(params);
+
+            try {
+                rt = Runtime.getRuntime();
+                proc = rt.exec(
+                        cmdAndArgs.toArray(new String[cmdAndArgs.size()]),
+                        hashToStringArray(env), wd);
+    
+                String sContentLength = env.get("CONTENT_LENGTH");
+
+                if(!"".equals(sContentLength)) {
+                    commandsStdIn = new BufferedOutputStream(proc.getOutputStream());
+                    IOTools.flow(stdin, commandsStdIn);
+                    commandsStdIn.flush();
+                    commandsStdIn.close();
+                }
+
+                /* we want to wait for the process to exit,  Process.waitFor()
+                 * is useless in our situation; see
+                 * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4223650
+                 */
+
+                boolean isRunning = true;
+                commandsStdErr = new BufferedReader
+                    (new InputStreamReader(proc.getErrorStream()));
+                final BufferedReader stdErrRdr = commandsStdErr ;
+
+                errReaderThread = new Thread() {
+                    @Override
+                    public void run () {
+                        sendToLog(stdErrRdr) ;
+                    }
+                };
+                errReaderThread.start();
+
+                InputStream cgiHeaderStream =
+                    new HTTPHeaderInputStream(proc.getInputStream());
+                cgiHeaderReader =
+                    new BufferedReader(new InputStreamReader(cgiHeaderStream));
+            
+                while (isRunning) {
+                    try {
+                        //set headers
+                        String line = null;
+                        while (((line = cgiHeaderReader.readLine()) != null)
+                               && !("".equals(line))) {
+                            if (debug >= 2) {
+                                log("runCGI: addHeader(\"" + line + "\")");
+                            }
+                            if (line.startsWith("HTTP")) {
+                                response.setStatus(getSCFromHttpStatusLine(line));
+                            } else if (line.indexOf(":") >= 0) {
+                                String header =
+                                    line.substring(0, line.indexOf(":")).trim();
+                                String value =
+                                    line.substring(line.indexOf(":") + 1).trim(); 
+                                if (header.equalsIgnoreCase("status")) {
+                                    response.setStatus(getSCFromCGIStatusHeader(value));
+                                } else {
+                                    response.addHeader(header , value);
+                                }
+                            } else {
+                                log("runCGI: bad header line \"" + line + "\"");
+                            }
+                        }
+    
+                        //write output
+                        byte[] bBuf = new byte[2048];
+    
+                        OutputStream out = response.getOutputStream();
+                        cgiOutput = proc.getInputStream();
+    
+                        try {
+                            while ((bufRead = cgiOutput.read(bBuf)) != -1) {
+                                if (debug >= 4) {
+                                    log("runCGI: output " + bufRead +
+                                        " bytes of data");
+                                }
+                                out.write(bBuf, 0, bufRead);
+                            }
+                        } finally {
+                            // Attempt to consume any leftover byte if something bad happens,
+                            // such as a socket disconnect on the servlet side; otherwise, the
+                            // external process could hang
+                            if (bufRead != -1) {
+                                while ((bufRead = cgiOutput.read(bBuf)) != -1) {
+                                    // NOOP - just read the data
+                                }
+                            }
+                        }
+        
+                        proc.exitValue(); // Throws exception if alive
+    
+                        isRunning = false;
+    
+                    } catch (IllegalThreadStateException e) {
+                        try {
+                            Thread.sleep(500);
+                        } catch (InterruptedException ignored) {
+                            // Ignore
+                        }
+                    }
+                } //replacement for Process.waitFor()
+    
+            }
+            catch (IOException e){
+                log ("Caught exception " + e);
+                throw e;
+            }
+            finally{
+                // Close the header reader
+                if (cgiHeaderReader != null) {
+                    try {
+                        cgiHeaderReader.close();
+                    } catch (IOException ioe) {
+                        log ("Exception closing header reader " + ioe);
+                    }
+                }
+                // Close the output stream if used
+                if (cgiOutput != null) {
+                    try {
+                        cgiOutput.close();
+                    } catch (IOException ioe) {
+                        log ("Exception closing output stream " + ioe);
+                    }
+                }
+                // Make sure the error stream reader has finished
+                if (errReaderThread != null) {
+                    try {
+                        errReaderThread.join(stderrTimeout);
+                    } catch (InterruptedException e) {
+                        log ("Interupted waiting for stderr reader thread");
+                    }
+                }
+                if (debug > 4) {
+                    log ("Running finally block");
+                }
+                if (proc != null){
+                    proc.destroy();
+                    proc = null;
+                }
+            }
+        }
+
+        /**
+         * Parses the Status-Line and extracts the status code.
+         * 
+         * @param line The HTTP Status-Line (RFC2616, section 6.1)
+         * @return The extracted status code or the code representing an
+         * internal error if a valid status code cannot be extracted. 
+         */
+        private int getSCFromHttpStatusLine(String line) {
+            int statusStart = line.indexOf(' ') + 1;
+            
+            if (statusStart < 1 || line.length() < statusStart + 3) {
+                // Not a valid HTTP Status-Line
+                log ("runCGI: invalid HTTP Status-Line:" + line);
+                return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+            }
+            
+            String status = line.substring(statusStart, statusStart + 3);
+            
+            int statusCode;
+            try {
+                statusCode = Integer.parseInt(status);
+            } catch (NumberFormatException nfe) {
+                // Not a valid status code
+                log ("runCGI: invalid status code:" + status);
+                return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+            }
+            
+            return statusCode;
+        }
+
+        /**
+         * Parses the CGI Status Header value and extracts the status code.
+         * 
+         * @param value The CGI Status value of the form <code>
+         *             digit digit digit SP reason-phrase</code>
+         * @return The extracted status code or the code representing an
+         * internal error if a valid status code cannot be extracted. 
+         */
+        private int getSCFromCGIStatusHeader(String value) {
+            if (value.length() < 3) {
+                // Not a valid status value
+                log ("runCGI: invalid status value:" + value);
+                return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+            }
+            
+            String status = value.substring(0, 3);
+            
+            int statusCode;
+            try {
+                statusCode = Integer.parseInt(status);
+            } catch (NumberFormatException nfe) {
+                // Not a valid status code
+                log ("runCGI: invalid status code:" + status);
+                return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+            }
+            
+            return statusCode;
+        }
+        
+        private void sendToLog(BufferedReader rdr) {
+            String line = null;
+            int lineCount = 0 ;
+            try {
+                while ((line = rdr.readLine()) != null) {
+                    log("runCGI (stderr):" +  line) ;
+                    lineCount++ ;
+                }
+            } catch (IOException e) {
+                log("sendToLog error", e) ;
+            } finally {
+                try {
+                    rdr.close() ;
+                } catch (IOException ce) {
+                    log("sendToLog error", ce) ;
+                }
+            }
+            if ( lineCount > 0 && debug > 2) {
+                log("runCGI: " + lineCount + " lines received on stderr") ;
+            }
+        }
+    } //class CGIRunner
+
+    /**
+     * This is an input stream specifically for reading HTTP headers. It reads
+     * upto and including the two blank lines terminating the headers. It
+     * allows the content to be read using bytes or characters as appropriate.
+     */
+    protected static class HTTPHeaderInputStream extends InputStream {
+        private static final int STATE_CHARACTER = 0;
+        private static final int STATE_FIRST_CR = 1;
+        private static final int STATE_FIRST_LF = 2;
+        private static final int STATE_SECOND_CR = 3;
+        private static final int STATE_HEADER_END = 4;
+        
+        private InputStream input;
+        private int state;
+        
+        HTTPHeaderInputStream(InputStream theInput) {
+            input = theInput;
+            state = STATE_CHARACTER;
+        }
+
+        /**
+         * @see java.io.InputStream#read()
+         */
+        @Override
+        public int read() throws IOException {
+            if (state == STATE_HEADER_END) {
+                return -1;
+            }
+
+            int i = input.read();
+
+            // Update the state
+            // State machine looks like this
+            //
+            //    -------->--------
+            //   |      (CR)       |
+            //   |                 |
+            //  CR1--->---         |
+            //   |        |        |
+            //   ^(CR)    |(LF)    |
+            //   |        |        |
+            // CHAR--->--LF1--->--EOH
+            //      (LF)  |  (LF)  |
+            //            |(CR)    ^(LF)
+            //            |        |
+            //          (CR2)-->---
+            
+            if (i == 10) {
+                // LF
+                switch(state) {
+                    case STATE_CHARACTER:
+                        state = STATE_FIRST_LF;
+                        break;
+                    case STATE_FIRST_CR:
+                        state = STATE_FIRST_LF;
+                        break;
+                    case STATE_FIRST_LF:
+                    case STATE_SECOND_CR:
+                        state = STATE_HEADER_END;
+                        break;
+                }
+
+            } else if (i == 13) {
+                // CR
+                switch(state) {
+                    case STATE_CHARACTER:
+                        state = STATE_FIRST_CR;
+                        break;
+                    case STATE_FIRST_CR:
+                        state = STATE_HEADER_END;
+                        break;
+                    case STATE_FIRST_LF:
+                        state = STATE_SECOND_CR;
+                        break;
+                }
+
+            } else {
+                state = STATE_CHARACTER;
+            }
+            
+            return i;            
+        }
+    }  // class HTTPHeaderInputStream
+
+} //class CGIServlet
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/Constants.java b/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/Constants.java
new file mode 100644
index 0000000..f716da1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/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.servlets;
+
+
+public class Constants {
+
+    public static final String Package = "org.apache.catalina.servlets";
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/DefaultServlet.java b/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/DefaultServlet.java
new file mode 100644
index 0000000..8d7fdb3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/DefaultServlet.java
@@ -0,0 +1,2157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.servlets;
+
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.RandomAccessFile;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.StringTokenizer;
+
+import javax.naming.InitialContext;
+import javax.naming.NameClassPair;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletResponseWrapper;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.apache.catalina.Globals;
+import org.apache.catalina.connector.RequestFacade;
+import org.apache.catalina.connector.ResponseFacade;
+import org.apache.catalina.util.RequestUtil;
+import org.apache.catalina.util.ServerInfo;
+import org.apache.catalina.util.URLEncoder;
+import org.apache.naming.resources.CacheEntry;
+import org.apache.naming.resources.ProxyDirContext;
+import org.apache.naming.resources.Resource;
+import org.apache.naming.resources.ResourceAttributes;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * <p>The default resource-serving servlet for most web applications,
+ * used to serve static resources such as HTML pages and images.
+ * </p>
+ * <p>
+ * This servlet is intended to be mapped to <em>/</em> e.g.:
+ * </p>
+ * <pre>
+ *   &lt;servlet-mapping&gt;
+ *       &lt;servlet-name&gt;default&lt;/servlet-name&gt;
+ *       &lt;url-pattern&gt;/&lt;/url-pattern&gt;
+ *   &lt;/servlet-mapping&gt;
+ * </pre>
+ * <p>It can be mapped to sub-paths, however in all cases resources are served
+ * from the web appplication resource root using the full path from the root
+ * of the web application context.
+ * <br/>e.g. given a web application structure:
+ *</p>
+ * <pre>
+ * /context
+ *   /images
+ *     tomcat2.jpg
+ *   /static
+ *     /images
+ *       tomcat.jpg
+ * </pre>
+ * <p>
+ * ... and a servlet mapping that maps only <code>/static/*</code> to the default servlet:
+ * </p>
+ * <pre>
+ *   &lt;servlet-mapping&gt;
+ *       &lt;servlet-name&gt;default&lt;/servlet-name&gt;
+ *       &lt;url-pattern&gt;/static/*&lt;/url-pattern&gt;
+ *   &lt;/servlet-mapping&gt;
+ * </pre>
+ * <p>
+ * Then a request to <code>/context/static/images/tomcat.jpg</code> will succeed
+ * while a request to <code>/context/images/tomcat2.jpg</code> will fail. 
+ * </p>
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @version $Id: DefaultServlet.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ */
+
+public class DefaultServlet
+    extends HttpServlet {
+
+    private static final long serialVersionUID = 1L;
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The debugging detail level for this servlet.
+     */
+    protected int debug = 0;
+
+
+    /**
+     * The input buffer size to use when serving resources.
+     */
+    protected int input = 2048;
+
+
+    /**
+     * Should we generate directory listings?
+     */
+    protected boolean listings = false;
+
+
+    /**
+     * Read only flag. By default, it's set to true.
+     */
+    protected boolean readOnly = true;
+
+
+    /**
+     * The output buffer size to use when serving resources.
+     */
+    protected int output = 2048;
+
+
+    /**
+     * Array containing the safe characters set.
+     */
+    protected static URLEncoder urlEncoder;
+
+
+    /**
+     * Allow customized directory listing per directory.
+     */
+    protected String localXsltFile = null;
+
+
+    /**
+     * Allow customized directory listing per context.
+     */
+    protected String contextXsltFile = null;
+    
+    
+    /**
+     * Allow customized directory listing per instance.
+     */
+    protected String globalXsltFile = null;
+
+
+    /**
+     * Allow a readme file to be included.
+     */
+    protected String readmeFile = null;
+
+
+    /**
+     * Proxy directory context.
+     */
+    protected transient ProxyDirContext resources = null;
+
+
+    /**
+     * File encoding to be used when reading static files. If none is specified
+     * the platform default is used.
+     */
+    protected String fileEncoding = null;
+    
+    
+    /**
+     * Minimum size for sendfile usage in bytes.
+     */
+    protected int sendfileSize = 48 * 1024;
+    
+    /**
+     * Should the Accept-Ranges: bytes header be send with static resources?
+     */
+    protected boolean useAcceptRanges = true;
+
+    /**
+     * Full range marker.
+     */
+    protected static ArrayList<Range> FULL = new ArrayList<Range>();
+    
+    
+    // ----------------------------------------------------- Static Initializer
+
+
+    /**
+     * GMT timezone - all HTTP dates are on GMT
+     */
+    static {
+        urlEncoder = new URLEncoder();
+        urlEncoder.addSafeCharacter('-');
+        urlEncoder.addSafeCharacter('_');
+        urlEncoder.addSafeCharacter('.');
+        urlEncoder.addSafeCharacter('*');
+        urlEncoder.addSafeCharacter('/');
+    }
+
+
+    /**
+     * MIME multipart separation string
+     */
+    protected static final String mimeSeparation = "CATALINA_MIME_BOUNDARY";
+
+
+    /**
+     * JNDI resources name.
+     */
+    protected static final String RESOURCES_JNDI_NAME = "java:/comp/Resources";
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
+     * Size of file transfer buffer in bytes.
+     */
+    protected static final int BUFFER_SIZE = 4096;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Finalize this servlet.
+     */
+    @Override
+    public void destroy() {
+        // NOOP
+    }
+
+
+    /**
+     * Initialize this servlet.
+     */
+    @Override
+    public void init() throws ServletException {
+
+        if (getServletConfig().getInitParameter("debug") != null)
+            debug = Integer.parseInt(getServletConfig().getInitParameter("debug"));
+
+        if (getServletConfig().getInitParameter("input") != null)
+            input = Integer.parseInt(getServletConfig().getInitParameter("input"));
+
+        if (getServletConfig().getInitParameter("output") != null)
+            output = Integer.parseInt(getServletConfig().getInitParameter("output"));
+
+        listings = Boolean.parseBoolean(getServletConfig().getInitParameter("listings"));
+
+        if (getServletConfig().getInitParameter("readonly") != null)
+            readOnly = Boolean.parseBoolean(getServletConfig().getInitParameter("readonly"));
+
+        if (getServletConfig().getInitParameter("sendfileSize") != null)
+            sendfileSize = 
+                Integer.parseInt(getServletConfig().getInitParameter("sendfileSize")) * 1024;
+
+        fileEncoding = getServletConfig().getInitParameter("fileEncoding");
+
+        globalXsltFile = getServletConfig().getInitParameter("globalXsltFile");
+        contextXsltFile = getServletConfig().getInitParameter("contextXsltFile");
+        localXsltFile = getServletConfig().getInitParameter("localXsltFile");
+        readmeFile = getServletConfig().getInitParameter("readmeFile");
+
+        if (getServletConfig().getInitParameter("useAcceptRanges") != null)
+            useAcceptRanges = Boolean.parseBoolean(getServletConfig().getInitParameter("useAcceptRanges"));
+
+        // Sanity check on the specified buffer sizes
+        if (input < 256)
+            input = 256;
+        if (output < 256)
+            output = 256;
+
+        if (debug > 0) {
+            log("DefaultServlet.init:  input buffer size=" + input +
+                ", output buffer size=" + output);
+        }
+
+        // Load the proxy dir context.
+        resources = (ProxyDirContext) getServletContext()
+            .getAttribute(Globals.RESOURCES_ATTR);
+        if (resources == null) {
+            try {
+                resources =
+                    (ProxyDirContext) new InitialContext()
+                    .lookup(RESOURCES_JNDI_NAME);
+            } catch (NamingException e) {
+                // Failed
+                throw new ServletException("No resources", e);
+            }
+        }
+
+        if (resources == null) {
+            throw new UnavailableException("No resources");
+        }
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Return the relative path associated with this servlet.
+     *
+     * @param request The servlet request we are processing
+     */
+    protected String getRelativePath(HttpServletRequest request) {
+        // IMPORTANT: DefaultServlet can be mapped to '/' or '/path/*' but always
+        // serves resources from the web app root with context rooted paths.
+        // i.e. it can not be used to mount the web app root under a sub-path
+        // This method must construct a complete context rooted path, although
+        // subclasses can change this behaviour.
+
+        // 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);
+            } else {
+                result = (String) request.getAttribute(
+                        RequestDispatcher.INCLUDE_SERVLET_PATH) + result;
+            }
+            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();
+        } else {
+            result = request.getServletPath() + result;
+        }
+        if ((result == null) || (result.equals(""))) {
+            result = "/";
+        }
+        return (result);
+
+    }
+
+
+    /**
+     * Determines the appropriate path to prepend resources with
+     * when generating directory listings. Depending on the behaviour of 
+     * {@link #getRelativePath(HttpServletRequest)} this will change.
+     * @param request the request to determine the path for
+     * @return the prefix to apply to all resources in the listing.
+     */
+    protected String getPathPrefix(final HttpServletRequest request) {
+        return request.getContextPath();
+    }
+
+
+    /**
+     * 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
+    protected void doGet(HttpServletRequest request,
+                         HttpServletResponse response)
+        throws IOException, ServletException {
+
+        // Serve the requested resource, including the data content
+        serveResource(request, response, true);
+
+    }
+
+
+    /**
+     * Process a HEAD 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
+    protected void doHead(HttpServletRequest request,
+                          HttpServletResponse response)
+        throws IOException, ServletException {
+
+        // Serve the requested resource, without the data content
+        serveResource(request, response, false);
+
+    }
+
+
+    /**
+     * Override default implementation to ensure that TRACE is correctly
+     * handled.
+     *
+     * @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
+     */
+    @Override
+    protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        StringBuilder allow = new StringBuilder();
+        // There is a doGet method
+        allow.append("GET, HEAD");
+        // There is a doPost
+        allow.append(", POST");
+        // There is a doPut
+        allow.append(", PUT");
+        // There is a doDelete
+        allow.append(", DELETE");
+        // Trace - assume disabled unless we can prove otherwise
+        if (req instanceof RequestFacade &&
+                ((RequestFacade) req).getAllowTrace()) {
+            allow.append(", TRACE");
+        }
+        // Always allow options
+        allow.append(", OPTIONS");
+        
+        resp.setHeader("Allow", allow.toString());
+    }
+    
+    
+    /**
+     * 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
+    protected void doPost(HttpServletRequest request,
+                          HttpServletResponse response)
+        throws IOException, ServletException {
+        doGet(request, response);
+    }
+
+
+    /**
+     * Process a PUT request for the specified resource.
+     *
+     * @param req The servlet request we are processing
+     * @param resp The servlet response we are creating
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet-specified error occurs
+     */
+    @Override
+    protected void doPut(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        if (readOnly) {
+            resp.sendError(HttpServletResponse.SC_FORBIDDEN);
+            return;
+        }
+
+        String path = getRelativePath(req);
+
+        boolean exists = true;
+        try {
+            resources.lookup(path);
+        } catch (NamingException e) {
+            exists = false;
+        }
+
+        boolean result = true;
+
+        // Temp. content file used to support partial PUT
+        File contentFile = null;
+
+        Range range = parseContentRange(req, resp);
+
+        InputStream resourceInputStream = null;
+
+        // Append data specified in ranges to existing content for this
+        // resource - create a temp. file on the local filesystem to
+        // perform this operation
+        // Assume just one range is specified for now
+        if (range != null) {
+            contentFile = executePartialPut(req, range, path);
+            resourceInputStream = new FileInputStream(contentFile);
+        } else {
+            resourceInputStream = req.getInputStream();
+        }
+
+        try {
+            Resource newResource = new Resource(resourceInputStream);
+            // FIXME: Add attributes
+            if (exists) {
+                resources.rebind(path, newResource);
+            } else {
+                resources.bind(path, newResource);
+            }
+        } catch(NamingException e) {
+            result = false;
+        }
+
+        if (result) {
+            if (exists) {
+                resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
+            } else {
+                resp.setStatus(HttpServletResponse.SC_CREATED);
+            }
+        } else {
+            resp.sendError(HttpServletResponse.SC_CONFLICT);
+        }
+
+    }
+
+
+    /**
+     * Handle a partial PUT.  New content specified in request is appended to
+     * existing content in oldRevisionContent (if present). This code does
+     * not support simultaneous partial updates to the same resource.
+     */
+    protected File executePartialPut(HttpServletRequest req, Range range,
+                                     String path)
+        throws IOException {
+
+        // Append data specified in ranges to existing content for this
+        // resource - create a temp. file on the local filesystem to
+        // perform this operation
+        File tempDir = (File) getServletContext().getAttribute
+            (ServletContext.TEMPDIR);
+        // Convert all '/' characters to '.' in resourcePath
+        String convertedResourcePath = path.replace('/', '.');
+        File contentFile = new File(tempDir, convertedResourcePath);
+        if (contentFile.createNewFile()) {
+            // Clean up contentFile when Tomcat is terminated
+            contentFile.deleteOnExit();
+        }
+
+        RandomAccessFile randAccessContentFile =
+            new RandomAccessFile(contentFile, "rw");
+
+        Resource oldResource = null;
+        try {
+            Object obj = resources.lookup(path);
+            if (obj instanceof Resource)
+                oldResource = (Resource) obj;
+        } catch (NamingException e) {
+            // Ignore
+        }
+
+        // Copy data in oldRevisionContent to contentFile
+        if (oldResource != null) {
+            BufferedInputStream bufOldRevStream =
+                new BufferedInputStream(oldResource.streamContent(),
+                                        BUFFER_SIZE);
+
+            int numBytesRead;
+            byte[] copyBuffer = new byte[BUFFER_SIZE];
+            while ((numBytesRead = bufOldRevStream.read(copyBuffer)) != -1) {
+                randAccessContentFile.write(copyBuffer, 0, numBytesRead);
+            }
+
+            bufOldRevStream.close();
+        }
+
+        randAccessContentFile.setLength(range.length);
+
+        // Append data in request input stream to contentFile
+        randAccessContentFile.seek(range.start);
+        int numBytesRead;
+        byte[] transferBuffer = new byte[BUFFER_SIZE];
+        BufferedInputStream requestBufInStream =
+            new BufferedInputStream(req.getInputStream(), BUFFER_SIZE);
+        while ((numBytesRead = requestBufInStream.read(transferBuffer)) != -1) {
+            randAccessContentFile.write(transferBuffer, 0, numBytesRead);
+        }
+        randAccessContentFile.close();
+        requestBufInStream.close();
+
+        return contentFile;
+
+    }
+
+
+    /**
+     * Process a DELETE request for the specified resource.
+     *
+     * @param req The servlet request we are processing
+     * @param resp The servlet response we are creating
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet-specified error occurs
+     */
+    @Override
+    protected void doDelete(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        if (readOnly) {
+            resp.sendError(HttpServletResponse.SC_FORBIDDEN);
+            return;
+        }
+
+        String path = getRelativePath(req);
+
+        boolean exists = true;
+        try {
+            resources.lookup(path);
+        } catch (NamingException e) {
+            exists = false;
+        }
+
+        if (exists) {
+            boolean result = true;
+            try {
+                resources.unbind(path);
+            } catch (NamingException e) {
+                result = false;
+            }
+            if (result) {
+                resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
+            } else {
+                resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+            }
+        } else {
+            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+        }
+
+    }
+
+
+    /**
+     * Check if the conditions specified in the optional If headers are
+     * satisfied.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     * @param resourceAttributes The resource information
+     * @return boolean true if the resource meets all the specified conditions,
+     * and false if any of the conditions is not satisfied, in which case
+     * request processing is stopped
+     */
+    protected boolean checkIfHeaders(HttpServletRequest request,
+                                     HttpServletResponse response,
+                                     ResourceAttributes resourceAttributes)
+        throws IOException {
+
+        return checkIfMatch(request, response, resourceAttributes)
+            && checkIfModifiedSince(request, response, resourceAttributes)
+            && checkIfNoneMatch(request, response, resourceAttributes)
+            && checkIfUnmodifiedSince(request, response, resourceAttributes);
+
+    }
+
+
+    /**
+     * URL rewriter.
+     *
+     * @param path Path which has to be rewritten
+     */
+    protected String rewriteUrl(String path) {
+        return urlEncoder.encode( path );
+    }
+
+
+    /**
+     * Display the size of a file.
+     */
+    protected void displaySize(StringBuilder buf, int filesize) {
+
+        int leftside = filesize / 1024;
+        int rightside = (filesize % 1024) / 103;  // makes 1 digit
+        // To avoid 0.0 for non-zero file, we bump to 0.1
+        if (leftside == 0 && rightside == 0 && filesize != 0)
+            rightside = 1;
+        buf.append(leftside).append(".").append(rightside);
+        buf.append(" KB");
+
+    }
+
+
+    /**
+     * Serve the specified resource, optionally including the data content.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     * @param content Should the content be included?
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet-specified error occurs
+     */
+    protected void serveResource(HttpServletRequest request,
+                                 HttpServletResponse response,
+                                 boolean content)
+        throws IOException, ServletException {
+
+        boolean serveContent = content;
+        
+        // Identify the requested resource path
+        String path = getRelativePath(request);
+        if (debug > 0) {
+            if (serveContent)
+                log("DefaultServlet.serveResource:  Serving resource '" +
+                    path + "' headers and data");
+            else
+                log("DefaultServlet.serveResource:  Serving resource '" +
+                    path + "' headers only");
+        }
+
+        CacheEntry cacheEntry = resources.lookupCache(path);
+
+        if (!cacheEntry.exists) {
+            // Check if we're included so we can return the appropriate 
+            // missing resource name in the error
+            String requestUri = (String) request.getAttribute(
+                    RequestDispatcher.INCLUDE_REQUEST_URI);
+            if (requestUri == null) {
+                requestUri = request.getRequestURI();
+            } else {
+                // We're included
+                // SRV.9.3 says we must throw a FNFE
+                throw new FileNotFoundException(
+                        sm.getString("defaultServlet.missingResource",
+                    requestUri));
+            }
+
+            response.sendError(HttpServletResponse.SC_NOT_FOUND,
+                               requestUri);
+            return;
+        }
+
+        // If the resource is not a collection, and the resource path
+        // ends with "/" or "\", return NOT FOUND
+        if (cacheEntry.context == null) {
+            if (path.endsWith("/") || (path.endsWith("\\"))) {
+                // Check if we're included so we can return the appropriate 
+                // missing resource name in the error
+                String requestUri = (String) request.getAttribute(
+                        RequestDispatcher.INCLUDE_REQUEST_URI);
+                if (requestUri == null) {
+                    requestUri = request.getRequestURI();
+                }
+                response.sendError(HttpServletResponse.SC_NOT_FOUND,
+                                   requestUri);
+                return;
+            }
+        }
+
+        boolean isError =
+            response.getStatus() >= HttpServletResponse.SC_BAD_REQUEST;
+
+        // Check if the conditions specified in the optional If headers are
+        // satisfied.
+        if (cacheEntry.context == null) {
+
+            // Checking If headers
+            boolean included = (request.getAttribute(
+                    RequestDispatcher.INCLUDE_CONTEXT_PATH) != null);
+            if (!included && !isError &&
+                    !checkIfHeaders(request, response, cacheEntry.attributes)) {
+                return;
+            }
+
+        }
+
+        // Find content type.
+        String contentType = cacheEntry.attributes.getMimeType();
+        if (contentType == null) {
+            contentType = getServletContext().getMimeType(cacheEntry.name);
+            cacheEntry.attributes.setMimeType(contentType);
+        }
+
+        ArrayList<Range> ranges = null;
+        long contentLength = -1L;
+
+        if (cacheEntry.context != null) {
+
+            // Skip directory listings if we have been configured to
+            // suppress them
+            if (!listings) {
+                response.sendError(HttpServletResponse.SC_NOT_FOUND,
+                                   request.getRequestURI());
+                return;
+            }
+            contentType = "text/html;charset=UTF-8";
+
+        } else {
+            if (!isError) {
+                if (useAcceptRanges) {
+                    // Accept ranges header
+                    response.setHeader("Accept-Ranges", "bytes");
+                }
+    
+                // Parse range specifier
+                ranges = parseRange(request, response, cacheEntry.attributes);
+    
+                // ETag header
+                response.setHeader("ETag", cacheEntry.attributes.getETag());
+    
+                // Last-Modified header
+                response.setHeader("Last-Modified",
+                        cacheEntry.attributes.getLastModifiedHttp());
+            }
+
+            // Get content length
+            contentLength = cacheEntry.attributes.getContentLength();
+            // Special case for zero length files, which would cause a
+            // (silent) ISE when setting the output buffer size
+            if (contentLength == 0L) {
+                serveContent = false;
+            }
+
+        }
+
+        ServletOutputStream ostream = null;
+        PrintWriter writer = null;
+
+        if (serveContent) {
+
+            // Trying to retrieve the servlet output stream
+
+            try {
+                ostream = response.getOutputStream();
+            } catch (IllegalStateException e) {
+                // If it fails, we try to get a Writer instead if we're
+                // trying to serve a text file
+                if ( (contentType == null)
+                        || (contentType.startsWith("text"))
+                        || (contentType.endsWith("xml")) ) {
+                    writer = response.getWriter();
+                    // Cannot reliably serve partial content with a Writer
+                    ranges = FULL;
+                } else {
+                    throw e;
+                }
+            }
+
+        }
+
+        // Check to see if a Filter, Valve of wrapper has written some content.
+        // If it has, disable range requests and setting of a content length
+        // since neither can be done reliably.
+        ServletResponse r = response;
+        long contentWritten = 0;
+        while (r instanceof ServletResponseWrapper) {
+            r = ((ServletResponseWrapper) r).getResponse();
+        }
+        if (r instanceof ResponseFacade) {
+            contentWritten = ((ResponseFacade) r).getContentWritten();
+        }
+        if (contentWritten > 0) {
+            ranges = FULL;
+        }
+        
+        if ( (cacheEntry.context != null)
+                || isError
+                || ( ((ranges == null) || (ranges.isEmpty()))
+                        && (request.getHeader("Range") == null) )
+                || (ranges == FULL) ) {
+
+            // Set the appropriate output headers
+            if (contentType != null) {
+                if (debug > 0)
+                    log("DefaultServlet.serveFile:  contentType='" +
+                        contentType + "'");
+                response.setContentType(contentType);
+            }
+            if ((cacheEntry.resource != null) && (contentLength >= 0)
+                    && (!serveContent || ostream != null)) {
+                if (debug > 0)
+                    log("DefaultServlet.serveFile:  contentLength=" +
+                        contentLength);
+                // Don't set a content length if something else has already
+                // written to the response.
+                if (contentWritten == 0) {
+                    if (contentLength < Integer.MAX_VALUE) {
+                        response.setContentLength((int) contentLength);
+                    } else {
+                        // Set the content-length as String to be able to use a
+                        // long
+                        response.setHeader("content-length",
+                                "" + contentLength);
+                    }
+                }
+            }
+
+            InputStream renderResult = null;
+            if (cacheEntry.context != null) {
+
+                if (serveContent) {
+                    // Serve the directory browser
+                    renderResult = render(getPathPrefix(request), cacheEntry);
+                }
+
+            }
+
+            // Copy the input stream to our output stream (if requested)
+            if (serveContent) {
+                try {
+                    response.setBufferSize(output);
+                } catch (IllegalStateException e) {
+                    // Silent catch
+                }
+                if (ostream != null) {
+                    if (!checkSendfile(request, response, cacheEntry, contentLength, null))
+                        copy(cacheEntry, renderResult, ostream);
+                } else {
+                    copy(cacheEntry, renderResult, writer);
+                }
+            }
+
+        } else {
+
+            if ((ranges == null) || (ranges.isEmpty()))
+                return;
+
+            // Partial content response.
+
+            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
+
+            if (ranges.size() == 1) {
+
+                Range range = ranges.get(0);
+                response.addHeader("Content-Range", "bytes "
+                                   + range.start
+                                   + "-" + range.end + "/"
+                                   + range.length);
+                long length = range.end - range.start + 1;
+                if (length < Integer.MAX_VALUE) {
+                    response.setContentLength((int) length);
+                } else {
+                    // Set the content-length as String to be able to use a long
+                    response.setHeader("content-length", "" + length);
+                }
+
+                if (contentType != null) {
+                    if (debug > 0)
+                        log("DefaultServlet.serveFile:  contentType='" +
+                            contentType + "'");
+                    response.setContentType(contentType);
+                }
+
+                if (serveContent) {
+                    try {
+                        response.setBufferSize(output);
+                    } catch (IllegalStateException e) {
+                        // Silent catch
+                    }
+                    if (ostream != null) {
+                        if (!checkSendfile(request, response, cacheEntry, range.end - range.start + 1, range))
+                            copy(cacheEntry, ostream, range);
+                    } else {
+                        // we should not get here
+                        throw new IllegalStateException();
+                    }
+                }
+
+            } else {
+
+                response.setContentType("multipart/byteranges; boundary="
+                                        + mimeSeparation);
+
+                if (serveContent) {
+                    try {
+                        response.setBufferSize(output);
+                    } catch (IllegalStateException e) {
+                        // Silent catch
+                    }
+                    if (ostream != null) {
+                        copy(cacheEntry, ostream, ranges.iterator(),
+                             contentType);
+                    } else {
+                        // we should not get here
+                        throw new IllegalStateException();
+                    }
+                }
+
+            }
+
+        }
+
+    }
+
+
+    /**
+     * Parse the content-range header.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     * @return Range
+     */
+    protected Range parseContentRange(HttpServletRequest request,
+                                      HttpServletResponse response)
+        throws IOException {
+
+        // Retrieving the content-range header (if any is specified
+        String rangeHeader = request.getHeader("Content-Range");
+
+        if (rangeHeader == null)
+            return null;
+
+        // bytes is the only range unit supported
+        if (!rangeHeader.startsWith("bytes")) {
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+            return null;
+        }
+
+        rangeHeader = rangeHeader.substring(6).trim();
+
+        int dashPos = rangeHeader.indexOf('-');
+        int slashPos = rangeHeader.indexOf('/');
+
+        if (dashPos == -1) {
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+            return null;
+        }
+
+        if (slashPos == -1) {
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+            return null;
+        }
+
+        Range range = new Range();
+
+        try {
+            range.start = Long.parseLong(rangeHeader.substring(0, dashPos));
+            range.end =
+                Long.parseLong(rangeHeader.substring(dashPos + 1, slashPos));
+            range.length = Long.parseLong
+                (rangeHeader.substring(slashPos + 1, rangeHeader.length()));
+        } catch (NumberFormatException e) {
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+            return null;
+        }
+
+        if (!range.validate()) {
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+            return null;
+        }
+
+        return range;
+
+    }
+
+
+    /**
+     * Parse the range header.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     * @return Vector of ranges
+     */
+    protected ArrayList<Range> parseRange(HttpServletRequest request,
+            HttpServletResponse response,
+            ResourceAttributes resourceAttributes) throws IOException {
+
+        // Checking If-Range
+        String headerValue = request.getHeader("If-Range");
+
+        if (headerValue != null) {
+
+            long headerValueTime = (-1L);
+            try {
+                headerValueTime = request.getDateHeader("If-Range");
+            } catch (IllegalArgumentException e) {
+                // Ignore
+            }
+
+            String eTag = resourceAttributes.getETag();
+            long lastModified = resourceAttributes.getLastModified();
+
+            if (headerValueTime == (-1L)) {
+
+                // If the ETag the client gave does not match the entity
+                // etag, then the entire entity is returned.
+                if (!eTag.equals(headerValue.trim()))
+                    return FULL;
+
+            } else {
+
+                // If the timestamp of the entity the client got is older than
+                // the last modification date of the entity, the entire entity
+                // is returned.
+                if (lastModified > (headerValueTime + 1000))
+                    return FULL;
+
+            }
+
+        }
+
+        long fileLength = resourceAttributes.getContentLength();
+
+        if (fileLength == 0)
+            return null;
+
+        // Retrieving the range header (if any is specified
+        String rangeHeader = request.getHeader("Range");
+
+        if (rangeHeader == null)
+            return null;
+        // bytes is the only range unit supported (and I don't see the point
+        // of adding new ones).
+        if (!rangeHeader.startsWith("bytes")) {
+            response.addHeader("Content-Range", "bytes */" + fileLength);
+            response.sendError
+                (HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
+            return null;
+        }
+
+        rangeHeader = rangeHeader.substring(6);
+
+        // Vector which will contain all the ranges which are successfully
+        // parsed.
+        ArrayList<Range> result = new ArrayList<Range>();
+        StringTokenizer commaTokenizer = new StringTokenizer(rangeHeader, ",");
+
+        // Parsing the range list
+        while (commaTokenizer.hasMoreTokens()) {
+            String rangeDefinition = commaTokenizer.nextToken().trim();
+
+            Range currentRange = new Range();
+            currentRange.length = fileLength;
+
+            int dashPos = rangeDefinition.indexOf('-');
+
+            if (dashPos == -1) {
+                response.addHeader("Content-Range", "bytes */" + fileLength);
+                response.sendError
+                    (HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
+                return null;
+            }
+
+            if (dashPos == 0) {
+
+                try {
+                    long offset = Long.parseLong(rangeDefinition);
+                    currentRange.start = fileLength + offset;
+                    currentRange.end = fileLength - 1;
+                } catch (NumberFormatException e) {
+                    response.addHeader("Content-Range",
+                                       "bytes */" + fileLength);
+                    response.sendError
+                        (HttpServletResponse
+                         .SC_REQUESTED_RANGE_NOT_SATISFIABLE);
+                    return null;
+                }
+
+            } else {
+
+                try {
+                    currentRange.start = Long.parseLong
+                        (rangeDefinition.substring(0, dashPos));
+                    if (dashPos < rangeDefinition.length() - 1)
+                        currentRange.end = Long.parseLong
+                            (rangeDefinition.substring
+                             (dashPos + 1, rangeDefinition.length()));
+                    else
+                        currentRange.end = fileLength - 1;
+                } catch (NumberFormatException e) {
+                    response.addHeader("Content-Range",
+                                       "bytes */" + fileLength);
+                    response.sendError
+                        (HttpServletResponse
+                         .SC_REQUESTED_RANGE_NOT_SATISFIABLE);
+                    return null;
+                }
+
+            }
+
+            if (!currentRange.validate()) {
+                response.addHeader("Content-Range", "bytes */" + fileLength);
+                response.sendError
+                    (HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
+                return null;
+            }
+
+            result.add(currentRange);
+        }
+
+        return result;
+    }
+
+
+
+    /**
+     *  Decide which way to render. HTML or XML.
+     */
+    protected InputStream render(String contextPath, CacheEntry cacheEntry)
+        throws IOException, ServletException {
+
+        InputStream xsltInputStream =
+            findXsltInputStream(cacheEntry.context);
+
+        if (xsltInputStream==null) {
+            return renderHtml(contextPath, cacheEntry);
+        }
+        return renderXml(contextPath, cacheEntry, xsltInputStream);
+        
+    }
+
+    /**
+     * Return an InputStream to an HTML representation of the contents
+     * of this directory.
+     *
+     * @param contextPath Context path to which our internal paths are
+     *  relative
+     */
+    protected InputStream renderXml(String contextPath,
+                                    CacheEntry cacheEntry,
+                                    InputStream xsltInputStream)
+        throws IOException, ServletException {
+
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("<?xml version=\"1.0\"?>");
+        sb.append("<listing ");
+        sb.append(" contextPath='");
+        sb.append(contextPath);
+        sb.append("'");
+        sb.append(" directory='");
+        sb.append(cacheEntry.name);
+        sb.append("' ");
+        sb.append(" hasParent='").append(!cacheEntry.name.equals("/"));
+        sb.append("'>");
+
+        sb.append("<entries>");
+
+        try {
+
+            // Render the directory entries within this directory
+            NamingEnumeration<NameClassPair> enumeration =
+                resources.list(cacheEntry.name);
+            
+            // rewriteUrl(contextPath) is expensive. cache result for later reuse
+            String rewrittenContextPath =  rewriteUrl(contextPath);
+
+            while (enumeration.hasMoreElements()) {
+
+                NameClassPair ncPair = enumeration.nextElement();
+                String resourceName = ncPair.getName();
+                String trimmed = resourceName/*.substring(trim)*/;
+                if (trimmed.equalsIgnoreCase("WEB-INF") ||
+                    trimmed.equalsIgnoreCase("META-INF") ||
+                    trimmed.equalsIgnoreCase(localXsltFile))
+                    continue;
+
+                if ((cacheEntry.name + trimmed).equals(contextXsltFile))
+                    continue;
+
+                CacheEntry childCacheEntry =
+                    resources.lookupCache(cacheEntry.name + resourceName);
+                if (!childCacheEntry.exists) {
+                    continue;
+                }
+
+                sb.append("<entry");
+                sb.append(" type='")
+                  .append((childCacheEntry.context != null)?"dir":"file")
+                  .append("'");
+                sb.append(" urlPath='")
+                  .append(rewrittenContextPath)
+                  .append(rewriteUrl(cacheEntry.name + resourceName))
+                  .append((childCacheEntry.context != null)?"/":"")
+                  .append("'");
+                if (childCacheEntry.resource != null) {
+                    sb.append(" size='")
+                      .append(renderSize(childCacheEntry.attributes.getContentLength()))
+                      .append("'");
+                }
+                sb.append(" date='")
+                  .append(childCacheEntry.attributes.getLastModifiedHttp())
+                  .append("'");
+
+                sb.append(">");
+                sb.append(RequestUtil.filter(trimmed));
+                if (childCacheEntry.context != null)
+                    sb.append("/");
+                sb.append("</entry>");
+
+            }
+
+        } catch (NamingException e) {
+            // Something went wrong
+            throw new ServletException("Error accessing resource", e);
+        }
+
+        sb.append("</entries>");
+
+        String readme = getReadme(cacheEntry.context);
+
+        if (readme!=null) {
+            sb.append("<readme><![CDATA[");
+            sb.append(readme);
+            sb.append("]]></readme>");
+        }
+
+
+        sb.append("</listing>");
+
+
+        try {
+            TransformerFactory tFactory = TransformerFactory.newInstance();
+            Source xmlSource = new StreamSource(new StringReader(sb.toString()));
+            Source xslSource = new StreamSource(xsltInputStream);
+            Transformer transformer = tFactory.newTransformer(xslSource);
+
+            ByteArrayOutputStream stream = new ByteArrayOutputStream();
+            OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
+            StreamResult out = new StreamResult(osWriter);
+            transformer.transform(xmlSource, out);
+            osWriter.flush();
+            return (new ByteArrayInputStream(stream.toByteArray()));
+        } catch (TransformerException e) {
+            throw new ServletException("XSL transformer error", e);
+        }
+    }
+
+    /**
+     * Return an InputStream to an HTML representation of the contents
+     * of this directory.
+     *
+     * @param contextPath Context path to which our internal paths are
+     *  relative
+     */
+    protected InputStream renderHtml(String contextPath, CacheEntry cacheEntry)
+        throws IOException, ServletException {
+
+        String name = cacheEntry.name;
+
+        // Number of characters to trim from the beginnings of filenames
+        int trim = name.length();
+        if (!name.endsWith("/"))
+            trim += 1;
+        if (name.equals("/"))
+            trim = 1;
+
+        // Prepare a writer to a buffered area
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
+        PrintWriter writer = new PrintWriter(osWriter);
+
+        StringBuilder sb = new StringBuilder();
+        
+        // rewriteUrl(contextPath) is expensive. cache result for later reuse
+        String rewrittenContextPath =  rewriteUrl(contextPath);
+
+        // Render the page header
+        sb.append("<html>\r\n");
+        sb.append("<head>\r\n");
+        sb.append("<title>");
+        sb.append(sm.getString("directory.title", name));
+        sb.append("</title>\r\n");
+        sb.append("<STYLE><!--");
+        sb.append(org.apache.catalina.util.TomcatCSS.TOMCAT_CSS);
+        sb.append("--></STYLE> ");
+        sb.append("</head>\r\n");
+        sb.append("<body>");
+        sb.append("<h1>");
+        sb.append(sm.getString("directory.title", name));
+
+        // Render the link to our parent (if required)
+        String parentDirectory = name;
+        if (parentDirectory.endsWith("/")) {
+            parentDirectory =
+                parentDirectory.substring(0, parentDirectory.length() - 1);
+        }
+        int slash = parentDirectory.lastIndexOf('/');
+        if (slash >= 0) {
+            String parent = name.substring(0, slash);
+            sb.append(" - <a href=\"");
+            sb.append(rewrittenContextPath);
+            if (parent.equals(""))
+                parent = "/";
+            sb.append(rewriteUrl(parent));
+            if (!parent.endsWith("/"))
+                sb.append("/");
+            sb.append("\">");
+            sb.append("<b>");
+            sb.append(sm.getString("directory.parent", parent));
+            sb.append("</b>");
+            sb.append("</a>");
+        }
+
+        sb.append("</h1>");
+        sb.append("<HR size=\"1\" noshade=\"noshade\">");
+
+        sb.append("<table width=\"100%\" cellspacing=\"0\"" +
+                     " cellpadding=\"5\" align=\"center\">\r\n");
+
+        // Render the column headings
+        sb.append("<tr>\r\n");
+        sb.append("<td align=\"left\"><font size=\"+1\"><strong>");
+        sb.append(sm.getString("directory.filename"));
+        sb.append("</strong></font></td>\r\n");
+        sb.append("<td align=\"center\"><font size=\"+1\"><strong>");
+        sb.append(sm.getString("directory.size"));
+        sb.append("</strong></font></td>\r\n");
+        sb.append("<td align=\"right\"><font size=\"+1\"><strong>");
+        sb.append(sm.getString("directory.lastModified"));
+        sb.append("</strong></font></td>\r\n");
+        sb.append("</tr>");
+
+        try {
+
+            // Render the directory entries within this directory
+            NamingEnumeration<NameClassPair> enumeration =
+                resources.list(cacheEntry.name);
+            boolean shade = false;
+            while (enumeration.hasMoreElements()) {
+
+                NameClassPair ncPair = enumeration.nextElement();
+                String resourceName = ncPair.getName();
+                String trimmed = resourceName/*.substring(trim)*/;
+                if (trimmed.equalsIgnoreCase("WEB-INF") ||
+                    trimmed.equalsIgnoreCase("META-INF"))
+                    continue;
+
+                CacheEntry childCacheEntry =
+                    resources.lookupCache(cacheEntry.name + resourceName);
+                if (!childCacheEntry.exists) {
+                    continue;
+                }
+
+                sb.append("<tr");
+                if (shade)
+                    sb.append(" bgcolor=\"#eeeeee\"");
+                sb.append(">\r\n");
+                shade = !shade;
+
+                sb.append("<td align=\"left\">&nbsp;&nbsp;\r\n");
+                sb.append("<a href=\"");
+                sb.append(rewrittenContextPath);
+                resourceName = rewriteUrl(name + resourceName);
+                sb.append(resourceName);
+                if (childCacheEntry.context != null)
+                    sb.append("/");
+                sb.append("\"><tt>");
+                sb.append(RequestUtil.filter(trimmed));
+                if (childCacheEntry.context != null)
+                    sb.append("/");
+                sb.append("</tt></a></td>\r\n");
+
+                sb.append("<td align=\"right\"><tt>");
+                if (childCacheEntry.context != null)
+                    sb.append("&nbsp;");
+                else
+                    sb.append(renderSize(childCacheEntry.attributes.getContentLength()));
+                sb.append("</tt></td>\r\n");
+
+                sb.append("<td align=\"right\"><tt>");
+                sb.append(childCacheEntry.attributes.getLastModifiedHttp());
+                sb.append("</tt></td>\r\n");
+
+                sb.append("</tr>\r\n");
+            }
+
+        } catch (NamingException e) {
+            // Something went wrong
+            throw new ServletException("Error accessing resource", e);
+        }
+
+        // Render the page footer
+        sb.append("</table>\r\n");
+
+        sb.append("<HR size=\"1\" noshade=\"noshade\">");
+
+        String readme = getReadme(cacheEntry.context);
+        if (readme!=null) {
+            sb.append(readme);
+            sb.append("<HR size=\"1\" noshade=\"noshade\">");
+        }
+
+        sb.append("<h3>").append(ServerInfo.getServerInfo()).append("</h3>");
+        sb.append("</body>\r\n");
+        sb.append("</html>\r\n");
+
+        // Return an input stream to the underlying bytes
+        writer.write(sb.toString());
+        writer.flush();
+        return (new ByteArrayInputStream(stream.toByteArray()));
+
+    }
+
+
+    /**
+     * Render the specified file size (in bytes).
+     *
+     * @param size File size (in bytes)
+     */
+    protected String renderSize(long size) {
+
+        long leftSide = size / 1024;
+        long rightSide = (size % 1024) / 103;   // Makes 1 digit
+        if ((leftSide == 0) && (rightSide == 0) && (size > 0))
+            rightSide = 1;
+
+        return ("" + leftSide + "." + rightSide + " kb");
+
+    }
+
+
+    /**
+     * Get the readme file as a string.
+     */
+    protected String getReadme(DirContext directory)
+        throws IOException {
+
+        if (readmeFile != null) {
+            try {
+                Object obj = directory.lookup(readmeFile);
+                if ((obj != null) && (obj instanceof Resource)) {
+                    StringWriter buffer = new StringWriter();
+                    InputStream is = ((Resource) obj).streamContent();
+                    copyRange(new InputStreamReader(is),
+                            new PrintWriter(buffer));
+                    return buffer.toString();
+                }
+            } catch (NamingException e) {
+                if (debug > 10)
+                    log("readme '" + readmeFile + "' not found", e);
+
+                return null;
+            }
+        }
+
+        return null;
+    }
+
+
+    /**
+     * Return the xsl template inputstream (if possible)
+     */
+    protected InputStream findXsltInputStream(DirContext directory)
+        throws IOException {
+
+        if (localXsltFile != null) {
+            try {
+                Object obj = directory.lookup(localXsltFile);
+                if ((obj != null) && (obj instanceof Resource)) {
+                    InputStream is = ((Resource) obj).streamContent();
+                    if (is != null)
+                        return is;
+                }
+            } catch (NamingException e) {
+                if (debug > 10)
+                    log("localXsltFile '" + localXsltFile + "' not found", e);
+            }
+        }
+
+        if (contextXsltFile != null) {
+            InputStream is =
+                getServletContext().getResourceAsStream(contextXsltFile);
+            if (is != null)
+                return is;
+
+            if (debug > 10)
+                log("contextXsltFile '" + contextXsltFile + "' not found");
+        }
+
+        /*  Open and read in file in one fell swoop to reduce chance
+         *  chance of leaving handle open.
+         */
+        if (globalXsltFile!=null) {
+            FileInputStream fis = null;
+
+            try {
+                File f = new File(globalXsltFile);
+                if (f.exists()){
+                    fis =new FileInputStream(f);
+                    byte b[] = new byte[(int)f.length()]; /* danger! */
+                    fis.read(b);
+                    return new ByteArrayInputStream(b);
+                }
+            } finally {
+                if (fis!=null)
+                    fis.close();
+            }
+        }
+
+        return null;
+
+    }
+
+
+    // -------------------------------------------------------- protected Methods
+
+
+    /**
+     * Check if sendfile can be used.
+     */
+    protected boolean checkSendfile(HttpServletRequest request,
+                                  HttpServletResponse response,
+                                  CacheEntry entry,
+                                  long length, Range range) {
+        if ((sendfileSize > 0)
+            && (entry.resource != null)
+            && ((length > sendfileSize) || (entry.resource.getContent() == null))
+            && (entry.attributes.getCanonicalPath() != null)
+            && (Boolean.TRUE == request.getAttribute("org.apache.tomcat.sendfile.support"))
+            && (request.getClass().getName().equals("org.apache.catalina.connector.RequestFacade"))
+            && (response.getClass().getName().equals("org.apache.catalina.connector.ResponseFacade"))) {
+            request.setAttribute("org.apache.tomcat.sendfile.filename", entry.attributes.getCanonicalPath());
+            if (range == null) {
+                request.setAttribute("org.apache.tomcat.sendfile.start", Long.valueOf(0L));
+                request.setAttribute("org.apache.tomcat.sendfile.end", Long.valueOf(length));
+            } else {
+                request.setAttribute("org.apache.tomcat.sendfile.start", Long.valueOf(range.start));
+                request.setAttribute("org.apache.tomcat.sendfile.end", Long.valueOf(range.end + 1));
+            }
+            request.setAttribute("org.apache.tomcat.sendfile.token", this);
+            return true;
+        }
+        return false;
+    }
+    
+    
+    /**
+     * Check if the if-match condition is satisfied.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     * @param resourceAttributes File object
+     * @return boolean true if the resource meets the specified condition,
+     * and false if the condition is not satisfied, in which case request
+     * processing is stopped
+     */
+    protected boolean checkIfMatch(HttpServletRequest request,
+                                 HttpServletResponse response,
+                                 ResourceAttributes resourceAttributes)
+        throws IOException {
+
+        String eTag = resourceAttributes.getETag();
+        String headerValue = request.getHeader("If-Match");
+        if (headerValue != null) {
+            if (headerValue.indexOf('*') == -1) {
+
+                StringTokenizer commaTokenizer = new StringTokenizer
+                    (headerValue, ",");
+                boolean conditionSatisfied = false;
+
+                while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
+                    String currentToken = commaTokenizer.nextToken();
+                    if (currentToken.trim().equals(eTag))
+                        conditionSatisfied = true;
+                }
+
+                // If none of the given ETags match, 412 Precodition failed is
+                // sent back
+                if (!conditionSatisfied) {
+                    response.sendError
+                        (HttpServletResponse.SC_PRECONDITION_FAILED);
+                    return false;
+                }
+
+            }
+        }
+        return true;
+
+    }
+
+
+    /**
+     * Check if the if-modified-since condition is satisfied.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     * @param resourceAttributes File object
+     * @return boolean true if the resource meets the specified condition,
+     * and false if the condition is not satisfied, in which case request
+     * processing is stopped
+     */
+    protected boolean checkIfModifiedSince(HttpServletRequest request,
+            HttpServletResponse response,
+            ResourceAttributes resourceAttributes) {
+        try {
+            long headerValue = request.getDateHeader("If-Modified-Since");
+            long lastModified = resourceAttributes.getLastModified();
+            if (headerValue != -1) {
+
+                // If an If-None-Match header has been specified, if modified since
+                // is ignored.
+                if ((request.getHeader("If-None-Match") == null)
+                    && (lastModified < headerValue + 1000)) {
+                    // The entity has not been modified since the date
+                    // specified by the client. This is not an error case.
+                    response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                    response.setHeader("ETag", resourceAttributes.getETag());
+
+                    return false;
+                }
+            }
+        } catch (IllegalArgumentException illegalArgument) {
+            return true;
+        }
+        return true;
+
+    }
+
+
+    /**
+     * Check if the if-none-match condition is satisfied.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     * @param resourceAttributes File object
+     * @return boolean true if the resource meets the specified condition,
+     * and false if the condition is not satisfied, in which case request
+     * processing is stopped
+     */
+    protected boolean checkIfNoneMatch(HttpServletRequest request,
+                                     HttpServletResponse response,
+                                     ResourceAttributes resourceAttributes)
+        throws IOException {
+
+        String eTag = resourceAttributes.getETag();
+        String headerValue = request.getHeader("If-None-Match");
+        if (headerValue != null) {
+
+            boolean conditionSatisfied = false;
+
+            if (!headerValue.equals("*")) {
+
+                StringTokenizer commaTokenizer =
+                    new StringTokenizer(headerValue, ",");
+
+                while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
+                    String currentToken = commaTokenizer.nextToken();
+                    if (currentToken.trim().equals(eTag))
+                        conditionSatisfied = true;
+                }
+
+            } else {
+                conditionSatisfied = true;
+            }
+
+            if (conditionSatisfied) {
+
+                // For GET and HEAD, we should respond with
+                // 304 Not Modified.
+                // For every other method, 412 Precondition Failed is sent
+                // back.
+                if ( ("GET".equals(request.getMethod()))
+                     || ("HEAD".equals(request.getMethod())) ) {
+                    response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                    response.setHeader("ETag", eTag);
+
+                    return false;
+                }
+                response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+                return false;
+            }
+        }
+        return true;
+
+    }
+
+
+    /**
+     * Check if the if-unmodified-since condition is satisfied.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     * @param resourceAttributes File object
+     * @return boolean true if the resource meets the specified condition,
+     * and false if the condition is not satisfied, in which case request
+     * processing is stopped
+     */
+    protected boolean checkIfUnmodifiedSince(HttpServletRequest request,
+                                           HttpServletResponse response,
+                                           ResourceAttributes resourceAttributes)
+        throws IOException {
+        try {
+            long lastModified = resourceAttributes.getLastModified();
+            long headerValue = request.getDateHeader("If-Unmodified-Since");
+            if (headerValue != -1) {
+                if ( lastModified >= (headerValue + 1000)) {
+                    // The entity has not been modified since the date
+                    // specified by the client. This is not an error case.
+                    response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+                    return false;
+                }
+            }
+        } catch(IllegalArgumentException illegalArgument) {
+            return true;
+        }
+        return true;
+
+    }
+
+
+    /**
+     * Copy the contents of the specified input stream to the specified
+     * output stream, and ensure that both streams are closed before returning
+     * (even in the face of an exception).
+     *
+     * @param cacheEntry The cache entry for the source resource
+     * @param is The input stream to read the source resource from   
+     * @param ostream The output stream to write to
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    protected void copy(CacheEntry cacheEntry, InputStream is,
+                      ServletOutputStream ostream)
+        throws IOException {
+
+        IOException exception = null;
+        InputStream resourceInputStream = null;
+
+        // Optimization: If the binary content has already been loaded, send
+        // it directly
+        if (cacheEntry.resource != null) {
+            byte buffer[] = cacheEntry.resource.getContent();
+            if (buffer != null) {
+                ostream.write(buffer, 0, buffer.length);
+                return;
+            }
+            resourceInputStream = cacheEntry.resource.streamContent();
+        } else {
+            resourceInputStream = is;
+        }
+
+        InputStream istream = new BufferedInputStream
+            (resourceInputStream, input);
+
+        // Copy the input stream to the output stream
+        exception = copyRange(istream, ostream);
+
+        // Clean up the input stream
+        istream.close();
+
+        // Rethrow any exception that has occurred
+        if (exception != null)
+            throw exception;
+
+    }
+
+
+    /**
+     * Copy the contents of the specified input stream to the specified
+     * output stream, and ensure that both streams are closed before returning
+     * (even in the face of an exception).
+     *
+     * @param cacheEntry The cache entry for the source resource
+     * @param is The input stream to read the source resource from   
+     * @param writer The writer to write to
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    protected void copy(CacheEntry cacheEntry, InputStream is, PrintWriter writer)
+        throws IOException {
+
+        IOException exception = null;
+
+        InputStream resourceInputStream = null;
+        if (cacheEntry.resource != null) {
+            resourceInputStream = cacheEntry.resource.streamContent();
+        } else {
+            resourceInputStream = is;
+        }
+
+        Reader reader;
+        if (fileEncoding == null) {
+            reader = new InputStreamReader(resourceInputStream);
+        } else {
+            reader = new InputStreamReader(resourceInputStream,
+                                           fileEncoding);
+        }
+
+        // Copy the input stream to the output stream
+        exception = copyRange(reader, writer);
+
+        // Clean up the reader
+        reader.close();
+
+        // Rethrow any exception that has occurred
+        if (exception != null)
+            throw exception;
+
+    }
+
+
+    /**
+     * Copy the contents of the specified input stream to the specified
+     * output stream, and ensure that both streams are closed before returning
+     * (even in the face of an exception).
+     *
+     * @param cacheEntry The cache entry for the source resource
+     * @param ostream The output stream to write to
+     * @param range Range the client wanted to retrieve
+     * @exception IOException if an input/output error occurs
+     */
+    protected void copy(CacheEntry cacheEntry, ServletOutputStream ostream,
+                      Range range)
+        throws IOException {
+
+        IOException exception = null;
+
+        InputStream resourceInputStream = cacheEntry.resource.streamContent();
+        InputStream istream =
+            new BufferedInputStream(resourceInputStream, input);
+        exception = copyRange(istream, ostream, range.start, range.end);
+
+        // Clean up the input stream
+        istream.close();
+
+        // Rethrow any exception that has occurred
+        if (exception != null)
+            throw exception;
+
+    }
+
+
+    /**
+     * Copy the contents of the specified input stream to the specified
+     * output stream, and ensure that both streams are closed before returning
+     * (even in the face of an exception).
+     *
+     * @param cacheEntry The cache entry for the source resource
+     * @param ostream The output stream to write to
+     * @param ranges Enumeration of the ranges the client wanted to retrieve
+     * @param contentType Content type of the resource
+     * @exception IOException if an input/output error occurs
+     */
+    protected void copy(CacheEntry cacheEntry, ServletOutputStream ostream,
+                      Iterator<Range> ranges, String contentType)
+        throws IOException {
+
+        IOException exception = null;
+
+        while ( (exception == null) && (ranges.hasNext()) ) {
+
+            InputStream resourceInputStream = cacheEntry.resource.streamContent();
+            InputStream istream =
+                new BufferedInputStream(resourceInputStream, input);
+
+            Range currentRange = ranges.next();
+
+            // Writing MIME header.
+            ostream.println();
+            ostream.println("--" + mimeSeparation);
+            if (contentType != null)
+                ostream.println("Content-Type: " + contentType);
+            ostream.println("Content-Range: bytes " + currentRange.start
+                           + "-" + currentRange.end + "/"
+                           + currentRange.length);
+            ostream.println();
+
+            // Printing content
+            exception = copyRange(istream, ostream, currentRange.start,
+                                  currentRange.end);
+
+            istream.close();
+
+        }
+
+        ostream.println();
+        ostream.print("--" + mimeSeparation + "--");
+
+        // Rethrow any exception that has occurred
+        if (exception != null)
+            throw exception;
+
+    }
+
+
+    /**
+     * Copy the contents of the specified input stream to the specified
+     * output stream, and ensure that both streams are closed before returning
+     * (even in the face of an exception).
+     *
+     * @param istream The input stream to read from
+     * @param ostream The output stream to write to
+     * @return Exception which occurred during processing
+     */
+    protected IOException copyRange(InputStream istream,
+                                  ServletOutputStream ostream) {
+
+        // Copy the input stream to the output stream
+        IOException exception = null;
+        byte buffer[] = new byte[input];
+        int len = buffer.length;
+        while (true) {
+            try {
+                len = istream.read(buffer);
+                if (len == -1)
+                    break;
+                ostream.write(buffer, 0, len);
+            } catch (IOException e) {
+                exception = e;
+                len = -1;
+                break;
+            }
+        }
+        return exception;
+
+    }
+
+
+    /**
+     * Copy the contents of the specified input stream to the specified
+     * output stream, and ensure that both streams are closed before returning
+     * (even in the face of an exception).
+     *
+     * @param reader The reader to read from
+     * @param writer The writer to write to
+     * @return Exception which occurred during processing
+     */
+    protected IOException copyRange(Reader reader, PrintWriter writer) {
+
+        // Copy the input stream to the output stream
+        IOException exception = null;
+        char buffer[] = new char[input];
+        int len = buffer.length;
+        while (true) {
+            try {
+                len = reader.read(buffer);
+                if (len == -1)
+                    break;
+                writer.write(buffer, 0, len);
+            } catch (IOException e) {
+                exception = e;
+                len = -1;
+                break;
+            }
+        }
+        return exception;
+
+    }
+
+
+    /**
+     * Copy the contents of the specified input stream to the specified
+     * output stream, and ensure that both streams are closed before returning
+     * (even in the face of an exception).
+     *
+     * @param istream The input stream to read from
+     * @param ostream The output stream to write to
+     * @param start Start of the range which will be copied
+     * @param end End of the range which will be copied
+     * @return Exception which occurred during processing
+     */
+    protected IOException copyRange(InputStream istream,
+                                  ServletOutputStream ostream,
+                                  long start, long end) {
+
+        if (debug > 10)
+            log("Serving bytes:" + start + "-" + end);
+
+        long skipped = 0;
+        try {
+            skipped = istream.skip(start);
+        } catch (IOException e) {
+            return e;
+        }
+        if (skipped < start) {
+            return new IOException(sm.getString("defaultservlet.skipfail",
+                    Long.valueOf(skipped), Long.valueOf(start)));
+        }
+        
+        IOException exception = null;
+        long bytesToRead = end - start + 1;
+
+        byte buffer[] = new byte[input];
+        int len = buffer.length;
+        while ( (bytesToRead > 0) && (len >= buffer.length)) {
+            try {
+                len = istream.read(buffer);
+                if (bytesToRead >= len) {
+                    ostream.write(buffer, 0, len);
+                    bytesToRead -= len;
+                } else {
+                    ostream.write(buffer, 0, (int) bytesToRead);
+                    bytesToRead = 0;
+                }
+            } catch (IOException e) {
+                exception = e;
+                len = -1;
+            }
+            if (len < buffer.length)
+                break;
+        }
+
+        return exception;
+
+    }
+
+
+    // ------------------------------------------------------ Range Inner Class
+
+
+    protected static class Range {
+
+        public long start;
+        public long end;
+        public long length;
+
+        /**
+         * Validate range.
+         */
+        public boolean validate() {
+            if (end >= length)
+                end = length - 1;
+            return (start >= 0) && (end >= 0) && (start <= end) && (length > 0);
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/LocalStrings.properties
new file mode 100644
index 0000000..47e8d16
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/LocalStrings.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.
+
+defaultServlet.missingResource=The requested resource ({0}) is not available
+defaultservlet.directorylistingfor=Directory Listing for:
+defaultservlet.upto=Up to:
+defaultservlet.subdirectories=Subdirectories:
+defaultservlet.files=Files:
+defaultservlet.skipfail=Only skipped [{0}] bytes when [{1}] were requested
+webdavservlet.jaxpfailed=JAXP initialization failed
+webdavservlet.enternalEntityIgnored=The request included a reference to an external entity with PublicID {0} and SystemID {1} which was ignored
+directory.filename=Filename
+directory.lastModified=Last Modified
+directory.parent=Up To {0}
+directory.size=Size
+directory.title=Directory Listing For {0}
+directory.version=Tomcat Catalina version 4.0
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/LocalStrings_es.properties
new file mode 100644
index 0000000..c125c28
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/LocalStrings_es.properties
@@ -0,0 +1,27 @@
+defaultServlet.missingResource = El recurso requerido {0} no se encuentra disponible
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+defaultservlet.directorylistingfor = Listado de Directorio para\:
+defaultservlet.upto = Atr\u00E1s a\:
+defaultservlet.subdirectories = Subdirectorios\:
+defaultservlet.files = Archivos\:
+webdavservlet.jaxpfailed = Fall\u00F3 la inicializaci\u00F3n de JAXP
+webdavservlet.enternalEntityIgnored = El requerimiento inclu\u00EDa una referencia a una entidad externa con PublicID {0} y SystemID {1} que fue ignorada
+directory.filename = Nombre de Archivo
+directory.lastModified = \u00DAltima Modificaci\u00F3n
+directory.parent = Atr\u00E1s A {0}
+directory.size = Medida
+directory.title = Listado de Directorio Para {0}
+directory.version = Tomcat Catalina versi\u00F3n 4.0
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/LocalStrings_fr.properties
new file mode 100644
index 0000000..f64d887
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/LocalStrings_fr.properties
@@ -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.
+
+defaultservlet.directorylistingfor=Liste du r\u00e9pertoire pour :
+defaultservlet.upto=Jusqu''\u00e0:
+defaultservlet.subdirectories=Sous-r\u00e9pertoires:
+defaultservlet.files=Fichiers:
+webdavservlet.jaxpfailed=Erreur d''initialisation de JAXP
+directory.filename=Nom de fichier
+directory.lastModified=Derni\u00e8re modification
+directory.parent=Jusqu''\u00e0 {0}
+directory.size=Taille
+directory.title=Liste du r\u00e9pertoire pour {0}
+directory.version=Tomcat Catalina version 4.0
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/LocalStrings_ja.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/LocalStrings_ja.properties
new file mode 100644
index 0000000..5772ae5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/LocalStrings_ja.properties
@@ -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.
+
+defaultservlet.directorylistingfor=\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306e\u4e00\u89a7: 
+defaultservlet.upto=\u89aa\u30c7\u30a3\u30ec\u30af\u30c8\u30ea: 
+defaultservlet.subdirectories=\u30b5\u30d6\u30c7\u30a3\u30ec\u30af\u30c8\u30ea:
+defaultservlet.files=\u30d5\u30a1\u30a4\u30eb:
+webdavservlet.jaxpfailed=JAXP\u306e\u521d\u671f\u5316\u306b\u5931\u6557\u3057\u307e\u3057\u305f
+directory.filename=\u30d5\u30a1\u30a4\u30eb\u540d
+directory.lastModified=\u6700\u7d42\u66f4\u65b0
+directory.parent={0} \u306b\u79fb\u52d5
+directory.size=\u30b5\u30a4\u30ba
+directory.title={0} \u306e\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306e\u4e00\u89a7
+directory.version=Tomcat Catalina \u30d0\u30fc\u30b8\u30e7\u30f3 4.0
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/WebdavServlet.java b/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/WebdavServlet.java
new file mode 100644
index 0000000..0b6eb64
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/WebdavServlet.java
@@ -0,0 +1,3142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.servlets;
+
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Locale;
+import java.util.Stack;
+import java.util.TimeZone;
+import java.util.Vector;
+
+import javax.naming.NameClassPair;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.catalina.util.DOMWriter;
+import org.apache.catalina.util.MD5Encoder;
+import org.apache.catalina.util.RequestUtil;
+import org.apache.catalina.util.XMLWriter;
+import org.apache.naming.resources.CacheEntry;
+import org.apache.naming.resources.Resource;
+import org.apache.naming.resources.ResourceAttributes;
+import org.apache.tomcat.util.http.FastHttpDateFormat;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+
+
+/**
+ * Servlet which adds support for WebDAV level 2. All the basic HTTP requests
+ * are handled by the DefaultServlet. The WebDAVServlet must not be used as the
+ * default servlet (ie mapped to '/') as it will not work in this configuration.
+ * <p/>
+ * Mapping a subpath (e.g. <code>/webdav/*</code> to this servlet has the effect
+ * of re-mounting the entire web application under that sub-path, with WebDAV
+ * access to all the resources. This <code>WEB-INF</code> and <code>META-INF</code>
+ * directories are protected in this re-mounted resource tree.
+ * <p/>
+ * To enable WebDAV for a context add the following to web.xml:
+ * <pre>
+ * &lt;servlet&gt;
+ *  &lt;servlet-name&gt;webdav&lt;/servlet-name&gt;
+ *  &lt;servlet-class&gt;org.apache.catalina.servlets.WebdavServlet&lt;/servlet-class&gt;
+ *    &lt;init-param&gt;
+ *      &lt;param-name&gt;debug&lt;/param-name&gt;
+ *      &lt;param-value&gt;0&lt;/param-value&gt;
+ *    &lt;/init-param&gt;
+ *    &lt;init-param&gt;
+ *      &lt;param-name&gt;listings&lt;/param-name&gt;
+ *      &lt;param-value&gt;false&lt;/param-value&gt;
+ *    &lt;/init-param&gt;
+ *  &lt;/servlet&gt;
+ *  &lt;servlet-mapping&gt;
+ *    &lt;servlet-name&gt;webdav&lt;/servlet-name&gt;
+ *    &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
+ *  &lt;/servlet-mapping&gt;
+ * </pre>
+ * This will enable read only access. To enable read-write access add:
+ * <pre>
+ *  &lt;init-param&gt;
+ *    &lt;param-name&gt;readonly&lt;/param-name&gt;
+ *    &lt;param-value&gt;false&lt;/param-value&gt;
+ *  &lt;/init-param&gt;
+ * </pre>
+ * To make the content editable via a different URL, use the following
+ * mapping:
+ * <pre>
+ *  &lt;servlet-mapping&gt;
+ *    &lt;servlet-name&gt;webdav&lt;/servlet-name&gt;
+ *    &lt;url-pattern&gt;/webdavedit/*&lt;/url-pattern&gt;
+ *  &lt;/servlet-mapping&gt;
+ * </pre>
+ * By default access to /WEB-INF and META-INF are not available via WebDAV. To
+ * enable access to these URLs, use add:
+ * <pre>
+ *  &lt;init-param&gt;
+ *    &lt;param-name&gt;allowSpecialPaths&lt;/param-name&gt;
+ *    &lt;param-value&gt;true&lt;/param-value&gt;
+ *  &lt;/init-param&gt;
+ * </pre>
+ * Don't forget to secure access appropriately to the editing URLs, especially
+ * if allowSpecialPaths is used. With the mapping configuration above, the
+ * context will be accessible to normal users as before. Those users with the
+ * necessary access will be able to edit content available via
+ * http://host:port/context/content using
+ * http://host:port/context/webdavedit/content
+ *
+ * @author Remy Maucherat
+ * @version $Id: WebdavServlet.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ */
+
+public class WebdavServlet
+    extends DefaultServlet {
+
+    private static final long serialVersionUID = 1L;
+
+
+    // -------------------------------------------------------------- Constants
+
+    private static final String METHOD_PROPFIND = "PROPFIND";
+    private static final String METHOD_PROPPATCH = "PROPPATCH";
+    private static final String METHOD_MKCOL = "MKCOL";
+    private static final String METHOD_COPY = "COPY";
+    private static final String METHOD_MOVE = "MOVE";
+    private static final String METHOD_LOCK = "LOCK";
+    private static final String METHOD_UNLOCK = "UNLOCK";
+
+
+    /**
+     * PROPFIND - Specify a property mask.
+     */
+    private static final int FIND_BY_PROPERTY = 0;
+
+
+    /**
+     * PROPFIND - Display all properties.
+     */
+    private static final int FIND_ALL_PROP = 1;
+
+
+    /**
+     * PROPFIND - Return property names.
+     */
+    private static final int FIND_PROPERTY_NAMES = 2;
+
+
+    /**
+     * Create a new lock.
+     */
+    private static final int LOCK_CREATION = 0;
+
+
+    /**
+     * Refresh lock.
+     */
+    private static final int LOCK_REFRESH = 1;
+
+
+    /**
+     * Default lock timeout value.
+     */
+    private static final int DEFAULT_TIMEOUT = 3600;
+
+
+    /**
+     * Maximum lock timeout.
+     */
+    private static final int MAX_TIMEOUT = 604800;
+
+
+    /**
+     * Default namespace.
+     */
+    protected static final String DEFAULT_NAMESPACE = "DAV:";
+
+
+    /**
+     * Simple date format for the creation date ISO representation (partial).
+     */
+    protected static final SimpleDateFormat creationDateFormat =
+        new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+
+
+     /**
+     * MD5 message digest provider.
+     */
+    protected static MessageDigest md5Helper;
+
+
+    /**
+     * The MD5 helper object for this class.
+     */
+    protected static final MD5Encoder md5Encoder = new MD5Encoder();
+
+
+
+    static {
+        creationDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Repository of the locks put on single resources.
+     * <p>
+     * Key : path <br>
+     * Value : LockInfo
+     */
+    private Hashtable<String,LockInfo> resourceLocks =
+        new Hashtable<String,LockInfo>();
+
+
+    /**
+     * Repository of the lock-null resources.
+     * <p>
+     * Key : path of the collection containing the lock-null resource<br>
+     * Value : Vector of lock-null resource which are members of the
+     * collection. Each element of the Vector is the path associated with
+     * the lock-null resource.
+     */
+    private Hashtable<String,Vector<String>> lockNullResources =
+        new Hashtable<String,Vector<String>>();
+
+
+    /**
+     * Vector of the heritable locks.
+     * <p>
+     * Key : path <br>
+     * Value : LockInfo
+     */
+    private Vector<LockInfo> collectionLocks = new Vector<LockInfo>();
+
+
+    /**
+     * Secret information used to generate reasonably secure lock ids.
+     */
+    private String secret = "catalina";
+
+
+    /**
+     * Default depth in spec is infinite. Limit depth to 3 by default as
+     * infinite depth makes operations very expensive.
+     */
+    private int maxDepth = 3;
+
+
+    /**
+     * Is access allowed via WebDAV to the special paths (/WEB-INF and
+     * /META-INF)? 
+     */
+    private boolean allowSpecialPaths = false;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Initialize this servlet.
+     */
+    @Override
+    public void init()
+        throws ServletException {
+
+        super.init();
+
+        if (getServletConfig().getInitParameter("secret") != null)
+            secret = getServletConfig().getInitParameter("secret");
+
+        if (getServletConfig().getInitParameter("maxDepth") != null)
+            maxDepth = Integer.parseInt(
+                    getServletConfig().getInitParameter("maxDepth"));
+
+        if (getServletConfig().getInitParameter("allowSpecialPaths") != null)
+            allowSpecialPaths = Boolean.parseBoolean(
+                    getServletConfig().getInitParameter("allowSpecialPaths"));
+
+        // Load the MD5 helper used to calculate signatures.
+        try {
+            md5Helper = MessageDigest.getInstance("MD5");
+        } catch (NoSuchAlgorithmException e) {
+            throw new UnavailableException("No MD5");
+        }
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Return JAXP document builder instance.
+     */
+    protected DocumentBuilder getDocumentBuilder()
+        throws ServletException {
+        DocumentBuilder documentBuilder = null;
+        DocumentBuilderFactory documentBuilderFactory = null;
+        try {
+            documentBuilderFactory = DocumentBuilderFactory.newInstance();
+            documentBuilderFactory.setNamespaceAware(true);
+            documentBuilderFactory.setExpandEntityReferences(false);
+            documentBuilder = documentBuilderFactory.newDocumentBuilder();
+            documentBuilder.setEntityResolver(
+                    new WebdavResolver(this.getServletContext()));
+        } catch(ParserConfigurationException e) {
+            throw new ServletException
+                (sm.getString("webdavservlet.jaxpfailed"));
+        }
+        return documentBuilder;
+    }
+
+
+    /**
+     * Handles the special WebDAV methods.
+     */
+    @Override
+    protected void service(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        final String path = getRelativePath(req);
+        
+        // Block access to special subdirectories.
+        // DefaultServlet assumes it services resources from the root of the web app
+        // and doesn't add any special path protection
+        // WebdavServlet remounts the webapp under a new path, so this check is
+        // necessary on all methods (including GET).
+        if (isSpecialPath(path)) {
+            resp.sendError(WebdavStatus.SC_NOT_FOUND);
+            return;
+        }
+
+        final String method = req.getMethod();
+
+        if (debug > 0) {
+            log("[" + method + "] " + path);
+        }
+
+        if (method.equals(METHOD_PROPFIND)) {
+            doPropfind(req, resp);
+        } else if (method.equals(METHOD_PROPPATCH)) {
+            doProppatch(req, resp);
+        } else if (method.equals(METHOD_MKCOL)) {
+            doMkcol(req, resp);
+        } else if (method.equals(METHOD_COPY)) {
+            doCopy(req, resp);
+        } else if (method.equals(METHOD_MOVE)) {
+            doMove(req, resp);
+        } else if (method.equals(METHOD_LOCK)) {
+            doLock(req, resp);
+        } else if (method.equals(METHOD_UNLOCK)) {
+            doUnlock(req, resp);
+        } else {
+            // DefaultServlet processing
+            super.service(req, resp);
+        }
+
+    }
+
+
+    /**
+     * Checks whether a given path refers to a resource under
+     * <code>WEB-INF</code> or <code>META-INF</code>.
+     * @param path the full path of the resource being accessed
+     * @return <code>true</code> if the resource specified is under a special path
+     */
+    private final boolean isSpecialPath(final String path) {
+        return !allowSpecialPaths && (
+                path.toUpperCase(Locale.ENGLISH).startsWith("/WEB-INF") ||
+                path.toUpperCase(Locale.ENGLISH).startsWith("/META-INF"));
+    }
+
+
+    /**
+     * Check if the conditions specified in the optional If headers are
+     * satisfied.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     * @param resourceAttributes The resource information
+     * @return boolean true if the resource meets all the specified conditions,
+     * and false if any of the conditions is not satisfied, in which case
+     * request processing is stopped
+     */
+    @Override
+    protected boolean checkIfHeaders(HttpServletRequest request,
+                                     HttpServletResponse response,
+                                     ResourceAttributes resourceAttributes)
+        throws IOException {
+
+        if (!super.checkIfHeaders(request, response, resourceAttributes))
+            return false;
+
+        // TODO : Checking the WebDAV If header
+        return true;
+
+    }
+
+
+    /**
+     * Override the DefaultServlet implementation and only use the PathInfo. If
+     * the ServletPath is non-null, it will be because the WebDAV servlet has
+     * been mapped to a url other than /* to configure editing at different url
+     * than normal viewing.
+     *
+     * @param request The servlet request we are processing
+     */
+    @Override
+    protected 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.equals("")))
+                result = "/";
+            return (result);
+        }
+
+        // No, extract the desired path directly from the request
+        String result = request.getPathInfo();
+        if ((result == null) || (result.equals(""))) {
+            result = "/";
+        }
+        return (result);
+
+    }
+
+
+    /**
+     * Determines the prefix for standard directory GET listings.
+     */
+    @Override
+    protected String getPathPrefix(final HttpServletRequest request) {
+        // Repeat the servlet path (e.g. /webdav/) in the listing path
+        String contextPath = request.getContextPath();
+        if (request.getServletPath() !=  null) {
+            contextPath = contextPath + request.getServletPath();
+        }
+        return contextPath;
+    }
+
+
+    /**
+     * OPTIONS Method.
+     *
+     * @param req The request
+     * @param resp The response
+     * @throws ServletException If an error occurs
+     * @throws IOException If an IO error occurs
+     */
+    @Override
+    protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        resp.addHeader("DAV", "1,2");
+
+        StringBuilder methodsAllowed = determineMethodsAllowed(resources,
+                                                              req);
+
+        resp.addHeader("Allow", methodsAllowed.toString());
+        resp.addHeader("MS-Author-Via", "DAV");
+
+    }
+
+
+    /**
+     * PROPFIND Method.
+     */
+    protected void doPropfind(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        if (!listings) {
+            // Get allowed methods
+            StringBuilder methodsAllowed = determineMethodsAllowed(resources,
+                                                                  req);
+
+            resp.addHeader("Allow", methodsAllowed.toString());
+            resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
+            return;
+        }
+
+        String path = getRelativePath(req);
+        if (path.endsWith("/"))
+            path = path.substring(0, path.length() - 1);
+
+        // Properties which are to be displayed.
+        Vector<String> properties = null;
+        // Propfind depth
+        int depth = maxDepth;
+        // Propfind type
+        int type = FIND_ALL_PROP;
+
+        String depthStr = req.getHeader("Depth");
+
+        if (depthStr == null) {
+            depth = maxDepth;
+        } else {
+            if (depthStr.equals("0")) {
+                depth = 0;
+            } else if (depthStr.equals("1")) {
+                depth = 1;
+            } else if (depthStr.equals("infinity")) {
+                depth = maxDepth;
+            }
+        }
+
+        Node propNode = null;
+        
+        if (req.getContentLength() > 0) {
+            DocumentBuilder documentBuilder = getDocumentBuilder();
+    
+            try {
+                Document document = documentBuilder.parse
+                    (new InputSource(req.getInputStream()));
+    
+                // Get the root element of the document
+                Element rootElement = document.getDocumentElement();
+                NodeList childList = rootElement.getChildNodes();
+    
+                for (int i=0; i < childList.getLength(); i++) {
+                    Node currentNode = childList.item(i);
+                    switch (currentNode.getNodeType()) {
+                    case Node.TEXT_NODE:
+                        break;
+                    case Node.ELEMENT_NODE:
+                        if (currentNode.getNodeName().endsWith("prop")) {
+                            type = FIND_BY_PROPERTY;
+                            propNode = currentNode;
+                        }
+                        if (currentNode.getNodeName().endsWith("propname")) {
+                            type = FIND_PROPERTY_NAMES;
+                        }
+                        if (currentNode.getNodeName().endsWith("allprop")) {
+                            type = FIND_ALL_PROP;
+                        }
+                        break;
+                    }
+                }
+            } catch (SAXException e) {
+                // Something went wrong - bad request
+                resp.sendError(WebdavStatus.SC_BAD_REQUEST);
+            } catch (IOException e) {
+                // Something went wrong - bad request
+                resp.sendError(WebdavStatus.SC_BAD_REQUEST);
+            }
+        }
+
+        if (type == FIND_BY_PROPERTY) {
+            properties = new Vector<String>();
+            // propNode must be non-null if type == FIND_BY_PROPERTY
+            @SuppressWarnings("null")
+            NodeList childList = propNode.getChildNodes();
+
+            for (int i=0; i < childList.getLength(); i++) {
+                Node currentNode = childList.item(i);
+                switch (currentNode.getNodeType()) {
+                case Node.TEXT_NODE:
+                    break;
+                case Node.ELEMENT_NODE:
+                    String nodeName = currentNode.getNodeName();
+                    String propertyName = null;
+                    if (nodeName.indexOf(':') != -1) {
+                        propertyName = nodeName.substring
+                            (nodeName.indexOf(':') + 1);
+                    } else {
+                        propertyName = nodeName;
+                    }
+                    // href is a live property which is handled differently
+                    properties.addElement(propertyName);
+                    break;
+                }
+            }
+
+        }
+
+        boolean exists = true;
+        Object object = null;
+        try {
+            object = resources.lookup(path);
+        } catch (NamingException e) {
+            exists = false;
+            int slash = path.lastIndexOf('/');
+            if (slash != -1) {
+                String parentPath = path.substring(0, slash);
+                Vector<String> currentLockNullResources =
+                    lockNullResources.get(parentPath);
+                if (currentLockNullResources != null) {
+                    Enumeration<String> lockNullResourcesList =
+                        currentLockNullResources.elements();
+                    while (lockNullResourcesList.hasMoreElements()) {
+                        String lockNullPath =
+                            lockNullResourcesList.nextElement();
+                        if (lockNullPath.equals(path)) {
+                            resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
+                            resp.setContentType("text/xml; charset=UTF-8");
+                            // Create multistatus object
+                            XMLWriter generatedXML =
+                                new XMLWriter(resp.getWriter());
+                            generatedXML.writeXMLHeader();
+                            generatedXML.writeElement("D", DEFAULT_NAMESPACE,
+                                    "multistatus", XMLWriter.OPENING);
+                            parseLockNullProperties
+                                (req, generatedXML, lockNullPath, type,
+                                 properties);
+                            generatedXML.writeElement("D", "multistatus",
+                                    XMLWriter.CLOSING);
+                            generatedXML.sendData();
+                            return;
+                        }
+                    }
+                }
+            }
+        }
+
+        if (!exists) {
+            resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
+            return;
+        }
+
+        resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
+
+        resp.setContentType("text/xml; charset=UTF-8");
+
+        // Create multistatus object
+        XMLWriter generatedXML = new XMLWriter(resp.getWriter());
+        generatedXML.writeXMLHeader();
+
+        generatedXML.writeElement("D", DEFAULT_NAMESPACE, "multistatus",
+                XMLWriter.OPENING);
+
+        if (depth == 0) {
+            parseProperties(req, generatedXML, path, type,
+                            properties);
+        } else {
+            // The stack always contains the object of the current level
+            Stack<String> stack = new Stack<String>();
+            stack.push(path);
+
+            // Stack of the objects one level below
+            Stack<String> stackBelow = new Stack<String>();
+ 
+            while ((!stack.isEmpty()) && (depth >= 0)) {
+
+                String currentPath = stack.pop();
+                parseProperties(req, generatedXML, currentPath,
+                                type, properties);
+
+                try {
+                    object = resources.lookup(currentPath);
+                } catch (NamingException e) {
+                    continue;
+                }
+
+                if ((object instanceof DirContext) && (depth > 0)) {
+
+                    try {
+                        NamingEnumeration<NameClassPair> enumeration =
+                            resources.list(currentPath);
+                        while (enumeration.hasMoreElements()) {
+                            NameClassPair ncPair = enumeration.nextElement();
+                            String newPath = currentPath;
+                            if (!(newPath.endsWith("/")))
+                                newPath += "/";
+                            newPath += ncPair.getName();
+                            stackBelow.push(newPath);
+                        }
+                    } catch (NamingException e) {
+                        resp.sendError
+                            (HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                             path);
+                        return;
+                    }
+
+                    // Displaying the lock-null resources present in that
+                    // collection
+                    String lockPath = currentPath;
+                    if (lockPath.endsWith("/"))
+                        lockPath =
+                            lockPath.substring(0, lockPath.length() - 1);
+                    Vector<String> currentLockNullResources =
+                        lockNullResources.get(lockPath);
+                    if (currentLockNullResources != null) {
+                        Enumeration<String> lockNullResourcesList =
+                            currentLockNullResources.elements();
+                        while (lockNullResourcesList.hasMoreElements()) {
+                            String lockNullPath =
+                                lockNullResourcesList.nextElement();
+                            parseLockNullProperties
+                                (req, generatedXML, lockNullPath, type,
+                                 properties);
+                        }
+                    }
+
+                }
+
+                if (stack.isEmpty()) {
+                    depth--;
+                    stack = stackBelow;
+                    stackBelow = new Stack<String>();
+                }
+
+                generatedXML.sendData();
+
+            }
+        }
+
+        generatedXML.writeElement("D", "multistatus", XMLWriter.CLOSING);
+
+        generatedXML.sendData();
+
+    }
+
+
+    /**
+     * PROPPATCH Method.
+     */
+    protected void doProppatch(HttpServletRequest req, HttpServletResponse resp)
+            throws IOException {
+
+        if (readOnly) {
+            resp.sendError(WebdavStatus.SC_FORBIDDEN);
+            return;
+        }
+
+        if (isLocked(req)) {
+            resp.sendError(WebdavStatus.SC_LOCKED);
+            return;
+        }
+
+        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
+
+    }
+
+
+    /**
+     * MKCOL Method.
+     */
+    protected void doMkcol(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        if (readOnly) {
+            resp.sendError(WebdavStatus.SC_FORBIDDEN);
+            return;
+        }
+
+        if (isLocked(req)) {
+            resp.sendError(WebdavStatus.SC_LOCKED);
+            return;
+        }
+
+        String path = getRelativePath(req);
+
+        boolean exists = true;
+        try {
+            resources.lookup(path);
+        } catch (NamingException e) {
+            exists = false;
+        }
+
+        // Can't create a collection if a resource already exists at the given
+        // path
+        if (exists) {
+            // Get allowed methods
+            StringBuilder methodsAllowed = determineMethodsAllowed(resources,
+                                                                  req);
+
+            resp.addHeader("Allow", methodsAllowed.toString());
+
+            resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
+            return;
+        }
+
+        if (req.getContentLength() > 0) {
+            DocumentBuilder documentBuilder = getDocumentBuilder();
+            try {
+                // Document document =
+                documentBuilder.parse(new InputSource(req.getInputStream()));
+                // TODO : Process this request body
+                resp.sendError(WebdavStatus.SC_NOT_IMPLEMENTED);
+                return;
+
+            } catch(SAXException saxe) {
+                // Parse error - assume invalid content
+                resp.sendError(WebdavStatus.SC_UNSUPPORTED_MEDIA_TYPE);
+                return;
+            }
+        }
+
+        boolean result = true;
+        try {
+            resources.createSubcontext(path);
+        } catch (NamingException e) {
+            result = false;
+        }
+
+        if (!result) {
+            resp.sendError(WebdavStatus.SC_CONFLICT,
+                           WebdavStatus.getStatusText
+                           (WebdavStatus.SC_CONFLICT));
+        } else {
+            resp.setStatus(WebdavStatus.SC_CREATED);
+            // Removing any lock-null resource which would be present
+            lockNullResources.remove(path);
+        }
+
+    }
+
+
+    /**
+     * DELETE Method.
+     */
+    @Override
+    protected void doDelete(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        if (readOnly) {
+            resp.sendError(WebdavStatus.SC_FORBIDDEN);
+            return;
+        }
+
+        if (isLocked(req)) {
+            resp.sendError(WebdavStatus.SC_LOCKED);
+            return;
+        }
+
+        deleteResource(req, resp);
+
+    }
+
+
+    /**
+     * Process a PUT request for the specified resource.
+     *
+     * @param req The servlet request we are processing
+     * @param resp The servlet response we are creating
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet-specified error occurs
+     */
+    @Override
+    protected void doPut(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        if (isLocked(req)) {
+            resp.sendError(WebdavStatus.SC_LOCKED);
+            return;
+        }
+
+        super.doPut(req, resp);
+
+        String path = getRelativePath(req);
+
+        // Removing any lock-null resource which would be present
+        lockNullResources.remove(path);
+
+    }
+
+    /**
+     * COPY Method.
+     */
+    protected void doCopy(HttpServletRequest req, HttpServletResponse resp)
+            throws IOException {
+
+        if (readOnly) {
+            resp.sendError(WebdavStatus.SC_FORBIDDEN);
+            return;
+        }
+
+        copyResource(req, resp);
+
+    }
+
+
+    /**
+     * MOVE Method.
+     */
+    protected void doMove(HttpServletRequest req, HttpServletResponse resp)
+            throws IOException {
+
+        if (readOnly) {
+            resp.sendError(WebdavStatus.SC_FORBIDDEN);
+            return;
+        }
+
+        if (isLocked(req)) {
+            resp.sendError(WebdavStatus.SC_LOCKED);
+            return;
+        }
+
+        String path = getRelativePath(req);
+
+        if (copyResource(req, resp)) {
+            deleteResource(path, req, resp, false);
+        }
+
+    }
+
+
+    /**
+     * LOCK Method.
+     */
+    protected void doLock(HttpServletRequest req, HttpServletResponse resp)
+        throws ServletException, IOException {
+
+        if (readOnly) {
+            resp.sendError(WebdavStatus.SC_FORBIDDEN);
+            return;
+        }
+
+        if (isLocked(req)) {
+            resp.sendError(WebdavStatus.SC_LOCKED);
+            return;
+        }
+
+        LockInfo lock = new LockInfo();
+
+        // Parsing lock request
+
+        // Parsing depth header
+
+        String depthStr = req.getHeader("Depth");
+
+        if (depthStr == null) {
+            lock.depth = maxDepth;
+        } else {
+            if (depthStr.equals("0")) {
+                lock.depth = 0;
+            } else {
+                lock.depth = maxDepth;
+            }
+        }
+
+        // Parsing timeout header
+
+        int lockDuration = DEFAULT_TIMEOUT;
+        String lockDurationStr = req.getHeader("Timeout");
+        if (lockDurationStr == null) {
+            lockDuration = DEFAULT_TIMEOUT;
+        } else {
+            int commaPos = lockDurationStr.indexOf(",");
+            // If multiple timeouts, just use the first
+            if (commaPos != -1) {
+                lockDurationStr = lockDurationStr.substring(0,commaPos);
+            }
+            if (lockDurationStr.startsWith("Second-")) {
+                lockDuration =
+                    (new Integer(lockDurationStr.substring(7))).intValue();
+            } else {
+                if (lockDurationStr.equalsIgnoreCase("infinity")) {
+                    lockDuration = MAX_TIMEOUT;
+                } else {
+                    try {
+                        lockDuration =
+                            (new Integer(lockDurationStr)).intValue();
+                    } catch (NumberFormatException e) {
+                        lockDuration = MAX_TIMEOUT;
+                    }
+                }
+            }
+            if (lockDuration == 0) {
+                lockDuration = DEFAULT_TIMEOUT;
+            }
+            if (lockDuration > MAX_TIMEOUT) {
+                lockDuration = MAX_TIMEOUT;
+            }
+        }
+        lock.expiresAt = System.currentTimeMillis() + (lockDuration * 1000);
+
+        int lockRequestType = LOCK_CREATION;
+
+        Node lockInfoNode = null;
+
+        DocumentBuilder documentBuilder = getDocumentBuilder();
+
+        try {
+            Document document = documentBuilder.parse(new InputSource
+                (req.getInputStream()));
+
+            // Get the root element of the document
+            Element rootElement = document.getDocumentElement();
+            lockInfoNode = rootElement;
+        } catch (IOException e) {
+            lockRequestType = LOCK_REFRESH;
+        } catch (SAXException e) {
+            lockRequestType = LOCK_REFRESH;
+        }
+
+        if (lockInfoNode != null) {
+
+            // Reading lock information
+
+            NodeList childList = lockInfoNode.getChildNodes();
+            StringWriter strWriter = null;
+            DOMWriter domWriter = null;
+
+            Node lockScopeNode = null;
+            Node lockTypeNode = null;
+            Node lockOwnerNode = null;
+
+            for (int i=0; i < childList.getLength(); i++) {
+                Node currentNode = childList.item(i);
+                switch (currentNode.getNodeType()) {
+                case Node.TEXT_NODE:
+                    break;
+                case Node.ELEMENT_NODE:
+                    String nodeName = currentNode.getNodeName();
+                    if (nodeName.endsWith("lockscope")) {
+                        lockScopeNode = currentNode;
+                    }
+                    if (nodeName.endsWith("locktype")) {
+                        lockTypeNode = currentNode;
+                    }
+                    if (nodeName.endsWith("owner")) {
+                        lockOwnerNode = currentNode;
+                    }
+                    break;
+                }
+            }
+
+            if (lockScopeNode != null) {
+
+                childList = lockScopeNode.getChildNodes();
+                for (int i=0; i < childList.getLength(); i++) {
+                    Node currentNode = childList.item(i);
+                    switch (currentNode.getNodeType()) {
+                    case Node.TEXT_NODE:
+                        break;
+                    case Node.ELEMENT_NODE:
+                        String tempScope = currentNode.getNodeName();
+                        if (tempScope.indexOf(':') != -1) {
+                            lock.scope = tempScope.substring
+                                (tempScope.indexOf(':') + 1);
+                        } else {
+                            lock.scope = tempScope;
+                        }
+                        break;
+                    }
+                }
+
+                if (lock.scope == null) {
+                    // Bad request
+                    resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
+                }
+
+            } else {
+                // Bad request
+                resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
+            }
+
+            if (lockTypeNode != null) {
+
+                childList = lockTypeNode.getChildNodes();
+                for (int i=0; i < childList.getLength(); i++) {
+                    Node currentNode = childList.item(i);
+                    switch (currentNode.getNodeType()) {
+                    case Node.TEXT_NODE:
+                        break;
+                    case Node.ELEMENT_NODE:
+                        String tempType = currentNode.getNodeName();
+                        if (tempType.indexOf(':') != -1) {
+                            lock.type =
+                                tempType.substring(tempType.indexOf(':') + 1);
+                        } else {
+                            lock.type = tempType;
+                        }
+                        break;
+                    }
+                }
+
+                if (lock.type == null) {
+                    // Bad request
+                    resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
+                }
+
+            } else {
+                // Bad request
+                resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
+            }
+
+            if (lockOwnerNode != null) {
+
+                childList = lockOwnerNode.getChildNodes();
+                for (int i=0; i < childList.getLength(); i++) {
+                    Node currentNode = childList.item(i);
+                    switch (currentNode.getNodeType()) {
+                    case Node.TEXT_NODE:
+                        lock.owner += currentNode.getNodeValue();
+                        break;
+                    case Node.ELEMENT_NODE:
+                        strWriter = new StringWriter();
+                        domWriter = new DOMWriter(strWriter, true);
+                        domWriter.setQualifiedNames(false);
+                        domWriter.print(currentNode);
+                        lock.owner += strWriter.toString();
+                        break;
+                    }
+                }
+
+                if (lock.owner == null) {
+                    // Bad request
+                    resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
+                }
+
+            } else {
+                lock.owner = "";
+            }
+
+        }
+
+        String path = getRelativePath(req);
+
+        lock.path = path;
+
+        boolean exists = true;
+        Object object = null;
+        try {
+            object = resources.lookup(path);
+        } catch (NamingException e) {
+            exists = false;
+        }
+
+        Enumeration<LockInfo> locksList = null;
+
+        if (lockRequestType == LOCK_CREATION) {
+
+            // Generating lock id
+            String lockTokenStr = req.getServletPath() + "-" + lock.type + "-"
+                + lock.scope + "-" + req.getUserPrincipal() + "-"
+                + lock.depth + "-" + lock.owner + "-" + lock.tokens + "-"
+                + lock.expiresAt + "-" + System.currentTimeMillis() + "-"
+                + secret;
+            String lockToken =
+                md5Encoder.encode(md5Helper.digest(lockTokenStr.getBytes()));
+
+            if ( (exists) && (object instanceof DirContext) &&
+                 (lock.depth == maxDepth) ) {
+
+                // Locking a collection (and all its member resources)
+
+                // Checking if a child resource of this collection is
+                // already locked
+                Vector<String> lockPaths = new Vector<String>();
+                locksList = collectionLocks.elements();
+                while (locksList.hasMoreElements()) {
+                    LockInfo currentLock = locksList.nextElement();
+                    if (currentLock.hasExpired()) {
+                        resourceLocks.remove(currentLock.path);
+                        continue;
+                    }
+                    if ( (currentLock.path.startsWith(lock.path)) &&
+                         ((currentLock.isExclusive()) ||
+                          (lock.isExclusive())) ) {
+                        // A child collection of this collection is locked
+                        lockPaths.addElement(currentLock.path);
+                    }
+                }
+                locksList = resourceLocks.elements();
+                while (locksList.hasMoreElements()) {
+                    LockInfo currentLock = locksList.nextElement();
+                    if (currentLock.hasExpired()) {
+                        resourceLocks.remove(currentLock.path);
+                        continue;
+                    }
+                    if ( (currentLock.path.startsWith(lock.path)) &&
+                         ((currentLock.isExclusive()) ||
+                          (lock.isExclusive())) ) {
+                        // A child resource of this collection is locked
+                        lockPaths.addElement(currentLock.path);
+                    }
+                }
+
+                if (!lockPaths.isEmpty()) {
+
+                    // One of the child paths was locked
+                    // We generate a multistatus error report
+
+                    Enumeration<String> lockPathsList = lockPaths.elements();
+
+                    resp.setStatus(WebdavStatus.SC_CONFLICT);
+
+                    XMLWriter generatedXML = new XMLWriter();
+                    generatedXML.writeXMLHeader();
+
+                    generatedXML.writeElement("D", DEFAULT_NAMESPACE,
+                            "multistatus", XMLWriter.OPENING);
+
+                    while (lockPathsList.hasMoreElements()) {
+                        generatedXML.writeElement("D", "response",
+                                XMLWriter.OPENING);
+                        generatedXML.writeElement("D", "href",
+                                XMLWriter.OPENING);
+                        generatedXML.writeText(lockPathsList.nextElement());
+                        generatedXML.writeElement("D", "href",
+                                XMLWriter.CLOSING);
+                        generatedXML.writeElement("D", "status",
+                                XMLWriter.OPENING);
+                        generatedXML
+                            .writeText("HTTP/1.1 " + WebdavStatus.SC_LOCKED
+                                       + " " + WebdavStatus
+                                       .getStatusText(WebdavStatus.SC_LOCKED));
+                        generatedXML.writeElement("D", "status",
+                                XMLWriter.CLOSING);
+
+                        generatedXML.writeElement("D", "response",
+                                XMLWriter.CLOSING);
+                    }
+
+                    generatedXML.writeElement("D", "multistatus",
+                            XMLWriter.CLOSING);
+
+                    Writer writer = resp.getWriter();
+                    writer.write(generatedXML.toString());
+                    writer.close();
+
+                    return;
+
+                }
+
+                boolean addLock = true;
+
+                // Checking if there is already a shared lock on this path
+                locksList = collectionLocks.elements();
+                while (locksList.hasMoreElements()) {
+
+                    LockInfo currentLock = locksList.nextElement();
+                    if (currentLock.path.equals(lock.path)) {
+
+                        if (currentLock.isExclusive()) {
+                            resp.sendError(WebdavStatus.SC_LOCKED);
+                            return;
+                        } else {
+                            if (lock.isExclusive()) {
+                                resp.sendError(WebdavStatus.SC_LOCKED);
+                                return;
+                            }
+                        }
+
+                        currentLock.tokens.addElement(lockToken);
+                        lock = currentLock;
+                        addLock = false;
+
+                    }
+
+                }
+
+                if (addLock) {
+                    lock.tokens.addElement(lockToken);
+                    collectionLocks.addElement(lock);
+                }
+
+            } else {
+
+                // Locking a single resource
+
+                // Retrieving an already existing lock on that resource
+                LockInfo presentLock = resourceLocks.get(lock.path);
+                if (presentLock != null) {
+
+                    if ((presentLock.isExclusive()) || (lock.isExclusive())) {
+                        // If either lock is exclusive, the lock can't be
+                        // granted
+                        resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
+                        return;
+                    } else {
+                        presentLock.tokens.addElement(lockToken);
+                        lock = presentLock;
+                    }
+
+                } else {
+
+                    lock.tokens.addElement(lockToken);
+                    resourceLocks.put(lock.path, lock);
+
+                    // Checking if a resource exists at this path
+                    exists = true;
+                    try {
+                        object = resources.lookup(path);
+                    } catch (NamingException e) {
+                        exists = false;
+                    }
+                    if (!exists) {
+
+                        // "Creating" a lock-null resource
+                        int slash = lock.path.lastIndexOf('/');
+                        String parentPath = lock.path.substring(0, slash);
+
+                        Vector<String> lockNulls =
+                            lockNullResources.get(parentPath);
+                        if (lockNulls == null) {
+                            lockNulls = new Vector<String>();
+                            lockNullResources.put(parentPath, lockNulls);
+                        }
+
+                        lockNulls.addElement(lock.path);
+
+                    }
+                    // Add the Lock-Token header as by RFC 2518 8.10.1
+                    // - only do this for newly created locks
+                    resp.addHeader("Lock-Token", "<opaquelocktoken:"
+                                   + lockToken + ">");
+                }
+
+            }
+
+        }
+
+        if (lockRequestType == LOCK_REFRESH) {
+
+            String ifHeader = req.getHeader("If");
+            if (ifHeader == null)
+                ifHeader = "";
+
+            // Checking resource locks
+
+            LockInfo toRenew = resourceLocks.get(path);
+            Enumeration<String> tokenList = null;
+
+            // At least one of the tokens of the locks must have been given
+            tokenList = toRenew.tokens.elements();
+            while (tokenList.hasMoreElements()) {
+                String token = tokenList.nextElement();
+                if (ifHeader.indexOf(token) != -1) {
+                    toRenew.expiresAt = lock.expiresAt;
+                    lock = toRenew;
+                }
+            }
+
+            // Checking inheritable collection locks
+
+            Enumeration<LockInfo> collectionLocksList =
+                collectionLocks.elements();
+            while (collectionLocksList.hasMoreElements()) {
+                toRenew = collectionLocksList.nextElement();
+                if (path.equals(toRenew.path)) {
+
+                    tokenList = toRenew.tokens.elements();
+                    while (tokenList.hasMoreElements()) {
+                        String token = tokenList.nextElement();
+                        if (ifHeader.indexOf(token) != -1) {
+                            toRenew.expiresAt = lock.expiresAt;
+                            lock = toRenew;
+                        }
+                    }
+
+                }
+            }
+
+        }
+
+        // Set the status, then generate the XML response containing
+        // the lock information
+        XMLWriter generatedXML = new XMLWriter();
+        generatedXML.writeXMLHeader();
+        generatedXML.writeElement("D", DEFAULT_NAMESPACE, "prop",
+                XMLWriter.OPENING);
+
+        generatedXML.writeElement("D", "lockdiscovery", XMLWriter.OPENING);
+
+        lock.toXML(generatedXML);
+
+        generatedXML.writeElement("D", "lockdiscovery", XMLWriter.CLOSING);
+
+        generatedXML.writeElement("D", "prop", XMLWriter.CLOSING);
+
+        resp.setStatus(WebdavStatus.SC_OK);
+        resp.setContentType("text/xml; charset=UTF-8");
+        Writer writer = resp.getWriter();
+        writer.write(generatedXML.toString());
+        writer.close();
+
+    }
+
+
+    /**
+     * UNLOCK Method.
+     */
+    protected void doUnlock(HttpServletRequest req, HttpServletResponse resp)
+            throws IOException {
+
+        if (readOnly) {
+            resp.sendError(WebdavStatus.SC_FORBIDDEN);
+            return;
+        }
+
+        if (isLocked(req)) {
+            resp.sendError(WebdavStatus.SC_LOCKED);
+            return;
+        }
+
+        String path = getRelativePath(req);
+
+        String lockTokenHeader = req.getHeader("Lock-Token");
+        if (lockTokenHeader == null)
+            lockTokenHeader = "";
+
+        // Checking resource locks
+
+        LockInfo lock = resourceLocks.get(path);
+        Enumeration<String> tokenList = null;
+        if (lock != null) {
+
+            // At least one of the tokens of the locks must have been given
+
+            tokenList = lock.tokens.elements();
+            while (tokenList.hasMoreElements()) {
+                String token = tokenList.nextElement();
+                if (lockTokenHeader.indexOf(token) != -1) {
+                    lock.tokens.removeElement(token);
+                }
+            }
+
+            if (lock.tokens.isEmpty()) {
+                resourceLocks.remove(path);
+                // Removing any lock-null resource which would be present
+                lockNullResources.remove(path);
+            }
+
+        }
+
+        // Checking inheritable collection locks
+
+        Enumeration<LockInfo> collectionLocksList = collectionLocks.elements();
+        while (collectionLocksList.hasMoreElements()) {
+            lock = collectionLocksList.nextElement();
+            if (path.equals(lock.path)) {
+
+                tokenList = lock.tokens.elements();
+                while (tokenList.hasMoreElements()) {
+                    String token = tokenList.nextElement();
+                    if (lockTokenHeader.indexOf(token) != -1) {
+                        lock.tokens.removeElement(token);
+                        break;
+                    }
+                }
+
+                if (lock.tokens.isEmpty()) {
+                    collectionLocks.removeElement(lock);
+                    // Removing any lock-null resource which would be present
+                    lockNullResources.remove(path);
+                }
+
+            }
+        }
+
+        resp.setStatus(WebdavStatus.SC_NO_CONTENT);
+
+    }
+
+    // -------------------------------------------------------- Private Methods
+
+    /**
+     * Check to see if a resource is currently write locked. The method
+     * will look at the "If" header to make sure the client
+     * has give the appropriate lock tokens.
+     *
+     * @param req Servlet request
+     * @return boolean true if the resource is locked (and no appropriate
+     * lock token has been found for at least one of the non-shared locks which
+     * are present on the resource).
+     */
+    private boolean isLocked(HttpServletRequest req) {
+
+        String path = getRelativePath(req);
+
+        String ifHeader = req.getHeader("If");
+        if (ifHeader == null)
+            ifHeader = "";
+
+        String lockTokenHeader = req.getHeader("Lock-Token");
+        if (lockTokenHeader == null)
+            lockTokenHeader = "";
+
+        return isLocked(path, ifHeader + lockTokenHeader);
+
+    }
+
+
+    /**
+     * Check to see if a resource is currently write locked.
+     *
+     * @param path Path of the resource
+     * @param ifHeader "If" HTTP header which was included in the request
+     * @return boolean true if the resource is locked (and no appropriate
+     * lock token has been found for at least one of the non-shared locks which
+     * are present on the resource).
+     */
+    private boolean isLocked(String path, String ifHeader) {
+
+        // Checking resource locks
+
+        LockInfo lock = resourceLocks.get(path);
+        Enumeration<String> tokenList = null;
+        if ((lock != null) && (lock.hasExpired())) {
+            resourceLocks.remove(path);
+        } else if (lock != null) {
+
+            // At least one of the tokens of the locks must have been given
+
+            tokenList = lock.tokens.elements();
+            boolean tokenMatch = false;
+            while (tokenList.hasMoreElements()) {
+                String token = tokenList.nextElement();
+                if (ifHeader.indexOf(token) != -1)
+                    tokenMatch = true;
+            }
+            if (!tokenMatch)
+                return true;
+
+        }
+
+        // Checking inheritable collection locks
+
+        Enumeration<LockInfo> collectionLocksList = collectionLocks.elements();
+        while (collectionLocksList.hasMoreElements()) {
+            lock = collectionLocksList.nextElement();
+            if (lock.hasExpired()) {
+                collectionLocks.removeElement(lock);
+            } else if (path.startsWith(lock.path)) {
+
+                tokenList = lock.tokens.elements();
+                boolean tokenMatch = false;
+                while (tokenList.hasMoreElements()) {
+                    String token = tokenList.nextElement();
+                    if (ifHeader.indexOf(token) != -1)
+                        tokenMatch = true;
+                }
+                if (!tokenMatch)
+                    return true;
+
+            }
+        }
+
+        return false;
+
+    }
+
+
+    /**
+     * Copy a resource.
+     *
+     * @param req Servlet request
+     * @param resp Servlet response
+     * @return boolean true if the copy is successful
+     */
+    private boolean copyResource(HttpServletRequest req,
+                                 HttpServletResponse resp)
+            throws IOException {
+
+        // Parsing destination header
+
+        String destinationPath = req.getHeader("Destination");
+
+        if (destinationPath == null) {
+            resp.sendError(WebdavStatus.SC_BAD_REQUEST);
+            return false;
+        }
+
+        // Remove url encoding from destination
+        destinationPath = RequestUtil.URLDecode(destinationPath, "UTF8");
+
+        int protocolIndex = destinationPath.indexOf("://");
+        if (protocolIndex >= 0) {
+            // if the Destination URL contains the protocol, we can safely
+            // trim everything upto the first "/" character after "://"
+            int firstSeparator =
+                destinationPath.indexOf("/", protocolIndex + 4);
+            if (firstSeparator < 0) {
+                destinationPath = "/";
+            } else {
+                destinationPath = destinationPath.substring(firstSeparator);
+            }
+        } else {
+            String hostName = req.getServerName();
+            if ((hostName != null) && (destinationPath.startsWith(hostName))) {
+                destinationPath = destinationPath.substring(hostName.length());
+            }
+
+            int portIndex = destinationPath.indexOf(":");
+            if (portIndex >= 0) {
+                destinationPath = destinationPath.substring(portIndex);
+            }
+
+            if (destinationPath.startsWith(":")) {
+                int firstSeparator = destinationPath.indexOf("/");
+                if (firstSeparator < 0) {
+                    destinationPath = "/";
+                } else {
+                    destinationPath =
+                        destinationPath.substring(firstSeparator);
+                }
+            }
+        }
+
+        // Normalise destination path (remove '.' and '..')
+        destinationPath = RequestUtil.normalize(destinationPath);
+
+        String contextPath = req.getContextPath();
+        if ((contextPath != null) &&
+            (destinationPath.startsWith(contextPath))) {
+            destinationPath = destinationPath.substring(contextPath.length());
+        }
+
+        String pathInfo = req.getPathInfo();
+        if (pathInfo != null) {
+            String servletPath = req.getServletPath();
+            if ((servletPath != null) &&
+                (destinationPath.startsWith(servletPath))) {
+                destinationPath = destinationPath
+                    .substring(servletPath.length());
+            }
+        }
+
+        if (debug > 0)
+            log("Dest path :" + destinationPath);
+
+        // Check destination path to protect special subdirectories
+        if (isSpecialPath(destinationPath)) {
+            resp.sendError(WebdavStatus.SC_FORBIDDEN);
+            return false;
+        }
+
+        String path = getRelativePath(req);
+
+        if (destinationPath.equals(path)) {
+            resp.sendError(WebdavStatus.SC_FORBIDDEN);
+            return false;
+        }
+
+        // Parsing overwrite header
+
+        boolean overwrite = true;
+        String overwriteHeader = req.getHeader("Overwrite");
+
+        if (overwriteHeader != null) {
+            if (overwriteHeader.equalsIgnoreCase("T")) {
+                overwrite = true;
+            } else {
+                overwrite = false;
+            }
+        }
+
+        // Overwriting the destination
+
+        boolean exists = true;
+        try {
+            resources.lookup(destinationPath);
+        } catch (NamingException e) {
+            exists = false;
+        }
+
+        if (overwrite) {
+
+            // Delete destination resource, if it exists
+            if (exists) {
+                if (!deleteResource(destinationPath, req, resp, true)) {
+                    return false;
+                }
+            } else {
+                resp.setStatus(WebdavStatus.SC_CREATED);
+            }
+
+        } else {
+
+            // If the destination exists, then it's a conflict
+            if (exists) {
+                resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
+                return false;
+            }
+
+        }
+
+        // Copying source to destination
+
+        Hashtable<String,Integer> errorList = new Hashtable<String,Integer>();
+
+        boolean result = copyResource(resources, errorList,
+                                      path, destinationPath);
+
+        if ((!result) || (!errorList.isEmpty())) {
+            if (errorList.size() == 1) {
+                resp.sendError(errorList.elements().nextElement().intValue());
+            } else {
+                sendReport(req, resp, errorList);
+            }
+            return false;
+        }
+
+        // Copy was successful
+        if (exists) {
+            resp.setStatus(WebdavStatus.SC_NO_CONTENT);
+        } else {
+            resp.setStatus(WebdavStatus.SC_CREATED);
+        }
+
+        // Removing any lock-null resource which would be present at
+        // the destination path
+        lockNullResources.remove(destinationPath);
+
+        return true;
+
+    }
+
+
+    /**
+     * Copy a collection.
+     *
+     * @param dirContext Resources implementation to be used
+     * @param errorList Hashtable containing the list of errors which occurred
+     * during the copy operation
+     * @param source Path of the resource to be copied
+     * @param dest Destination path
+     */
+    private boolean copyResource(DirContext dirContext,
+            Hashtable<String,Integer> errorList, String source, String dest) {
+
+        if (debug > 1)
+            log("Copy: " + source + " To: " + dest);
+
+        Object object = null;
+        try {
+            object = dirContext.lookup(source);
+        } catch (NamingException e) {
+            // Ignore
+        }
+
+        if (object instanceof DirContext) {
+
+            try {
+                dirContext.createSubcontext(dest);
+            } catch (NamingException e) {
+                errorList.put
+                    (dest, new Integer(WebdavStatus.SC_CONFLICT));
+                return false;
+            }
+
+            try {
+                NamingEnumeration<NameClassPair> enumeration =
+                    dirContext.list(source);
+                while (enumeration.hasMoreElements()) {
+                    NameClassPair ncPair = enumeration.nextElement();
+                    String childDest = dest;
+                    if (!childDest.equals("/"))
+                        childDest += "/";
+                    childDest += ncPair.getName();
+                    String childSrc = source;
+                    if (!childSrc.equals("/"))
+                        childSrc += "/";
+                    childSrc += ncPair.getName();
+                    copyResource(dirContext, errorList, childSrc, childDest);
+                }
+            } catch (NamingException e) {
+                errorList.put
+                    (dest, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
+                return false;
+            }
+
+        } else {
+
+            if (object instanceof Resource) {
+                try {
+                    dirContext.bind(dest, object);
+                } catch (NamingException e) {
+                    if (e.getCause() instanceof FileNotFoundException) {
+                        // We know the source exists so it must be the
+                        // destination dir that can't be found
+                        errorList.put(source,
+                                new Integer(WebdavStatus.SC_CONFLICT));
+                    } else {
+                        errorList.put(source,
+                                new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
+                    }
+                    return false;
+                }
+            } else {
+                errorList.put
+                    (source,
+                     new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
+                return false;
+            }
+
+        }
+
+        return true;
+
+    }
+
+
+    /**
+     * Delete a resource.
+     *
+     * @param req Servlet request
+     * @param resp Servlet response
+     * @return boolean true if the copy is successful
+     */
+    private boolean deleteResource(HttpServletRequest req,
+                                   HttpServletResponse resp)
+            throws IOException {
+
+        String path = getRelativePath(req);
+
+        return deleteResource(path, req, resp, true);
+
+    }
+
+
+    /**
+     * Delete a resource.
+     *
+     * @param path Path of the resource which is to be deleted
+     * @param req Servlet request
+     * @param resp Servlet response
+     * @param setStatus Should the response status be set on successful
+     *                  completion
+     */
+    private boolean deleteResource(String path, HttpServletRequest req,
+                                   HttpServletResponse resp, boolean setStatus)
+            throws IOException {
+
+        String ifHeader = req.getHeader("If");
+        if (ifHeader == null)
+            ifHeader = "";
+
+        String lockTokenHeader = req.getHeader("Lock-Token");
+        if (lockTokenHeader == null)
+            lockTokenHeader = "";
+
+        if (isLocked(path, ifHeader + lockTokenHeader)) {
+            resp.sendError(WebdavStatus.SC_LOCKED);
+            return false;
+        }
+
+        boolean exists = true;
+        Object object = null;
+        try {
+            object = resources.lookup(path);
+        } catch (NamingException e) {
+            exists = false;
+        }
+
+        if (!exists) {
+            resp.sendError(WebdavStatus.SC_NOT_FOUND);
+            return false;
+        }
+
+        boolean collection = (object instanceof DirContext);
+
+        if (!collection) {
+            try {
+                resources.unbind(path);
+            } catch (NamingException e) {
+                resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
+                return false;
+            }
+        } else {
+
+            Hashtable<String,Integer> errorList =
+                new Hashtable<String,Integer>();
+
+            deleteCollection(req, resources, path, errorList);
+            try {
+                resources.unbind(path);
+            } catch (NamingException e) {
+                errorList.put(path, new Integer
+                    (WebdavStatus.SC_INTERNAL_SERVER_ERROR));
+            }
+
+            if (!errorList.isEmpty()) {
+
+                sendReport(req, resp, errorList);
+                return false;
+
+            }
+
+        }
+        if (setStatus) {
+            resp.setStatus(WebdavStatus.SC_NO_CONTENT);
+        }
+        return true;
+
+    }
+
+
+    /**
+     * Deletes a collection.
+     *
+     * @param dirContext Resources implementation associated with the context
+     * @param path Path to the collection to be deleted
+     * @param errorList Contains the list of the errors which occurred
+     */
+    private void deleteCollection(HttpServletRequest req,
+                                  DirContext dirContext,
+                                  String path,
+                                  Hashtable<String,Integer> errorList) {
+
+        if (debug > 1)
+            log("Delete:" + path);
+
+        // Prevent deletion of special subdirectories
+        if (isSpecialPath(path)) {
+            errorList.put(path, new Integer(WebdavStatus.SC_FORBIDDEN));
+            return;
+        }
+
+        String ifHeader = req.getHeader("If");
+        if (ifHeader == null)
+            ifHeader = "";
+
+        String lockTokenHeader = req.getHeader("Lock-Token");
+        if (lockTokenHeader == null)
+            lockTokenHeader = "";
+
+        Enumeration<NameClassPair> enumeration = null;
+        try {
+            enumeration = dirContext.list(path);
+        } catch (NamingException e) {
+            errorList.put(path, new Integer
+                (WebdavStatus.SC_INTERNAL_SERVER_ERROR));
+            return;
+        }
+
+        while (enumeration.hasMoreElements()) {
+            NameClassPair ncPair = enumeration.nextElement();
+            String childName = path;
+            if (!childName.equals("/"))
+                childName += "/";
+            childName += ncPair.getName();
+
+            if (isLocked(childName, ifHeader + lockTokenHeader)) {
+
+                errorList.put(childName, new Integer(WebdavStatus.SC_LOCKED));
+
+            } else {
+
+                try {
+                    Object object = dirContext.lookup(childName);
+                    if (object instanceof DirContext) {
+                        deleteCollection(req, dirContext, childName, errorList);
+                    }
+
+                    try {
+                        dirContext.unbind(childName);
+                    } catch (NamingException e) {
+                        if (!(object instanceof DirContext)) {
+                            // If it's not a collection, then it's an unknown
+                            // error
+                            errorList.put
+                                (childName, new Integer
+                                    (WebdavStatus.SC_INTERNAL_SERVER_ERROR));
+                        }
+                    }
+                } catch (NamingException e) {
+                    errorList.put
+                        (childName, new Integer
+                            (WebdavStatus.SC_INTERNAL_SERVER_ERROR));
+                }
+            }
+
+        }
+
+    }
+
+
+    /**
+     * Send a multistatus element containing a complete error report to the
+     * client.
+     *
+     * @param req Servlet request
+     * @param resp Servlet response
+     * @param errorList List of error to be displayed
+     */
+    private void sendReport(HttpServletRequest req, HttpServletResponse resp,
+                            Hashtable<String,Integer> errorList)
+            throws IOException {
+
+        resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
+
+        String absoluteUri = req.getRequestURI();
+        String relativePath = getRelativePath(req);
+
+        XMLWriter generatedXML = new XMLWriter();
+        generatedXML.writeXMLHeader();
+
+        generatedXML.writeElement("D", DEFAULT_NAMESPACE, "multistatus",
+                XMLWriter.OPENING);
+
+        Enumeration<String> pathList = errorList.keys();
+        while (pathList.hasMoreElements()) {
+
+            String errorPath = pathList.nextElement();
+            int errorCode = errorList.get(errorPath).intValue();
+
+            generatedXML.writeElement("D", "response", XMLWriter.OPENING);
+
+            generatedXML.writeElement("D", "href", XMLWriter.OPENING);
+            String toAppend = errorPath.substring(relativePath.length());
+            if (!toAppend.startsWith("/"))
+                toAppend = "/" + toAppend;
+            generatedXML.writeText(absoluteUri + toAppend);
+            generatedXML.writeElement("D", "href", XMLWriter.CLOSING);
+            generatedXML.writeElement("D", "status", XMLWriter.OPENING);
+            generatedXML.writeText("HTTP/1.1 " + errorCode + " "
+                    + WebdavStatus.getStatusText(errorCode));
+            generatedXML.writeElement("D", "status", XMLWriter.CLOSING);
+
+            generatedXML.writeElement("D", "response", XMLWriter.CLOSING);
+
+        }
+
+        generatedXML.writeElement("D", "multistatus", XMLWriter.CLOSING);
+
+        Writer writer = resp.getWriter();
+        writer.write(generatedXML.toString());
+        writer.close();
+
+    }
+
+
+    /**
+     * Propfind helper method.
+     *
+     * @param req The servlet request
+     * @param resources Resources object associated with this context
+     * @param generatedXML XML response to the Propfind request
+     * @param path Path of the current resource
+     * @param type Propfind type
+     * @param propertiesVector If the propfind type is find properties by
+     * name, then this Vector contains those properties
+     */
+    private void parseProperties(HttpServletRequest req,
+                                 XMLWriter generatedXML,
+                                 String path, int type,
+                                 Vector<String> propertiesVector) {
+
+        // Exclude any resource in the /WEB-INF and /META-INF subdirectories
+        if (isSpecialPath(path))
+            return;
+
+        CacheEntry cacheEntry = resources.lookupCache(path);
+        if (!cacheEntry.exists) {
+            // File is in directory listing but doesn't appear to exist
+            // Broken symlink or odd permission settings?
+            return;
+        }
+
+        generatedXML.writeElement("D", "response", XMLWriter.OPENING);
+        String status = "HTTP/1.1 " + WebdavStatus.SC_OK + " " +
+                WebdavStatus.getStatusText(WebdavStatus.SC_OK);
+
+        // Generating href element
+        generatedXML.writeElement("D", "href", XMLWriter.OPENING);
+
+        String href = req.getContextPath() + req.getServletPath();
+        if ((href.endsWith("/")) && (path.startsWith("/")))
+            href += path.substring(1);
+        else
+            href += path;
+        if ((cacheEntry.context != null) && (!href.endsWith("/")))
+            href += "/";
+
+        generatedXML.writeText(rewriteUrl(href));
+
+        generatedXML.writeElement("D", "href", XMLWriter.CLOSING);
+
+        String resourceName = path;
+        int lastSlash = path.lastIndexOf('/');
+        if (lastSlash != -1)
+            resourceName = resourceName.substring(lastSlash + 1);
+
+        switch (type) {
+
+        case FIND_ALL_PROP :
+
+            generatedXML.writeElement("D", "propstat", XMLWriter.OPENING);
+            generatedXML.writeElement("D", "prop", XMLWriter.OPENING);
+
+            generatedXML.writeProperty("D", "creationdate",
+                    getISOCreationDate(cacheEntry.attributes.getCreation()));
+            generatedXML.writeElement("D", "displayname", XMLWriter.OPENING);
+            generatedXML.writeData(resourceName);
+            generatedXML.writeElement("D", "displayname", XMLWriter.CLOSING);
+            if (cacheEntry.resource != null) {
+                generatedXML.writeProperty
+                    ("D", "getlastmodified", FastHttpDateFormat.formatDate
+                           (cacheEntry.attributes.getLastModified(), null));
+                generatedXML.writeProperty
+                    ("D", "getcontentlength",
+                     String.valueOf(cacheEntry.attributes.getContentLength()));
+                String contentType = getServletContext().getMimeType
+                    (cacheEntry.name);
+                if (contentType != null) {
+                    generatedXML.writeProperty("D", "getcontenttype",
+                            contentType);
+                }
+                generatedXML.writeProperty("D", "getetag",
+                        cacheEntry.attributes.getETag());
+                generatedXML.writeElement("D", "resourcetype",
+                        XMLWriter.NO_CONTENT);
+            } else {
+                generatedXML.writeElement("D", "resourcetype",
+                        XMLWriter.OPENING);
+                generatedXML.writeElement("D", "collection",
+                        XMLWriter.NO_CONTENT);
+                generatedXML.writeElement("D", "resourcetype",
+                        XMLWriter.CLOSING);
+            }
+
+            generatedXML.writeProperty("D", "source", "");
+
+            String supportedLocks = "<D:lockentry>"
+                + "<D:lockscope><D:exclusive/></D:lockscope>"
+                + "<D:locktype><D:write/></D:locktype>"
+                + "</D:lockentry>" + "<D:lockentry>"
+                + "<D:lockscope><D:shared/></D:lockscope>"
+                + "<D:locktype><D:write/></D:locktype>"
+                + "</D:lockentry>";
+            generatedXML.writeElement("D", "supportedlock", XMLWriter.OPENING);
+            generatedXML.writeText(supportedLocks);
+            generatedXML.writeElement("D", "supportedlock", XMLWriter.CLOSING);
+
+            generateLockDiscovery(path, generatedXML);
+
+            generatedXML.writeElement("D", "prop", XMLWriter.CLOSING);
+            generatedXML.writeElement("D", "status", XMLWriter.OPENING);
+            generatedXML.writeText(status);
+            generatedXML.writeElement("D", "status", XMLWriter.CLOSING);
+            generatedXML.writeElement("D", "propstat", XMLWriter.CLOSING);
+
+            break;
+
+        case FIND_PROPERTY_NAMES :
+
+            generatedXML.writeElement("D", "propstat", XMLWriter.OPENING);
+            generatedXML.writeElement("D", "prop", XMLWriter.OPENING);
+
+            generatedXML.writeElement("D", "creationdate",
+                                      XMLWriter.NO_CONTENT);
+            generatedXML.writeElement("D", "displayname", XMLWriter.NO_CONTENT);
+            if (cacheEntry.resource != null) {
+                generatedXML.writeElement("D", "getcontentlanguage",
+                        XMLWriter.NO_CONTENT);
+                generatedXML.writeElement("D", "getcontentlength",
+                        XMLWriter.NO_CONTENT);
+                generatedXML.writeElement("D", "getcontenttype",
+                        XMLWriter.NO_CONTENT);
+                generatedXML.writeElement("D", "getetag", XMLWriter.NO_CONTENT);
+                generatedXML.writeElement("D", "getlastmodified",
+                        XMLWriter.NO_CONTENT);
+            }
+            generatedXML.writeElement("D", "resourcetype",
+                                      XMLWriter.NO_CONTENT);
+            generatedXML.writeElement("D", "source", XMLWriter.NO_CONTENT);
+            generatedXML.writeElement("D", "lockdiscovery",
+                                      XMLWriter.NO_CONTENT);
+
+            generatedXML.writeElement("D", "prop", XMLWriter.CLOSING);
+            generatedXML.writeElement("D", "status", XMLWriter.OPENING);
+            generatedXML.writeText(status);
+            generatedXML.writeElement("D", "status", XMLWriter.CLOSING);
+            generatedXML.writeElement("D", "propstat", XMLWriter.CLOSING);
+
+            break;
+
+        case FIND_BY_PROPERTY :
+
+            Vector<String> propertiesNotFound = new Vector<String>();
+
+            // Parse the list of properties
+
+            generatedXML.writeElement("D", "propstat", XMLWriter.OPENING);
+            generatedXML.writeElement("D", "prop", XMLWriter.OPENING);
+
+            Enumeration<String> properties = propertiesVector.elements();
+
+            while (properties.hasMoreElements()) {
+
+                String property = properties.nextElement();
+
+                if (property.equals("creationdate")) {
+                    generatedXML.writeProperty
+                        ("D", "creationdate",
+                         getISOCreationDate(cacheEntry.attributes.getCreation()));
+                } else if (property.equals("displayname")) {
+                    generatedXML.writeElement
+                        ("D", "displayname", XMLWriter.OPENING);
+                    generatedXML.writeData(resourceName);
+                    generatedXML.writeElement
+                        ("D", "displayname", XMLWriter.CLOSING);
+                } else if (property.equals("getcontentlanguage")) {
+                    if (cacheEntry.context != null) {
+                        propertiesNotFound.addElement(property);
+                    } else {
+                        generatedXML.writeElement("D", "getcontentlanguage",
+                                                  XMLWriter.NO_CONTENT);
+                    }
+                } else if (property.equals("getcontentlength")) {
+                    if (cacheEntry.context != null) {
+                        propertiesNotFound.addElement(property);
+                    } else {
+                        generatedXML.writeProperty
+                            ("D", "getcontentlength",
+                             (String.valueOf(cacheEntry.attributes.getContentLength())));
+                    }
+                } else if (property.equals("getcontenttype")) {
+                    if (cacheEntry.context != null) {
+                        propertiesNotFound.addElement(property);
+                    } else {
+                        generatedXML.writeProperty
+                            ("D", "getcontenttype",
+                             getServletContext().getMimeType
+                             (cacheEntry.name));
+                    }
+                } else if (property.equals("getetag")) {
+                    if (cacheEntry.context != null) {
+                        propertiesNotFound.addElement(property);
+                    } else {
+                        generatedXML.writeProperty
+                            ("D", "getetag", cacheEntry.attributes.getETag());
+                    }
+                } else if (property.equals("getlastmodified")) {
+                    if (cacheEntry.context != null) {
+                        propertiesNotFound.addElement(property);
+                    } else {
+                        generatedXML.writeProperty
+                            ("D", "getlastmodified", FastHttpDateFormat.formatDate
+                                    (cacheEntry.attributes.getLastModified(), null));
+                    }
+                } else if (property.equals("resourcetype")) {
+                    if (cacheEntry.context != null) {
+                        generatedXML.writeElement("D", "resourcetype",
+                                XMLWriter.OPENING);
+                        generatedXML.writeElement("D", "collection",
+                                XMLWriter.NO_CONTENT);
+                        generatedXML.writeElement("D", "resourcetype",
+                                XMLWriter.CLOSING);
+                    } else {
+                        generatedXML.writeElement("D", "resourcetype",
+                                XMLWriter.NO_CONTENT);
+                    }
+                } else if (property.equals("source")) {
+                    generatedXML.writeProperty("D", "source", "");
+                } else if (property.equals("supportedlock")) {
+                    supportedLocks = "<D:lockentry>"
+                        + "<D:lockscope><D:exclusive/></D:lockscope>"
+                        + "<D:locktype><D:write/></D:locktype>"
+                        + "</D:lockentry>" + "<D:lockentry>"
+                        + "<D:lockscope><D:shared/></D:lockscope>"
+                        + "<D:locktype><D:write/></D:locktype>"
+                        + "</D:lockentry>";
+                    generatedXML.writeElement("D", "supportedlock",
+                            XMLWriter.OPENING);
+                    generatedXML.writeText(supportedLocks);
+                    generatedXML.writeElement("D", "supportedlock",
+                            XMLWriter.CLOSING);
+                } else if (property.equals("lockdiscovery")) {
+                    if (!generateLockDiscovery(path, generatedXML))
+                        propertiesNotFound.addElement(property);
+                } else {
+                    propertiesNotFound.addElement(property);
+                }
+
+            }
+
+            generatedXML.writeElement("D", "prop", XMLWriter.CLOSING);
+            generatedXML.writeElement("D", "status", XMLWriter.OPENING);
+            generatedXML.writeText(status);
+            generatedXML.writeElement("D", "status", XMLWriter.CLOSING);
+            generatedXML.writeElement("D", "propstat", XMLWriter.CLOSING);
+
+            Enumeration<String> propertiesNotFoundList =
+                propertiesNotFound.elements();
+
+            if (propertiesNotFoundList.hasMoreElements()) {
+
+                status = "HTTP/1.1 " + WebdavStatus.SC_NOT_FOUND + " " +
+                        WebdavStatus.getStatusText(WebdavStatus.SC_NOT_FOUND);
+
+                generatedXML.writeElement("D", "propstat", XMLWriter.OPENING);
+                generatedXML.writeElement("D", "prop", XMLWriter.OPENING);
+
+                while (propertiesNotFoundList.hasMoreElements()) {
+                    generatedXML.writeElement
+                        ("D", propertiesNotFoundList.nextElement(),
+                         XMLWriter.NO_CONTENT);
+                }
+
+                generatedXML.writeElement("D", "prop", XMLWriter.CLOSING);
+                generatedXML.writeElement("D", "status", XMLWriter.OPENING);
+                generatedXML.writeText(status);
+                generatedXML.writeElement("D", "status", XMLWriter.CLOSING);
+                generatedXML.writeElement("D", "propstat", XMLWriter.CLOSING);
+
+            }
+
+            break;
+
+        }
+
+        generatedXML.writeElement("D", "response", XMLWriter.CLOSING);
+
+    }
+
+
+    /**
+     * Propfind helper method. Displays the properties of a lock-null resource.
+     *
+     * @param resources Resources object associated with this context
+     * @param generatedXML XML response to the Propfind request
+     * @param path Path of the current resource
+     * @param type Propfind type
+     * @param propertiesVector If the propfind type is find properties by
+     * name, then this Vector contains those properties
+     */
+    private void parseLockNullProperties(HttpServletRequest req,
+                                         XMLWriter generatedXML,
+                                         String path, int type,
+                                         Vector<String> propertiesVector) {
+
+        // Exclude any resource in the /WEB-INF and /META-INF subdirectories
+        if (isSpecialPath(path))
+            return;
+
+        // Retrieving the lock associated with the lock-null resource
+        LockInfo lock = resourceLocks.get(path);
+
+        if (lock == null)
+            return;
+
+        generatedXML.writeElement("D", "response", XMLWriter.OPENING);
+        String status = "HTTP/1.1 " + WebdavStatus.SC_OK + " " +
+                WebdavStatus.getStatusText(WebdavStatus.SC_OK);
+
+        // Generating href element
+        generatedXML.writeElement("D", "href", XMLWriter.OPENING);
+
+        String absoluteUri = req.getRequestURI();
+        String relativePath = getRelativePath(req);
+        String toAppend = path.substring(relativePath.length());
+        if (!toAppend.startsWith("/"))
+            toAppend = "/" + toAppend;
+
+        generatedXML.writeText(rewriteUrl(RequestUtil.normalize(
+                absoluteUri + toAppend)));
+
+        generatedXML.writeElement("D", "href", XMLWriter.CLOSING);
+
+        String resourceName = path;
+        int lastSlash = path.lastIndexOf('/');
+        if (lastSlash != -1)
+            resourceName = resourceName.substring(lastSlash + 1);
+
+        switch (type) {
+
+        case FIND_ALL_PROP :
+
+            generatedXML.writeElement("D", "propstat", XMLWriter.OPENING);
+            generatedXML.writeElement("D", "prop", XMLWriter.OPENING);
+
+            generatedXML.writeProperty("D", "creationdate",
+                    getISOCreationDate(lock.creationDate.getTime()));
+            generatedXML.writeElement("D", "displayname", XMLWriter.OPENING);
+            generatedXML.writeData(resourceName);
+            generatedXML.writeElement("D", "displayname", XMLWriter.CLOSING);
+            generatedXML.writeProperty("D", "getlastmodified",
+                                       FastHttpDateFormat.formatDate
+                                       (lock.creationDate.getTime(), null));
+            generatedXML.writeProperty("D", "getcontentlength",
+                    String.valueOf(0));
+            generatedXML.writeProperty("D", "getcontenttype", "");
+            generatedXML.writeProperty("D", "getetag", "");
+            generatedXML.writeElement("D", "resourcetype", XMLWriter.OPENING);
+            generatedXML.writeElement("D", "lock-null", XMLWriter.NO_CONTENT);
+            generatedXML.writeElement("D", "resourcetype", XMLWriter.CLOSING);
+
+            generatedXML.writeProperty("D", "source", "");
+
+            String supportedLocks = "<D:lockentry>"
+                + "<D:lockscope><D:exclusive/></D:lockscope>"
+                + "<D:locktype><D:write/></D:locktype>"
+                + "</D:lockentry>" + "<D:lockentry>"
+                + "<D:lockscope><D:shared/></D:lockscope>"
+                + "<D:locktype><D:write/></D:locktype>"
+                + "</D:lockentry>";
+            generatedXML.writeElement("D", "supportedlock", XMLWriter.OPENING);
+            generatedXML.writeText(supportedLocks);
+            generatedXML.writeElement("D", "supportedlock", XMLWriter.CLOSING);
+
+            generateLockDiscovery(path, generatedXML);
+
+            generatedXML.writeElement("D", "prop", XMLWriter.CLOSING);
+            generatedXML.writeElement("D", "status", XMLWriter.OPENING);
+            generatedXML.writeText(status);
+            generatedXML.writeElement("D", "status", XMLWriter.CLOSING);
+            generatedXML.writeElement("D", "propstat", XMLWriter.CLOSING);
+
+            break;
+
+        case FIND_PROPERTY_NAMES :
+
+            generatedXML.writeElement("D", "propstat", XMLWriter.OPENING);
+            generatedXML.writeElement("D", "prop", XMLWriter.OPENING);
+
+            generatedXML.writeElement("D", "creationdate",
+                                      XMLWriter.NO_CONTENT);
+            generatedXML.writeElement("D", "displayname", XMLWriter.NO_CONTENT);
+            generatedXML.writeElement("D", "getcontentlanguage",
+                                      XMLWriter.NO_CONTENT);
+            generatedXML.writeElement("D", "getcontentlength",
+                                      XMLWriter.NO_CONTENT);
+            generatedXML.writeElement("D", "getcontenttype",
+                                      XMLWriter.NO_CONTENT);
+            generatedXML.writeElement("D", "getetag", XMLWriter.NO_CONTENT);
+            generatedXML.writeElement("D", "getlastmodified",
+                                      XMLWriter.NO_CONTENT);
+            generatedXML.writeElement("D", "resourcetype",
+                                      XMLWriter.NO_CONTENT);
+            generatedXML.writeElement("D", "source", XMLWriter.NO_CONTENT);
+            generatedXML.writeElement("D", "lockdiscovery",
+                                      XMLWriter.NO_CONTENT);
+
+            generatedXML.writeElement("D", "prop", XMLWriter.CLOSING);
+            generatedXML.writeElement("D", "status", XMLWriter.OPENING);
+            generatedXML.writeText(status);
+            generatedXML.writeElement("D", "status", XMLWriter.CLOSING);
+            generatedXML.writeElement("D", "propstat", XMLWriter.CLOSING);
+
+            break;
+
+        case FIND_BY_PROPERTY :
+
+            Vector<String> propertiesNotFound = new Vector<String>();
+
+            // Parse the list of properties
+
+            generatedXML.writeElement("D", "propstat", XMLWriter.OPENING);
+            generatedXML.writeElement("D", "prop", XMLWriter.OPENING);
+
+            Enumeration<String> properties = propertiesVector.elements();
+
+            while (properties.hasMoreElements()) {
+
+                String property = properties.nextElement();
+
+                if (property.equals("creationdate")) {
+                    generatedXML.writeProperty("D", "creationdate",
+                            getISOCreationDate(lock.creationDate.getTime()));
+                } else if (property.equals("displayname")) {
+                    generatedXML.writeElement("D", "displayname",
+                            XMLWriter.OPENING);
+                    generatedXML.writeData(resourceName);
+                    generatedXML.writeElement("D", "displayname",
+                            XMLWriter.CLOSING);
+                } else if (property.equals("getcontentlanguage")) {
+                    generatedXML.writeElement("D", "getcontentlanguage",
+                            XMLWriter.NO_CONTENT);
+                } else if (property.equals("getcontentlength")) {
+                    generatedXML.writeProperty("D", "getcontentlength",
+                            (String.valueOf(0)));
+                } else if (property.equals("getcontenttype")) {
+                    generatedXML.writeProperty("D", "getcontenttype", "");
+                } else if (property.equals("getetag")) {
+                    generatedXML.writeProperty("D", "getetag", "");
+                } else if (property.equals("getlastmodified")) {
+                    generatedXML.writeProperty
+                        ("D", "getlastmodified",
+                          FastHttpDateFormat.formatDate
+                         (lock.creationDate.getTime(), null));
+                } else if (property.equals("resourcetype")) {
+                    generatedXML.writeElement("D", "resourcetype",
+                            XMLWriter.OPENING);
+                    generatedXML.writeElement("D", "lock-null",
+                            XMLWriter.NO_CONTENT);
+                    generatedXML.writeElement("D", "resourcetype",
+                            XMLWriter.CLOSING);
+                } else if (property.equals("source")) {
+                    generatedXML.writeProperty("D", "source", "");
+                } else if (property.equals("supportedlock")) {
+                    supportedLocks = "<D:lockentry>"
+                        + "<D:lockscope><D:exclusive/></D:lockscope>"
+                        + "<D:locktype><D:write/></D:locktype>"
+                        + "</D:lockentry>" + "<D:lockentry>"
+                        + "<D:lockscope><D:shared/></D:lockscope>"
+                        + "<D:locktype><D:write/></D:locktype>"
+                        + "</D:lockentry>";
+                    generatedXML.writeElement("D", "supportedlock",
+                            XMLWriter.OPENING);
+                    generatedXML.writeText(supportedLocks);
+                    generatedXML.writeElement("D", "supportedlock",
+                            XMLWriter.CLOSING);
+                } else if (property.equals("lockdiscovery")) {
+                    if (!generateLockDiscovery(path, generatedXML))
+                        propertiesNotFound.addElement(property);
+                } else {
+                    propertiesNotFound.addElement(property);
+                }
+
+            }
+
+            generatedXML.writeElement("D", "prop", XMLWriter.CLOSING);
+            generatedXML.writeElement("D", "status", XMLWriter.OPENING);
+            generatedXML.writeText(status);
+            generatedXML.writeElement("D", "status", XMLWriter.CLOSING);
+            generatedXML.writeElement("D", "propstat", XMLWriter.CLOSING);
+
+            Enumeration<String> propertiesNotFoundList = propertiesNotFound.elements();
+
+            if (propertiesNotFoundList.hasMoreElements()) {
+
+                status = "HTTP/1.1 " + WebdavStatus.SC_NOT_FOUND + " " +
+                        WebdavStatus.getStatusText(WebdavStatus.SC_NOT_FOUND);
+
+                generatedXML.writeElement("D", "propstat", XMLWriter.OPENING);
+                generatedXML.writeElement("D", "prop", XMLWriter.OPENING);
+
+                while (propertiesNotFoundList.hasMoreElements()) {
+                    generatedXML.writeElement
+                        ("D", propertiesNotFoundList.nextElement(),
+                         XMLWriter.NO_CONTENT);
+                }
+
+                generatedXML.writeElement("D", "prop", XMLWriter.CLOSING);
+                generatedXML.writeElement("D", "status", XMLWriter.OPENING);
+                generatedXML.writeText(status);
+                generatedXML.writeElement("D", "status", XMLWriter.CLOSING);
+                generatedXML.writeElement("D", "propstat", XMLWriter.CLOSING);
+
+            }
+
+            break;
+
+        }
+
+        generatedXML.writeElement("D", "response", XMLWriter.CLOSING);
+
+    }
+
+
+    /**
+     * Print the lock discovery information associated with a path.
+     *
+     * @param path Path
+     * @param generatedXML XML data to which the locks info will be appended
+     * @return true if at least one lock was displayed
+     */
+    private boolean generateLockDiscovery
+        (String path, XMLWriter generatedXML) {
+
+        LockInfo resourceLock = resourceLocks.get(path);
+        Enumeration<LockInfo> collectionLocksList = collectionLocks.elements();
+
+        boolean wroteStart = false;
+
+        if (resourceLock != null) {
+            wroteStart = true;
+            generatedXML.writeElement("D", "lockdiscovery", XMLWriter.OPENING);
+            resourceLock.toXML(generatedXML);
+        }
+
+        while (collectionLocksList.hasMoreElements()) {
+            LockInfo currentLock = collectionLocksList.nextElement();
+            if (path.startsWith(currentLock.path)) {
+                if (!wroteStart) {
+                    wroteStart = true;
+                    generatedXML.writeElement("D", "lockdiscovery",
+                            XMLWriter.OPENING);
+                }
+                currentLock.toXML(generatedXML);
+            }
+        }
+
+        if (wroteStart) {
+            generatedXML.writeElement("D", "lockdiscovery", XMLWriter.CLOSING);
+        } else {
+            return false;
+        }
+
+        return true;
+
+    }
+
+
+    /**
+     * Get creation date in ISO format.
+     */
+    private String getISOCreationDate(long creationDate) {
+        StringBuilder creationDateValue = new StringBuilder
+            (creationDateFormat.format
+             (new Date(creationDate)));
+        /*
+        int offset = Calendar.getInstance().getTimeZone().getRawOffset()
+            / 3600000; // FIXME ?
+        if (offset < 0) {
+            creationDateValue.append("-");
+            offset = -offset;
+        } else if (offset > 0) {
+            creationDateValue.append("+");
+        }
+        if (offset != 0) {
+            if (offset < 10)
+                creationDateValue.append("0");
+            creationDateValue.append(offset + ":00");
+        } else {
+            creationDateValue.append("Z");
+        }
+        */
+        return creationDateValue.toString();
+    }
+
+    /**
+     * Determines the methods normally allowed for the resource.
+     *
+     */
+    private StringBuilder determineMethodsAllowed(DirContext dirContext,
+                                                 HttpServletRequest req) {
+
+        StringBuilder methodsAllowed = new StringBuilder();
+        boolean exists = true;
+        Object object = null;
+        try {
+            String path = getRelativePath(req);
+
+            object = dirContext.lookup(path);
+        } catch (NamingException e) {
+            exists = false;
+        }
+
+        if (!exists) {
+            methodsAllowed.append("OPTIONS, MKCOL, PUT, LOCK");
+            return methodsAllowed;
+        }
+
+        methodsAllowed.append("OPTIONS, GET, HEAD, POST, DELETE, TRACE");
+        methodsAllowed.append(", PROPPATCH, COPY, MOVE, LOCK, UNLOCK");
+
+        if (listings) {
+            methodsAllowed.append(", PROPFIND");
+        }
+
+        if (!(object instanceof DirContext)) {
+            methodsAllowed.append(", PUT");
+        }
+
+        return methodsAllowed;
+    }
+
+    // --------------------------------------------------  LockInfo Inner Class
+
+
+    /**
+     * Holds a lock information.
+     */
+    private class LockInfo {
+
+
+        // -------------------------------------------------------- Constructor
+
+
+        /**
+         * Constructor.
+         */
+        public LockInfo() {
+            // Ignore
+        }
+
+
+        // ------------------------------------------------- Instance Variables
+
+
+        String path = "/";
+        String type = "write";
+        String scope = "exclusive";
+        int depth = 0;
+        String owner = "";
+        Vector<String> tokens = new Vector<String>();
+        long expiresAt = 0;
+        Date creationDate = new Date();
+
+
+        // ----------------------------------------------------- Public Methods
+
+
+        /**
+         * Get a String representation of this lock token.
+         */
+        @Override
+        public String toString() {
+
+            StringBuilder result =  new StringBuilder("Type:");
+            result.append(type);
+            result.append("\nScope:");
+            result.append(scope);
+            result.append("\nDepth:");
+            result.append(depth);
+            result.append("\nOwner:");
+            result.append(owner);
+            result.append("\nExpiration:");
+            result.append(FastHttpDateFormat.formatDate(expiresAt, null));
+            Enumeration<String> tokensList = tokens.elements();
+            while (tokensList.hasMoreElements()) {
+                result.append("\nToken:");
+                result.append(tokensList.nextElement());
+            }
+            result.append("\n");
+            return result.toString();
+        }
+
+
+        /**
+         * Return true if the lock has expired.
+         */
+        public boolean hasExpired() {
+            return (System.currentTimeMillis() > expiresAt);
+        }
+
+
+        /**
+         * Return true if the lock is exclusive.
+         */
+        public boolean isExclusive() {
+
+            return (scope.equals("exclusive"));
+
+        }
+
+
+        /**
+         * Get an XML representation of this lock token. This method will
+         * append an XML fragment to the given XML writer.
+         */
+        public void toXML(XMLWriter generatedXML) {
+
+            generatedXML.writeElement("D", "activelock", XMLWriter.OPENING);
+
+            generatedXML.writeElement("D", "locktype", XMLWriter.OPENING);
+            generatedXML.writeElement("D", type, XMLWriter.NO_CONTENT);
+            generatedXML.writeElement("D", "locktype", XMLWriter.CLOSING);
+
+            generatedXML.writeElement("D", "lockscope", XMLWriter.OPENING);
+            generatedXML.writeElement("D", scope, XMLWriter.NO_CONTENT);
+            generatedXML.writeElement("D", "lockscope", XMLWriter.CLOSING);
+
+            generatedXML.writeElement("D", "depth", XMLWriter.OPENING);
+            if (depth == maxDepth) {
+                generatedXML.writeText("Infinity");
+            } else {
+                generatedXML.writeText("0");
+            }
+            generatedXML.writeElement("D", "depth", XMLWriter.CLOSING);
+
+            generatedXML.writeElement("D", "owner", XMLWriter.OPENING);
+            generatedXML.writeText(owner);
+            generatedXML.writeElement("D", "owner", XMLWriter.CLOSING);
+
+            generatedXML.writeElement("D", "timeout", XMLWriter.OPENING);
+            long timeout = (expiresAt - System.currentTimeMillis()) / 1000;
+            generatedXML.writeText("Second-" + timeout);
+            generatedXML.writeElement("D", "timeout", XMLWriter.CLOSING);
+
+            generatedXML.writeElement("D", "locktoken", XMLWriter.OPENING);
+            Enumeration<String> tokensList = tokens.elements();
+            while (tokensList.hasMoreElements()) {
+                generatedXML.writeElement("D", "href", XMLWriter.OPENING);
+                generatedXML.writeText("opaquelocktoken:"
+                                       + tokensList.nextElement());
+                generatedXML.writeElement("D", "href", XMLWriter.CLOSING);
+            }
+            generatedXML.writeElement("D", "locktoken", XMLWriter.CLOSING);
+
+            generatedXML.writeElement("D", "activelock", XMLWriter.CLOSING);
+
+        }
+
+
+    }
+
+
+    // --------------------------------------------- WebdavResolver Inner Class
+    /**
+     * Work around for XML parsers that don't fully respect
+     * {@link DocumentBuilderFactory#setExpandEntityReferences(boolean)} when
+     * called with <code>false</code>. External references are filtered out for
+     * security reasons. See CVE-2007-5461.
+     */
+    private static class WebdavResolver implements EntityResolver {
+        private ServletContext context;
+        
+        public WebdavResolver(ServletContext theContext) {
+            context = theContext;
+        }
+     
+        @Override
+        public InputSource resolveEntity (String publicId, String systemId) {
+            context.log(sm.getString("webdavservlet.enternalEntityIgnored",
+                    publicId, systemId));
+            return new InputSource(
+                    new StringReader("Ignored external entity"));
+        }
+    }
+}
+
+
+// --------------------------------------------------------  WebdavStatus Class
+
+
+/**
+ * Wraps the HttpServletResponse class to abstract the
+ * specific protocol used.  To support other protocols
+ * we would only need to modify this class and the
+ * WebDavRetCode classes.
+ *
+ * @author              Marc Eaddy
+ * @version             1.0, 16 Nov 1997
+ */
+class WebdavStatus {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * This Hashtable contains the mapping of HTTP and WebDAV
+     * status codes to descriptive text.  This is a static
+     * variable.
+     */
+    private static Hashtable<Integer,String> mapStatusCodes =
+        new Hashtable<Integer,String>();
+
+
+    // ------------------------------------------------------ HTTP Status Codes
+
+
+    /**
+     * Status code (200) indicating the request succeeded normally.
+     */
+    public static final int SC_OK = HttpServletResponse.SC_OK;
+
+
+    /**
+     * Status code (201) indicating the request succeeded and created
+     * a new resource on the server.
+     */
+    public static final int SC_CREATED = HttpServletResponse.SC_CREATED;
+
+
+    /**
+     * Status code (202) indicating that a request was accepted for
+     * processing, but was not completed.
+     */
+    public static final int SC_ACCEPTED = HttpServletResponse.SC_ACCEPTED;
+
+
+    /**
+     * Status code (204) indicating that the request succeeded but that
+     * there was no new information to return.
+     */
+    public static final int SC_NO_CONTENT = HttpServletResponse.SC_NO_CONTENT;
+
+
+    /**
+     * 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 =
+        HttpServletResponse.SC_MOVED_PERMANENTLY;
+
+
+    /**
+     * 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.
+     */
+    public static final int SC_MOVED_TEMPORARILY =
+        HttpServletResponse.SC_MOVED_TEMPORARILY;
+
+
+    /**
+     * 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 =
+        HttpServletResponse.SC_NOT_MODIFIED;
+
+
+    /**
+     * Status code (400) indicating the request sent by the client was
+     * syntactically incorrect.
+     */
+    public static final int SC_BAD_REQUEST =
+        HttpServletResponse.SC_BAD_REQUEST;
+
+
+    /**
+     * Status code (401) indicating that the request requires HTTP
+     * authentication.
+     */
+    public static final int SC_UNAUTHORIZED =
+        HttpServletResponse.SC_UNAUTHORIZED;
+
+
+    /**
+     * Status code (403) indicating the server understood the request
+     * but refused to fulfill it.
+     */
+    public static final int SC_FORBIDDEN = HttpServletResponse.SC_FORBIDDEN;
+
+
+    /**
+     * Status code (404) indicating that the requested resource is not
+     * available.
+     */
+    public static final int SC_NOT_FOUND = HttpServletResponse.SC_NOT_FOUND;
+
+
+    /**
+     * Status code (500) indicating an error inside the HTTP service
+     * which prevented it from fulfilling the request.
+     */
+    public static final int SC_INTERNAL_SERVER_ERROR =
+        HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+
+
+    /**
+     * Status code (501) indicating the HTTP service does not support
+     * the functionality needed to fulfill the request.
+     */
+    public static final int SC_NOT_IMPLEMENTED =
+        HttpServletResponse.SC_NOT_IMPLEMENTED;
+
+
+    /**
+     * 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 =
+        HttpServletResponse.SC_BAD_GATEWAY;
+
+
+    /**
+     * Status code (503) indicating that the HTTP service is
+     * temporarily overloaded, and unable to handle the request.
+     */
+    public static final int SC_SERVICE_UNAVAILABLE =
+        HttpServletResponse.SC_SERVICE_UNAVAILABLE;
+
+
+    /**
+     * Status code (100) indicating the client may continue with
+     * its request.  This interim response is used to inform the
+     * client that the initial part of the request has been
+     * received and has not yet been rejected by the server.
+     */
+    public static final int SC_CONTINUE = 100;
+
+
+    /**
+     * Status code (405) indicating the method specified is not
+     * allowed for the resource.
+     */
+    public static final int SC_METHOD_NOT_ALLOWED = 405;
+
+
+    /**
+     * 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 (412) indicating 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 the server is refusing to
+     * process a request because the request entity is larger
+     * than the server is willing or able to process.
+     */
+    public static final int SC_REQUEST_TOO_LONG = 413;
+
+
+    /**
+     * Status code (415) indicating 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;
+
+
+    // -------------------------------------------- Extended WebDav status code
+
+
+    /**
+     * Status code (207) indicating that the response requires
+     * providing status for multiple independent operations.
+     */
+    public static final int SC_MULTI_STATUS = 207;
+    // This one collides with HTTP 1.1
+    // "207 Partial Update OK"
+
+
+    /**
+     * Status code (418) indicating the entity body submitted with
+     * the PATCH method was not understood by the resource.
+     */
+    public static final int SC_UNPROCESSABLE_ENTITY = 418;
+    // This one collides with HTTP 1.1
+    // "418 Reauthentication Required"
+
+
+    /**
+     * Status code (419) indicating that the resource does not have
+     * sufficient space to record the state of the resource after the
+     * execution of this method.
+     */
+    public static final int SC_INSUFFICIENT_SPACE_ON_RESOURCE = 419;
+    // This one collides with HTTP 1.1
+    // "419 Proxy Reauthentication Required"
+
+
+    /**
+     * Status code (420) indicating the method was not executed on
+     * a particular resource within its scope because some part of
+     * the method's execution failed causing the entire method to be
+     * aborted.
+     */
+    public static final int SC_METHOD_FAILURE = 420;
+
+
+    /**
+     * Status code (423) indicating the destination resource of a
+     * method is locked, and either the request did not contain a
+     * valid Lock-Info header, or the Lock-Info header identifies
+     * a lock held by another principal.
+     */
+    public static final int SC_LOCKED = 423;
+
+
+    // ------------------------------------------------------------ Initializer
+
+
+    static {
+        // HTTP 1.0 status Code
+        addStatusCodeMap(SC_OK, "OK");
+        addStatusCodeMap(SC_CREATED, "Created");
+        addStatusCodeMap(SC_ACCEPTED, "Accepted");
+        addStatusCodeMap(SC_NO_CONTENT, "No Content");
+        addStatusCodeMap(SC_MOVED_PERMANENTLY, "Moved Permanently");
+        addStatusCodeMap(SC_MOVED_TEMPORARILY, "Moved Temporarily");
+        addStatusCodeMap(SC_NOT_MODIFIED, "Not Modified");
+        addStatusCodeMap(SC_BAD_REQUEST, "Bad Request");
+        addStatusCodeMap(SC_UNAUTHORIZED, "Unauthorized");
+        addStatusCodeMap(SC_FORBIDDEN, "Forbidden");
+        addStatusCodeMap(SC_NOT_FOUND, "Not Found");
+        addStatusCodeMap(SC_INTERNAL_SERVER_ERROR, "Internal Server Error");
+        addStatusCodeMap(SC_NOT_IMPLEMENTED, "Not Implemented");
+        addStatusCodeMap(SC_BAD_GATEWAY, "Bad Gateway");
+        addStatusCodeMap(SC_SERVICE_UNAVAILABLE, "Service Unavailable");
+        addStatusCodeMap(SC_CONTINUE, "Continue");
+        addStatusCodeMap(SC_METHOD_NOT_ALLOWED, "Method Not Allowed");
+        addStatusCodeMap(SC_CONFLICT, "Conflict");
+        addStatusCodeMap(SC_PRECONDITION_FAILED, "Precondition Failed");
+        addStatusCodeMap(SC_REQUEST_TOO_LONG, "Request Too Long");
+        addStatusCodeMap(SC_UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type");
+        // WebDav Status Codes
+        addStatusCodeMap(SC_MULTI_STATUS, "Multi-Status");
+        addStatusCodeMap(SC_UNPROCESSABLE_ENTITY, "Unprocessable Entity");
+        addStatusCodeMap(SC_INSUFFICIENT_SPACE_ON_RESOURCE,
+                         "Insufficient Space On Resource");
+        addStatusCodeMap(SC_METHOD_FAILURE, "Method Failure");
+        addStatusCodeMap(SC_LOCKED, "Locked");
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Returns the HTTP status text for the HTTP or WebDav status code
+     * specified by looking it up in the static mapping.  This is a
+     * static function.
+     *
+     * @param   nHttpStatusCode [IN] HTTP or WebDAV status code
+     * @return  A string with a short descriptive phrase for the
+     *                  HTTP status code (e.g., "OK").
+     */
+    public static String getStatusText(int nHttpStatusCode) {
+        Integer intKey = Integer.valueOf(nHttpStatusCode);
+
+        if (!mapStatusCodes.containsKey(intKey)) {
+            return "";
+        } else {
+            return mapStatusCodes.get(intKey);
+        }
+    }
+
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Adds a new status code -> status text mapping.  This is a static
+     * method because the mapping is a static variable.
+     *
+     * @param   nKey    [IN] HTTP or WebDAV status code
+     * @param   strVal  [IN] HTTP status text
+     */
+    private static void addStatusCodeMap(int nKey, String strVal) {
+        mapStatusCodes.put(Integer.valueOf(nKey), strVal);
+    }
+
+}
+
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/package.html b/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/package.html
new file mode 100644
index 0000000..c4c7527
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/servlets/package.html
@@ -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.
+-->
+<body>
+
+<p>This package contains <code>Servlets</code> that implement some of the
+standard functionality provided by the Catalina servlet container.  Because
+these servlets are in the <code>org.apache.catalina</code> package hierarchy,
+they are in the privileged position of being able to reference internal server
+data structures, which application level servlets are prevented from
+accessing (by the application class loader implementation).</p>
+
+<p>To the extent that these servlets depend upon internal Catalina data
+structures, they are obviously not portable to other servlet container
+environments.  However, they can be used as models for creating application
+level servlets that provide similar capabilities -- most obviously the
+<a href="DefaultServlet.html">DefaultServlet</a> implementation, which
+serves static resources when Catalina runs stand-alone.</p>
+
+</body>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/session/Constants.java b/bundles/org.apache.tomcat/src/org/apache/catalina/session/Constants.java
new file mode 100644
index 0000000..2454687
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/session/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.session;
+
+/**
+ * Manifest constants for the <code>org.apache.catalina.session</code>
+ * package.
+ *
+ * @author Craig R. McClanahan
+ */
+
+public class Constants {
+
+    public static final String Package = "org.apache.catalina.session";
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/session/FileStore.java b/bundles/org.apache.tomcat/src/org/apache/catalina/session/FileStore.java
new file mode 100644
index 0000000..363938a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/session/FileStore.java
@@ -0,0 +1,450 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.session;
+
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+
+import javax.servlet.ServletContext;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Context;
+import org.apache.catalina.Loader;
+import org.apache.catalina.Session;
+import org.apache.catalina.util.CustomObjectInputStream;
+
+
+/**
+ * Concrete implementation of the <b>Store</b> interface that utilizes
+ * a file per saved Session in a configured directory.  Sessions that are
+ * saved are still subject to being expired based on inactivity.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: FileStore.java,v 1.1 2011/06/28 21:08:18 rherrmann Exp $
+ */
+
+public final class FileStore extends StoreBase {
+
+
+    // ----------------------------------------------------- Constants
+
+
+    /**
+     * The extension to use for serialized session filenames.
+     */
+    private static final String FILE_EXT = ".session";
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The pathname of the directory in which Sessions are stored.
+     * This may be an absolute pathname, or a relative path that is
+     * resolved against the temporary work directory for this application.
+     */
+    private String directory = ".";
+
+
+    /**
+     * A File representing the directory in which Sessions are stored.
+     */
+    private File directoryFile = null;
+
+
+    /**
+     * The descriptive information about this implementation.
+     */
+    private static final String info = "FileStore/1.0";
+
+    /**
+     * Name to register for this Store, used for logging.
+     */
+    private static final String storeName = "fileStore";
+
+    /**
+     * Name to register for the background thread.
+     */
+    private static final String threadName = "FileStore";
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the directory path for this Store.
+     */
+    public String getDirectory() {
+
+        return (directory);
+
+    }
+
+
+    /**
+     * Set the directory path for this Store.
+     *
+     * @param path The new directory path
+     */
+    public void setDirectory(String path) {
+
+        String oldDirectory = this.directory;
+        this.directory = path;
+        this.directoryFile = null;
+        support.firePropertyChange("directory", oldDirectory,
+                                   this.directory);
+
+    }
+
+
+    /**
+     * Return descriptive information about this Store 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 thread name for this Store.
+     */
+    public String getThreadName() {
+        return(threadName);
+    }
+
+    /**
+     * Return the name for this Store, used for logging.
+     */
+    @Override
+    public String getStoreName() {
+        return(storeName);
+    }
+
+
+    /**
+     * Return the number of Sessions present in this Store.
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public int getSize() throws IOException {
+
+        // Acquire the list of files in our storage directory
+        File file = directory();
+        if (file == null) {
+            return (0);
+        }
+        String files[] = file.list();
+
+        // Figure out which files are sessions
+        int keycount = 0;
+        for (int i = 0; i < files.length; i++) {
+            if (files[i].endsWith(FILE_EXT)) {
+                keycount++;
+            }
+        }
+        return (keycount);
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Remove all of the Sessions in this Store.
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public void clear()
+        throws IOException {
+
+        String[] keys = keys();
+        for (int i = 0; i < keys.length; i++) {
+            remove(keys[i]);
+        }
+
+    }
+
+
+    /**
+     * Return an array containing the session identifiers of all Sessions
+     * currently saved in this Store.  If there are no such Sessions, a
+     * zero-length array is returned.
+     *
+     * @exception IOException if an input/output error occurred
+     */
+    @Override
+    public String[] keys() throws IOException {
+
+        // Acquire the list of files in our storage directory
+        File file = directory();
+        if (file == null) {
+            return (new String[0]);
+        }
+
+        String files[] = file.list();
+        
+        // Bugzilla 32130
+        if((files == null) || (files.length < 1)) {
+            return (new String[0]);
+        }
+
+        // Build and return the list of session identifiers
+        ArrayList<String> list = new ArrayList<String>();
+        int n = FILE_EXT.length();
+        for (int i = 0; i < files.length; i++) {
+            if (files[i].endsWith(FILE_EXT)) {
+                list.add(files[i].substring(0, files[i].length() - n));
+            }
+        }
+        return list.toArray(new String[list.size()]);
+
+    }
+
+
+    /**
+     * Load and return the Session associated with the specified session
+     * identifier from this Store, without removing it.  If there is no
+     * such stored Session, return <code>null</code>.
+     *
+     * @param id Session identifier of the session to load
+     *
+     * @exception ClassNotFoundException if a deserialization error occurs
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public Session load(String id)
+        throws ClassNotFoundException, IOException {
+
+        // Open an input stream to the specified pathname, if any
+        File file = file(id);
+        if (file == null) {
+            return (null);
+        }
+
+        if (! file.exists()) {
+            return (null);
+        }
+        if (manager.getContainer().getLogger().isDebugEnabled()) {
+            manager.getContainer().getLogger().debug(sm.getString(getStoreName()+".loading",
+                             id, file.getAbsolutePath()));
+        }
+
+        FileInputStream fis = null;
+        BufferedInputStream bis = null;
+        ObjectInputStream ois = null;
+        Loader loader = null;
+        ClassLoader classLoader = null;
+        try {
+            fis = new FileInputStream(file.getAbsolutePath());
+            bis = new BufferedInputStream(fis);
+            Container container = manager.getContainer();
+            if (container != null)
+                loader = container.getLoader();
+            if (loader != null)
+                classLoader = loader.getClassLoader();
+            if (classLoader != null)
+                ois = new CustomObjectInputStream(bis, classLoader);
+            else
+                ois = new ObjectInputStream(bis);
+        } catch (FileNotFoundException e) {
+            if (manager.getContainer().getLogger().isDebugEnabled())
+                manager.getContainer().getLogger().debug("No persisted data file found");
+            return (null);
+        } catch (IOException e) {
+            if (bis != null) {
+                try {
+                    bis.close();
+                } catch (IOException f) {
+                    // Ignore
+                }
+            }
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (IOException f) {
+                    // Ignore
+                }
+            }
+            throw e;
+        }
+
+        try {
+            StandardSession session =
+                (StandardSession) manager.createEmptySession();
+            session.readObjectData(ois);
+            session.setManager(manager);
+            return (session);
+        } finally {
+            // Close the input stream
+            try {
+                ois.close();
+            } catch (IOException f) {
+                // Ignore
+            }
+        }
+    }
+
+
+    /**
+     * Remove the Session with the specified session identifier from
+     * this Store, if present.  If no such Session is present, this method
+     * takes no action.
+     *
+     * @param id Session identifier of the Session to be removed
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public void remove(String id) throws IOException {
+
+        File file = file(id);
+        if (file == null) {
+            return;
+        }
+        if (manager.getContainer().getLogger().isDebugEnabled()) {
+            manager.getContainer().getLogger().debug(sm.getString(getStoreName()+".removing",
+                             id, file.getAbsolutePath()));
+        }
+        file.delete();
+
+    }
+
+
+    /**
+     * Save the specified Session into this Store.  Any previously saved
+     * information for the associated session identifier is replaced.
+     *
+     * @param session Session to be saved
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public void save(Session session) throws IOException {
+
+        // Open an output stream to the specified pathname, if any
+        File file = file(session.getIdInternal());
+        if (file == null) {
+            return;
+        }
+        if (manager.getContainer().getLogger().isDebugEnabled()) {
+            manager.getContainer().getLogger().debug(sm.getString(getStoreName()+".saving",
+                             session.getIdInternal(), file.getAbsolutePath()));
+        }
+        FileOutputStream fos = null;
+        ObjectOutputStream oos = null;
+        try {
+            fos = new FileOutputStream(file.getAbsolutePath());
+            oos = new ObjectOutputStream(new BufferedOutputStream(fos));
+        } catch (IOException e) {
+            if (fos != null) {
+                try {
+                    fos.close();
+                } catch (IOException f) {
+                    // Ignore
+                }
+            }
+            throw e;
+        }
+
+        try {
+            ((StandardSession)session).writeObjectData(oos);
+        } finally {
+            oos.close();
+        }
+
+    }
+
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Return a File object representing the pathname to our
+     * session persistence directory, if any.  The directory will be
+     * created if it does not already exist.
+     */
+    private File directory() {
+
+        if (this.directory == null) {
+            return (null);
+        }
+        if (this.directoryFile != null) {
+            // NOTE:  Race condition is harmless, so do not synchronize
+            return (this.directoryFile);
+        }
+        File file = new File(this.directory);
+        if (!file.isAbsolute()) {
+            Container container = manager.getContainer();
+            if (container instanceof Context) {
+                ServletContext servletContext =
+                    ((Context) container).getServletContext();
+                File work = (File)
+                    servletContext.getAttribute(ServletContext.TEMPDIR);
+                file = new File(work, this.directory);
+            } else {
+                throw new IllegalArgumentException
+                    ("Parent Container is not a Context");
+            }
+        }
+        if (!file.exists() || !file.isDirectory()) {
+            file.delete();
+            file.mkdirs();
+        }
+        this.directoryFile = file;
+        return (file);
+
+    }
+
+
+    /**
+     * Return a File object representing the pathname to our
+     * session persistence file, if any.
+     *
+     * @param id The ID of the Session to be retrieved. This is
+     *    used in the file naming.
+     */
+    private File file(String id) {
+
+        if (this.directory == null) {
+            return (null);
+        }
+        String filename = id + FILE_EXT;
+        File file = new File(directory(), filename);
+        return (file);
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/session/JDBCStore.java b/bundles/org.apache.tomcat/src/org/apache/catalina/session/JDBCStore.java
new file mode 100644
index 0000000..081d06e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/session/JDBCStore.java
@@ -0,0 +1,1010 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.session;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.sql.Connection;
+import java.sql.Driver;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Properties;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.Loader;
+import org.apache.catalina.Session;
+import org.apache.catalina.util.CustomObjectInputStream;
+import org.apache.tomcat.util.ExceptionUtils;
+
+/**
+ * Implementation of the <code>Store</code> interface that stores
+ * serialized session objects in a database.  Sessions that are
+ * saved are still subject to being expired based on inactivity.
+ *
+ * @author Bip Thelin
+ * @version $Id: JDBCStore.java,v 1.1 2011/06/28 21:08:18 rherrmann Exp $
+ */
+
+public class JDBCStore extends StoreBase {
+
+    /**
+     * The descriptive information about this implementation.
+     */
+    protected static final String info = "JDBCStore/1.0";
+
+    /**
+     * Context name associated with this Store
+     */
+    private String name = null;
+
+    /**
+     * Name to register for this Store, used for logging.
+     */
+    protected static String storeName = "JDBCStore";
+
+    /**
+     * Name to register for the background thread.
+     */
+    protected String threadName = "JDBCStore";
+
+    /**
+     * The connection username to use when trying to connect to the database.
+     */
+    protected String connectionName = null;
+
+
+    /**
+     * The connection URL to use when trying to connect to the database.
+     */
+    protected String connectionPassword = null;
+
+    /**
+     * Connection string to use when connecting to the DB.
+     */
+    protected String connectionURL = null;
+
+    /**
+     * The database connection.
+     */
+    private Connection dbConnection = null;
+
+    /**
+     * Instance of the JDBC Driver class we use as a connection factory.
+     */
+    protected Driver driver = null;
+
+    /**
+     * Driver to use.
+     */
+    protected String driverName = null;
+
+    // ------------------------------------------------------------- Table & cols
+
+    /**
+     * Table to use.
+     */
+    protected String sessionTable = "tomcat$sessions";
+
+    /**
+     * Column to use for /Engine/Host/Context name
+     */
+    protected String sessionAppCol = "app";
+
+    /**
+     * Id column to use.
+     */
+    protected String sessionIdCol = "id";
+
+    /**
+     * Data column to use.
+     */
+    protected String sessionDataCol = "data";
+
+    /**
+     * Is Valid column to use.
+     */
+    protected String sessionValidCol = "valid";
+
+    /**
+     * Max Inactive column to use.
+     */
+    protected String sessionMaxInactiveCol = "maxinactive";
+
+    /**
+     * Last Accessed column to use.
+     */
+    protected String sessionLastAccessedCol = "lastaccess";
+
+    // ------------------------------------------------------------- SQL Variables
+
+    /**
+     * Variable to hold the <code>getSize()</code> prepared statement.
+     */
+    protected PreparedStatement preparedSizeSql = null;
+
+    /**
+     * Variable to hold the <code>keys()</code> prepared statement.
+     */
+    protected PreparedStatement preparedKeysSql = null;
+
+    /**
+     * Variable to hold the <code>save()</code> prepared statement.
+     */
+    protected PreparedStatement preparedSaveSql = null;
+
+    /**
+     * Variable to hold the <code>clear()</code> prepared statement.
+     */
+    protected PreparedStatement preparedClearSql = null;
+
+    /**
+     * Variable to hold the <code>remove()</code> prepared statement.
+     */
+    protected PreparedStatement preparedRemoveSql = null;
+
+    /**
+     * Variable to hold the <code>load()</code> prepared statement.
+     */
+    protected PreparedStatement preparedLoadSql = null;
+
+    // ------------------------------------------------------------- Properties
+
+    /**
+     * Return the info for this Store.
+     */
+    @Override
+    public String getInfo() {
+        return (info);
+    }
+
+    /**
+     * Return the name for this instance (built from container name)
+     */
+    public String getName() {
+        if (name == null) {
+            Container container = manager.getContainer();
+            String contextName = container.getName();
+            if (!contextName.startsWith("/")) {
+                contextName = "/" + contextName;
+            }
+            String hostName = "";
+            String engineName = "";
+
+            if (container.getParent() != null) {
+                Container host = container.getParent();
+                hostName = host.getName();
+                if (host.getParent() != null) {
+                    engineName = host.getParent().getName();
+                }
+            }
+            name = "/" + engineName + "/" + hostName + contextName;
+        }
+        return name;
+    }
+
+    /**
+     * Return the thread name for this Store.
+     */
+    public String getThreadName() {
+        return (threadName);
+    }
+
+    /**
+     * Return the name for this Store, used for logging.
+     */
+    @Override
+    public String getStoreName() {
+        return (storeName);
+    }
+
+    /**
+     * Set the driver for this Store.
+     *
+     * @param driverName The new driver
+     */
+    public void setDriverName(String driverName) {
+        String oldDriverName = this.driverName;
+        this.driverName = driverName;
+        support.firePropertyChange("driverName",
+                oldDriverName,
+                this.driverName);
+        this.driverName = driverName;
+    }
+
+    /**
+     * Return the driver for this Store.
+     */
+    public String getDriverName() {
+        return (this.driverName);
+    }
+
+    /**
+     * Return the username to use to connect to the database.
+     *
+     */
+    public String getConnectionName() {
+        return connectionName;
+    }
+
+    /**
+     * Set the username to use to connect to the database.
+     *
+     * @param connectionName Username
+     */
+    public void setConnectionName(String connectionName) {
+        this.connectionName = connectionName;
+    }
+
+    /**
+     * Return the password to use to connect to the database.
+     *
+     */
+    public String getConnectionPassword() {
+        return connectionPassword;
+    }
+
+    /**
+     * Set the password to use to connect to the database.
+     *
+     * @param connectionPassword User password
+     */
+    public void setConnectionPassword(String connectionPassword) {
+        this.connectionPassword = connectionPassword;
+    }
+
+    /**
+     * Set the Connection URL for this Store.
+     *
+     * @param connectionURL The new Connection URL
+     */
+    public void setConnectionURL(String connectionURL) {
+        String oldConnString = this.connectionURL;
+        this.connectionURL = connectionURL;
+        support.firePropertyChange("connectionURL",
+                oldConnString,
+                this.connectionURL);
+    }
+
+    /**
+     * Return the Connection URL for this Store.
+     */
+    public String getConnectionURL() {
+        return (this.connectionURL);
+    }
+
+    /**
+     * Set the table for this Store.
+     *
+     * @param sessionTable The new table
+     */
+    public void setSessionTable(String sessionTable) {
+        String oldSessionTable = this.sessionTable;
+        this.sessionTable = sessionTable;
+        support.firePropertyChange("sessionTable",
+                oldSessionTable,
+                this.sessionTable);
+    }
+
+    /**
+     * Return the table for this Store.
+     */
+    public String getSessionTable() {
+        return (this.sessionTable);
+    }
+
+    /**
+     * Set the App column for the table.
+     *
+     * @param sessionAppCol the column name
+     */
+    public void setSessionAppCol(String sessionAppCol) {
+        String oldSessionAppCol = this.sessionAppCol;
+        this.sessionAppCol = sessionAppCol;
+        support.firePropertyChange("sessionAppCol",
+                oldSessionAppCol,
+                this.sessionAppCol);
+    }
+
+    /**
+     * Return the web application name column for the table.
+     */
+    public String getSessionAppCol() {
+        return (this.sessionAppCol);
+    }
+
+    /**
+     * Set the Id column for the table.
+     *
+     * @param sessionIdCol the column name
+     */
+    public void setSessionIdCol(String sessionIdCol) {
+        String oldSessionIdCol = this.sessionIdCol;
+        this.sessionIdCol = sessionIdCol;
+        support.firePropertyChange("sessionIdCol",
+                oldSessionIdCol,
+                this.sessionIdCol);
+    }
+
+    /**
+     * Return the Id column for the table.
+     */
+    public String getSessionIdCol() {
+        return (this.sessionIdCol);
+    }
+
+    /**
+     * Set the Data column for the table
+     *
+     * @param sessionDataCol the column name
+     */
+    public void setSessionDataCol(String sessionDataCol) {
+        String oldSessionDataCol = this.sessionDataCol;
+        this.sessionDataCol = sessionDataCol;
+        support.firePropertyChange("sessionDataCol",
+                oldSessionDataCol,
+                this.sessionDataCol);
+    }
+
+    /**
+     * Return the data column for the table
+     */
+    public String getSessionDataCol() {
+        return (this.sessionDataCol);
+    }
+
+    /**
+     * Set the Is Valid column for the table
+     *
+     * @param sessionValidCol The column name
+     */
+    public void setSessionValidCol(String sessionValidCol) {
+        String oldSessionValidCol = this.sessionValidCol;
+        this.sessionValidCol = sessionValidCol;
+        support.firePropertyChange("sessionValidCol",
+                oldSessionValidCol,
+                this.sessionValidCol);
+    }
+
+    /**
+     * Return the Is Valid column
+     */
+    public String getSessionValidCol() {
+        return (this.sessionValidCol);
+    }
+
+    /**
+     * Set the Max Inactive column for the table
+     *
+     * @param sessionMaxInactiveCol The column name
+     */
+    public void setSessionMaxInactiveCol(String sessionMaxInactiveCol) {
+        String oldSessionMaxInactiveCol = this.sessionMaxInactiveCol;
+        this.sessionMaxInactiveCol = sessionMaxInactiveCol;
+        support.firePropertyChange("sessionMaxInactiveCol",
+                oldSessionMaxInactiveCol,
+                this.sessionMaxInactiveCol);
+    }
+
+    /**
+     * Return the Max Inactive column
+     */
+    public String getSessionMaxInactiveCol() {
+        return (this.sessionMaxInactiveCol);
+    }
+
+    /**
+     * Set the Last Accessed column for the table
+     *
+     * @param sessionLastAccessedCol The column name
+     */
+    public void setSessionLastAccessedCol(String sessionLastAccessedCol) {
+        String oldSessionLastAccessedCol = this.sessionLastAccessedCol;
+        this.sessionLastAccessedCol = sessionLastAccessedCol;
+        support.firePropertyChange("sessionLastAccessedCol",
+                oldSessionLastAccessedCol,
+                this.sessionLastAccessedCol);
+    }
+
+    /**
+     * Return the Last Accessed column
+     */
+    public String getSessionLastAccessedCol() {
+        return (this.sessionLastAccessedCol);
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Return an array containing the session identifiers of all Sessions
+     * currently saved in this Store.  If there are no such Sessions, a
+     * zero-length array is returned.
+     *
+     * @exception IOException if an input/output error occurred
+     */
+    @Override
+    public String[] keys() throws IOException {
+        ResultSet rst = null;
+        String keys[] = null;
+        synchronized (this) {
+            int numberOfTries = 2;
+            while (numberOfTries > 0) {
+
+                Connection _conn = getConnection();
+                if (_conn == null) {
+                    return (new String[0]);
+                }
+                try {
+                    if (preparedKeysSql == null) {
+                        String keysSql = "SELECT " + sessionIdCol + " FROM "
+                                + sessionTable + " WHERE " + sessionAppCol
+                                + " = ?";
+                        preparedKeysSql = _conn.prepareStatement(keysSql);
+                    }
+
+                    preparedKeysSql.setString(1, getName());
+                    rst = preparedKeysSql.executeQuery();
+                    ArrayList<String> tmpkeys = new ArrayList<String>();
+                    if (rst != null) {
+                        while (rst.next()) {
+                            tmpkeys.add(rst.getString(1));
+                        }
+                    }
+                    keys = tmpkeys.toArray(new String[tmpkeys.size()]);
+                    // Break out after the finally block
+                    numberOfTries = 0;
+                } catch (SQLException e) {
+                    manager.getContainer().getLogger().error(sm.getString(getStoreName() + ".SQLException", e));
+                    keys = new String[0];
+                    // Close the connection so that it gets reopened next time
+                    if (dbConnection != null)
+                        close(dbConnection);
+                } finally {
+                    try {
+                        if (rst != null) {
+                            rst.close();
+                        }
+                    } catch (SQLException e) {
+                        // Ignore
+                    }
+
+                    release(_conn);
+                }
+                numberOfTries--;
+            }
+        }
+
+        return (keys);
+    }
+
+    /**
+     * Return an integer containing a count of all Sessions
+     * currently saved in this Store.  If there are no Sessions,
+     * <code>0</code> is returned.
+     *
+     * @exception IOException if an input/output error occurred
+     */
+    @Override
+    public int getSize() throws IOException {
+        int size = 0;
+        ResultSet rst = null;
+
+        synchronized (this) {
+            int numberOfTries = 2;
+            while (numberOfTries > 0) {
+                Connection _conn = getConnection();
+
+                if (_conn == null) {
+                    return (size);
+                }
+
+                try {
+                    if (preparedSizeSql == null) {
+                        String sizeSql = "SELECT COUNT(" + sessionIdCol
+                                + ") FROM " + sessionTable + " WHERE "
+                                + sessionAppCol + " = ?";
+                        preparedSizeSql = _conn.prepareStatement(sizeSql);
+                    }
+
+                    preparedSizeSql.setString(1, getName());
+                    rst = preparedSizeSql.executeQuery();
+                    if (rst.next()) {
+                        size = rst.getInt(1);
+                    }
+                    // Break out after the finally block
+                    numberOfTries = 0;
+                } catch (SQLException e) {
+                    manager.getContainer().getLogger().error(sm.getString(getStoreName() + ".SQLException", e));
+                    if (dbConnection != null)
+                        close(dbConnection);
+                } finally {
+                    try {
+                        if (rst != null)
+                            rst.close();
+                    } catch (SQLException e) {
+                        // Ignore
+                    }
+
+                    release(_conn);
+                }
+                numberOfTries--;
+            }
+        }
+        return (size);
+    }
+
+    /**
+     * Load the Session associated with the id <code>id</code>.
+     * If no such session is found <code>null</code> is returned.
+     *
+     * @param id a value of type <code>String</code>
+     * @return the stored <code>Session</code>
+     * @exception ClassNotFoundException if an error occurs
+     * @exception IOException if an input/output error occurred
+     */
+    @Override
+    public Session load(String id)
+            throws ClassNotFoundException, IOException {
+        ResultSet rst = null;
+        StandardSession _session = null;
+        Loader loader = null;
+        ClassLoader classLoader = null;
+        ObjectInputStream ois = null;
+        BufferedInputStream bis = null;
+        Container container = manager.getContainer();
+ 
+        synchronized (this) {
+            int numberOfTries = 2;
+            while (numberOfTries > 0) {
+                Connection _conn = getConnection();
+                if (_conn == null) {
+                    return (null);
+                }
+
+                try {
+                    if (preparedLoadSql == null) {
+                        String loadSql = "SELECT " + sessionIdCol + ", "
+                                + sessionDataCol + " FROM " + sessionTable
+                                + " WHERE " + sessionIdCol + " = ? AND "
+                                + sessionAppCol + " = ?";
+                        preparedLoadSql = _conn.prepareStatement(loadSql);
+                    }
+
+                    preparedLoadSql.setString(1, id);
+                    preparedLoadSql.setString(2, getName());
+                    rst = preparedLoadSql.executeQuery();
+                    if (rst.next()) {
+                        bis = new BufferedInputStream(rst.getBinaryStream(2));
+
+                        if (container != null) {
+                            loader = container.getLoader();
+                        }
+                        if (loader != null) {
+                            classLoader = loader.getClassLoader();
+                        }
+                        if (classLoader != null) {
+                            ois = new CustomObjectInputStream(bis,
+                                    classLoader);
+                        } else {
+                            ois = new ObjectInputStream(bis);
+                        }
+
+                        if (manager.getContainer().getLogger().isDebugEnabled()) {
+                            manager.getContainer().getLogger().debug(sm.getString(getStoreName() + ".loading",
+                                    id, sessionTable));
+                        }
+
+                        _session = (StandardSession) manager.createEmptySession();
+                        _session.readObjectData(ois);
+                        _session.setManager(manager);
+                      } else if (manager.getContainer().getLogger().isDebugEnabled()) {
+                        manager.getContainer().getLogger().debug(getStoreName() + ": No persisted data object found");
+                    }
+                    // Break out after the finally block
+                    numberOfTries = 0;
+                } catch (SQLException e) {
+                    manager.getContainer().getLogger().error(sm.getString(getStoreName() + ".SQLException", e));
+                    if (dbConnection != null)
+                        close(dbConnection);
+                } finally {
+                    try {
+                        if (rst != null) {
+                            rst.close();
+                        }
+                    } catch (SQLException e) {
+                        // Ignore
+                    }
+                    if (ois != null) {
+                        try {
+                            ois.close();
+                        } catch (IOException e) {
+                            // Ignore
+                        }
+                    }
+                    release(_conn);
+                }
+                numberOfTries--;
+            }
+        }
+
+        return (_session);
+    }
+
+    /**
+     * Remove the Session with the specified session identifier from
+     * this Store, if present.  If no such Session is present, this method
+     * takes no action.
+     *
+     * @param id Session identifier of the Session to be removed
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public void remove(String id) throws IOException {
+
+        synchronized (this) {
+            int numberOfTries = 2;
+            while (numberOfTries > 0) {
+                Connection _conn = getConnection();
+
+                if (_conn == null) {
+                    return;
+                }
+
+                try {
+                    if (preparedRemoveSql == null) {
+                        String removeSql = "DELETE FROM " + sessionTable
+                                + " WHERE " + sessionIdCol + " = ?  AND "
+                                + sessionAppCol + " = ?";
+                        preparedRemoveSql = _conn.prepareStatement(removeSql);
+                    }
+
+                    preparedRemoveSql.setString(1, id);
+                    preparedRemoveSql.setString(2, getName());
+                    preparedRemoveSql.execute();
+                    // Break out after the finally block
+                    numberOfTries = 0;
+                } catch (SQLException e) {
+                    manager.getContainer().getLogger().error(sm.getString(getStoreName() + ".SQLException", e));
+                    if (dbConnection != null)
+                        close(dbConnection);
+                } finally {
+                    release(_conn);
+                }
+                numberOfTries--;
+            }
+        }
+
+        if (manager.getContainer().getLogger().isDebugEnabled()) {
+            manager.getContainer().getLogger().debug(sm.getString(getStoreName() + ".removing", id, sessionTable));
+        }
+    }
+
+    /**
+     * Remove all of the Sessions in this Store.
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public void clear() throws IOException {
+
+        synchronized (this) {
+            int numberOfTries = 2;
+            while (numberOfTries > 0) {
+                Connection _conn = getConnection();
+                if (_conn == null) {
+                    return;
+                }
+
+                try {
+                    if (preparedClearSql == null) {
+                        String clearSql = "DELETE FROM " + sessionTable
+                             + " WHERE " + sessionAppCol + " = ?";
+                        preparedClearSql = _conn.prepareStatement(clearSql);
+                    }
+
+                    preparedClearSql.setString(1, getName());
+                    preparedClearSql.execute();
+                    // Break out after the finally block
+                    numberOfTries = 0;
+                } catch (SQLException e) {
+                    manager.getContainer().getLogger().error(sm.getString(getStoreName() + ".SQLException", e));
+                    if (dbConnection != null)
+                        close(dbConnection);
+                } finally {
+                    release(_conn);
+                }
+                numberOfTries--;
+            }
+        }
+    }
+
+    /**
+     * Save a session to the Store.
+     *
+     * @param session the session to be stored
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public void save(Session session) throws IOException {
+        ObjectOutputStream oos = null;
+        ByteArrayOutputStream bos = null;
+        ByteArrayInputStream bis = null;
+        InputStream in = null;
+
+        synchronized (this) {
+            int numberOfTries = 2;
+            while (numberOfTries > 0) {
+                Connection _conn = getConnection();
+                if (_conn == null) {
+                    return;
+                }
+
+                // If sessions already exist in DB, remove and insert again.
+                // TODO:
+                // * Check if ID exists in database and if so use UPDATE.
+                remove(session.getIdInternal());
+
+                try {
+                    bos = new ByteArrayOutputStream();
+                    oos = new ObjectOutputStream(new BufferedOutputStream(bos));
+
+                    ((StandardSession) session).writeObjectData(oos);
+                    oos.close();
+                    oos = null;
+                    byte[] obs = bos.toByteArray();
+                    int size = obs.length;
+                    bis = new ByteArrayInputStream(obs, 0, size);
+                    in = new BufferedInputStream(bis, size);
+
+                    if (preparedSaveSql == null) {
+                        String saveSql = "INSERT INTO " + sessionTable + " ("
+                           + sessionIdCol + ", " + sessionAppCol + ", "
+                           + sessionDataCol + ", " + sessionValidCol
+                           + ", " + sessionMaxInactiveCol + ", "
+                           + sessionLastAccessedCol
+                           + ") VALUES (?, ?, ?, ?, ?, ?)";
+                       preparedSaveSql = _conn.prepareStatement(saveSql);
+                    }
+
+                    preparedSaveSql.setString(1, session.getIdInternal());
+                    preparedSaveSql.setString(2, getName());
+                    preparedSaveSql.setBinaryStream(3, in, size);
+                    preparedSaveSql.setString(4, session.isValid() ? "1" : "0");
+                    preparedSaveSql.setInt(5, session.getMaxInactiveInterval());
+                    preparedSaveSql.setLong(6, session.getLastAccessedTime());
+                    preparedSaveSql.execute();
+                    // Break out after the finally block
+                    numberOfTries = 0;
+                } catch (SQLException e) {
+                    manager.getContainer().getLogger().error(sm.getString(getStoreName() + ".SQLException", e));
+                    if (dbConnection != null)
+                        close(dbConnection);
+                } catch (IOException e) {
+                    // Ignore
+                } finally {
+                    if (oos != null) {
+                        oos.close();
+                    }
+                    if (bis != null) {
+                        bis.close();
+                    }
+                    if (in != null) {
+                        in.close();
+                    }
+
+                    release(_conn);
+                }
+                numberOfTries--;
+            }
+        }
+
+        if (manager.getContainer().getLogger().isDebugEnabled()) {
+            manager.getContainer().getLogger().debug(sm.getString(getStoreName() + ".saving",
+                    session.getIdInternal(), sessionTable));
+        }
+    }
+
+    // --------------------------------------------------------- Protected Methods
+
+    /**
+     * Check the connection associated with this store, if it's
+     * <code>null</code> or closed try to reopen it.
+     * Returns <code>null</code> if the connection could not be established.
+     *
+     * @return <code>Connection</code> if the connection succeeded
+     */
+    protected Connection getConnection() {
+        try {
+            if (dbConnection == null || dbConnection.isClosed()) {
+                manager.getContainer().getLogger().info(sm.getString(getStoreName() + ".checkConnectionDBClosed"));
+                open();
+                if (dbConnection == null || dbConnection.isClosed()) {
+                    manager.getContainer().getLogger().info(sm.getString(getStoreName() + ".checkConnectionDBReOpenFail"));
+                }
+            }
+        } catch (SQLException ex) {
+            manager.getContainer().getLogger().error(sm.getString(getStoreName() + ".checkConnectionSQLException",
+                    ex.toString()));
+        }
+
+        return dbConnection;
+    }
+
+    /**
+     * Open (if necessary) and return a database connection for use by
+     * this Realm.
+     *
+     * @exception SQLException if a database error occurs
+     */
+    protected Connection open() throws SQLException {
+
+        // Do nothing if there is a database connection already open
+        if (dbConnection != null)
+            return (dbConnection);
+
+        // Instantiate our database driver if necessary
+        if (driver == null) {
+            try {
+                Class<?> clazz = Class.forName(driverName);
+                driver = (Driver) clazz.newInstance();
+            } catch (ClassNotFoundException ex) {
+                manager.getContainer().getLogger().error(sm.getString(getStoreName() + ".checkConnectionClassNotFoundException",
+                        ex.toString()));
+            } catch (InstantiationException ex) {
+                manager.getContainer().getLogger().error(sm.getString(getStoreName() + ".checkConnectionClassNotFoundException",
+                        ex.toString()));
+            } catch (IllegalAccessException ex) {
+                manager.getContainer().getLogger().error(sm.getString(getStoreName() + ".checkConnectionClassNotFoundException",
+                        ex.toString()));
+            }
+        }
+
+        // Open a new connection
+        Properties props = new Properties();
+        if (connectionName != null)
+            props.put("user", connectionName);
+        if (connectionPassword != null)
+            props.put("password", connectionPassword);
+        dbConnection = driver.connect(connectionURL, props);
+        dbConnection.setAutoCommit(true);
+        return (dbConnection);
+
+    }
+
+    /**
+     * Close the specified database connection.
+     *
+     * @param dbConnection The connection to be closed
+     */
+    protected void close(Connection dbConnection) {
+
+        // Do nothing if the database connection is already closed
+        if (dbConnection == null)
+            return;
+
+        // Close our prepared statements (if any)
+        try {
+            preparedSizeSql.close();
+        } catch (Throwable f) {
+            ExceptionUtils.handleThrowable(f);
+        }
+        this.preparedSizeSql = null;
+
+        try {
+            preparedKeysSql.close();
+        } catch (Throwable f) {
+            ExceptionUtils.handleThrowable(f);
+        }
+        this.preparedKeysSql = null;
+
+        try {
+            preparedSaveSql.close();
+        } catch (Throwable f) {
+            ExceptionUtils.handleThrowable(f);
+        }
+        this.preparedSaveSql = null;
+
+        try {
+            preparedClearSql.close();
+        } catch (Throwable f) {
+            ExceptionUtils.handleThrowable(f);
+        }
+         
+        try {
+            preparedRemoveSql.close();
+        } catch (Throwable f) {
+            ExceptionUtils.handleThrowable(f);
+        }
+        this.preparedRemoveSql = null;
+
+        try {
+            preparedLoadSql.close();
+        } catch (Throwable f) {
+            ExceptionUtils.handleThrowable(f);
+        }
+        this.preparedLoadSql = null;
+
+        // Close this database connection, and log any errors
+        try {
+            dbConnection.close();
+        } catch (SQLException e) {
+            manager.getContainer().getLogger().error(sm.getString(getStoreName() + ".close", e.toString())); // Just log it here
+        } finally {
+            this.dbConnection = null;
+        }
+
+    }
+
+    /**
+     * Release the connection, not needed here since the
+     * connection is not associated with a connection pool.
+     *
+     * @param conn The connection to be released
+     */
+    protected void release(Connection conn) {
+        // NOOP
+    }
+
+    /**
+     * 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 {
+
+        // Open connection to the database
+        this.dbConnection = getConnection();
+        
+        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();
+
+        // Close and release everything associated with our db.
+        if (dbConnection != null) {
+            try {
+                dbConnection.commit();
+            } catch (SQLException e) {
+                // Ignore
+            }
+            close(dbConnection);
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/session/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/session/LocalStrings.properties
new file mode 100644
index 0000000..9ef362f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/session/LocalStrings.properties
@@ -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.
+
+applicationSession.session.ise=invalid session state
+applicationSession.value.iae=null value
+fileStore.saving=Saving Session {0} to file {1}
+fileStore.loading=Loading Session {0} from file {1}
+fileStore.removing=Removing Session {0} at file {1}
+JDBCStore.close=Exception closing database connection {0}
+JDBCStore.saving=Saving Session {0} to database {1}
+JDBCStore.loading=Loading Session {0} from database {1}
+JDBCStore.removing=Removing Session {0} at database {1}
+JDBCStore.SQLException=SQL Error {0}
+JDBCStore.checkConnectionDBClosed=The database connection is null or was found to be closed. Trying to re-open it.
+JDBCStore.checkConnectionDBReOpenFail=The re-open on the database failed. The database could be down.
+JDBCStore.checkConnectionSQLException=A SQL exception occurred {0}
+JDBCStore.checkConnectionClassNotFoundException=JDBC driver class not found {0}
+managerBase.createRandom=Created random number generator for session ID generation in {0}ms.
+managerBase.createSession.ise=createSession: Too many active sessions
+managerBase.sessionTimeout=Invalid session timeout setting {0}
+serverSession.value.iae=null value
+standardManager.expireException=processsExpire:  Exception during session expiration
+standardManager.loading=Loading persisted sessions from {0}
+standardManager.loading.cnfe=ClassNotFoundException while loading persisted sessions: {0}
+standardManager.loading.ioe=IOException while loading persisted sessions: {0}
+standardManager.unloading=Saving persisted sessions to {0}
+standardManager.unloading.debug=Unloading persisted sessions
+standardManager.unloading.ioe=IOException while saving persisted sessions: {0}
+standardManager.unloading.nosessions=No persisted sessions to unload
+standardManager.managerLoad=Exception loading sessions from persistent storage
+standardManager.managerUnload=Exception unloading sessions to persistent storage
+standardSession.attributeEvent=Session attribute event listener threw exception
+standardSession.bindingEvent=Session binding event listener threw exception
+standardSession.invalidate.ise=invalidate: Session already invalidated
+standardSession.isNew.ise=isNew: Session already invalidated
+standardSession.getAttribute.ise=getAttribute: Session already invalidated
+standardSession.getAttributeNames.ise=getAttributeNames: Session already invalidated
+standardSession.getCreationTime.ise=getCreationTime: Session already invalidated
+standardSession.getThisAccessedTime.ise=getThisAccessedTime: Session already invalidated
+standardSession.getLastAccessedTime.ise=getLastAccessedTime: Session already invalidated
+standardSession.getId.ise=getId: Session already invalidated
+standardSession.getMaxInactiveInterval.ise=getMaxInactiveInterval: Session already invalidated
+standardSession.getValueNames.ise=getValueNames: Session already invalidated
+standardSession.logoutfail=Exception logging out user when expiring session 
+standardSession.notSerializable=Cannot serialize session attribute {0} for session {1}
+standardSession.removeAttribute.ise=removeAttribute: Session already invalidated
+standardSession.sessionEvent=Session event listener threw exception
+standardSession.setAttribute.iae=setAttribute: Non-serializable attribute {0}
+standardSession.setAttribute.ise=setAttribute: Session [{0}] has already been invalidated
+standardSession.setAttribute.namenull=setAttribute: name parameter cannot be null
+standardSession.sessionCreated=Created Session id = {0}
+persistentManager.loading=Loading {0} persisted sessions
+persistentManager.unloading=Saving {0} persisted sessions
+persistentManager.expiring=Expiring {0} sessions before saving them
+persistentManager.deserializeError=Error deserializing Session {0}: {1}
+persistentManager.serializeError=Error serializing Session {0}: {1}
+persistentManager.swapMaxIdle=Swapping session {0} to Store, idle for {1} seconds
+persistentManager.backupMaxIdle=Backing up session {0} to Store, idle for {1} seconds
+persistentManager.backupException=Exception occurred when backing up Session {0}: {1}
+persistentManager.tooManyActive=Too many active sessions, {0}, looking for idle sessions to swap out
+persistentManager.swapTooManyActive=Swapping out session {0}, idle for {1} seconds too many sessions active
+persistentManager.processSwaps=Checking for sessions to swap out, {0} active sessions in memory
+persistentManager.activeSession=Session {0} has been idle for {1} seconds
+persistentManager.swapIn=Swapping session {0} in from Store
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/session/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/session/LocalStrings_es.properties
new file mode 100644
index 0000000..c3e3d7a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/session/LocalStrings_es.properties
@@ -0,0 +1,71 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+applicationSession.session.ise = estado inv\u00E1lido de sesi\u00F3n
+applicationSession.value.iae = valor nulo
+fileStore.saving = Salvando Sesi\u00F3n {0} en archivo {1}
+fileStore.loading = Cargando Sesi\u00F3n {0} desde archivo {1}
+fileStore.removing = Quitando Sesi\u00F3n {0} en archivo {1}
+JDBCStore.close = Excepci\u00F3n cerrando conexi\u00F3n a base de datos {0}
+JDBCStore.saving = Salvando Sesi\u00F3n {0} en base de datos {1}
+JDBCStore.loading = Cargando Sesi\u00F3n {0} desde base de datos {1}
+JDBCStore.removing = Quitando Sesi\u00F3n {0} en base de datos {1}
+JDBCStore.SQLException = Error SQL {0}
+JDBCStore.checkConnectionDBClosed = La conexi\u00F3na a base de datos es nula o est\u00E1 cerrada. Intentando reabrirla.
+JDBCStore.checkConnectionDBReOpenFail = Fall\u00F3 la reapertura de la base de datos. Puede que la base de datos est\u00E9 ca\u00EDda.
+JDBCStore.checkConnectionSQLException = Ha tenido lugar una excepci\u00F3n SQL {0}
+JDBCStore.checkConnectionClassNotFoundException = No se ha hallado la clase del manejador (driver) JDBC {0}
+managerBase.createSession.ise = createSession\: Demasiadas sesiones activas
+managerBase.sessionTimeout = Valor inv\u00E1lido de Tiempo Agotado de sesi\u00F3n {0}
+serverSession.value.iae = valor nulo
+standardManager.expireException = processsExpire\: Excepci\u00F3n durante la expiraci\u00F3n de sesi\u00F3n
+standardManager.loading = Cargando sesiones persistidas desde {0}
+standardManager.loading.cnfe = ClassNotFoundException al cargar sesiones persistidas\: {0}
+standardManager.loading.ioe = IOException al cargar sesiones persistidas\: {0}
+standardManager.unloading = Salvando sesiones persistidas a {0}
+standardManager.unloading.ioe = IOException al salvar sesiones persistidas\: {0}
+standardManager.managerLoad = Excepci\u00F3n cargando sesiones desde almacenamiento persistente
+standardManager.managerUnload = Excepci\u00F3n descargando sesiones a almacenamiento persistente
+standardSession.attributeEvent = El oyente de eventos de atributo de Sesi\u00F3n lanz\u00F3 una excepci\u00F3n
+standardSession.bindingEvent = El oyente de eventos de ligado de Sesi\u00F3n lanz\u00F3 una excepci\u00F3n
+standardSession.invalidate.ise = invalidate\: La Sesi\u00F3n ya ha sido invalidada
+standardSession.isNew.ise = isNew\: La Sesi\u00F3n ya ha sido invalidada
+standardSession.getAttribute.ise = getAttribute\: La Sesi\u00F3n ya ha sido invalidada
+standardSession.getAttributeNames.ise = getAttributeNames\: La Sesi\u00F3n ya ha sido invalidada
+standardSession.getCreationTime.ise = getCreationTime\: La Sesi\u00F3n ya ha sido invalidada
+standardSession.getThisAccessedTime.ise = getThisAccessedTime\: La Sesi\u00F3n ya ha sido invalidada
+standardSession.getLastAccessedTime.ise = getLastAccessedTime\: La Sesi\u00F3n ya ha sido invalidada
+standardSession.getId.ise = getId\: La Sesi\u00F3n ya ha sido invalidada
+standardSession.getMaxInactiveInterval.ise = getMaxInactiveInterval\: La Sesi\u00F3n ya ha sido invalidada
+standardSession.getValueNames.ise = getValueNames\: La Sesi\u00F3n ya ha sido invalidada
+standardSession.notSerializable = No puedo serializar atributo de sesi\u00F3n {0} para sesi\u00F3n {1}
+standardSession.removeAttribute.ise = removeAttribute\: La Sesi\u00F3n ya ha sido invalidada
+standardSession.sessionEvent = El oyente de evento de Sesi\u00F3n lanz\u00F3 una execpci\u00F3n
+standardSession.setAttribute.iae = setAttribute\: Atributo {0} no serializable
+standardSession.setAttribute.ise = setAttribute\: La Sesi\u00F3n ya ha sido invalidada
+standardSession.setAttribute.namenull = setAttribute\: el nuevo par\u00E1metro no puede ser nulo
+standardSession.sessionCreated = Creada Sesi\u00F3n id \= {0}
+persistentManager.loading = Cargando {0} sesiones persistidas
+persistentManager.unloading = Salvando {0} sesiones persistidas
+persistentManager.expiring = Expirando {0} sesiones antes de salvarlas
+persistentManager.deserializeError = Error des-serializando Sesi\u00F3n {0}\: {1}
+persistentManager.serializeError = Error serializando Sesi\u00F3n {0}\: {1}
+persistentManager.swapMaxIdle = Intercambiando sesi\u00F3n {0} a fuera a Almac\u00E9n, ociosa durante {1} segundos
+persistentManager.backupMaxIdle = Respaldando sesi\u00F3n {0} a Almac\u00E9n, ociosa durante {1} segundos
+persistentManager.backupException = Ha tenido lugar una excepci\u00F3n al respaldar la Sesi\u00F3n {0}\: {1}
+persistentManager.tooManyActive = Demasiadas sesiones activas, {0}, buscando sesiones ociosas para intercambiar
+persistentManager.swapTooManyActive = Intercambiando sesi\u00F3n {0} a fuera, ociosa durante {1} segundos\: Demasiadas sesiones activas
+persistentManager.processSwaps = Mirando qu\u00E9 sesiones intercambiar a fuera, {0} sesiones activas en memoria
+persistentManager.activeSession = La sesi\u00F3n {0} ha estado ociosa durante {1} segundos
+persistentManager.swapIn = Intercambiando sesi\u00F3n {0} a dentro desde Almac\u00E9n
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/session/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/session/LocalStrings_fr.properties
new file mode 100644
index 0000000..f675222
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/session/LocalStrings_fr.properties
@@ -0,0 +1,70 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+applicationSession.session.ise=\u00e9tat de session invalide
+applicationSession.value.iae=valeur nulle
+fileStore.saving=Sauvegarde de la Session {0} vers le fichier {1}
+fileStore.loading=Chargement de la Session {0} depuis le fichier {1}
+fileStore.removing=Retrait de la Session {0} du fichier {1}
+JDBCStore.saving=Sauvegarde de la Session {0} vers la base de donn\u00e9es {1}
+JDBCStore.loading=Chargement de la Session {0} depuis la base de donn\u00e9es {1}
+JDBCStore.removing=Retrait de la Session {0} de la base de donn\u00e9es {1}
+JDBCStore.SQLException=Erreur SQL {0}
+JDBCStore.checkConnectionDBClosed=La connexion \u00e0 la base de donn\u00e9es est nulle ou a \u00e9t\u00e9 trouv\u00e9e ferm\u00e9e. Tentative de r\u00e9ouverture.
+JDBCStore.checkConnectionDBReOpenFail=La tentative de r\u00e9ouverture de la base de donn\u00e9es a \u00e9chou\u00e9. La base de donn\u00e9es est peut-\u00eatre arr\u00eat\u00e9e.
+JDBCStore.checkConnectionSQLException=Une exception SQL s''est produite {0}
+JDBCStore.checkConnectionClassNotFoundException=La classe du driver JDBC n''a pas \u00e9t\u00e9 trouv\u00e9e {0}
+managerBase.createSession.ise="createSession": Trop de sessions actives
+managerBase.sessionTimeout=R\u00e9glage du d\u00e9lai d''inactivit\u00e9 (timeout) de session invalide {0}
+serverSession.value.iae=valeur nulle
+standardManager.expireException="processsExpire":  Exception lors de l''expiration de la session
+standardManager.loading=Chargement des sessions qui ont persist\u00e9 depuis {0}
+standardManager.loading.cnfe="ClassNotFoundException" lors du chargement de sessions persistantes: {0}
+standardManager.loading.ioe="IOException" lors du chargement de sessions persistantes: {0}
+standardManager.unloading=Sauvegarde des sessions ayant persist\u00e9 vers {0}
+standardManager.unloading.ioe="IOException" lors de la sauvegarde de sessions persistantes: {0}
+standardManager.managerLoad=Exception au chargement des sessions depuis le stockage persistant (persistent storage)
+standardManager.managerUnload=Exception au d\u00e9chargement des sessions vers le stockage persistant (persistent storage)
+standardSession.attributeEvent=L''\u00e9couteur d''\u00e9v\u00e8nement Attribut de Session (attribute event listener) a g\u00e9n\u00e9r\u00e9 une exception
+standardSession.invalidate.ise="invalidate": Session d\u00e9j\u00e0 invalid\u00e9e
+standardSession.isNew.ise="isNew": Session d\u00e9j\u00e0 invalid\u00e9e
+standardSession.getAttribute.ise="getAttribute": Session d\u00e9j\u00e0 invalid\u00e9e
+standardSession.getAttributeNames.ise="getAttributeNames": Session d\u00e9j\u00e0 invalid\u00e9e
+standardSession.getCreationTime.ise="getCreationTime": Session d\u00e9j\u00e0 invalid\u00e9e
+standardSession.getThisAccessedTime.ise="getThisAccessedTime": Session d\u00e9j\u00e0 invalid\u00e9e
+standardSession.getLastAccessedTime.ise="getLastAccessedTime": Session d\u00e9j\u00e0 invalid\u00e9e
+standardSession.getId.ise=getId: Session d\u00e9j\u00e0 invalid\u00e9e
+standardSession.getMaxInactiveInterval.ise="getMaxInactiveInterval": Session d\u00e9j\u00e0 invalid\u00e9e
+standardSession.getValueNames.ise="getValueNames": Session d\u00e9j\u00e0 invalid\u00e9e
+standardSession.notSerializable=Impossible de s\u00e9rialiser l''attribut de session {0} pour la session {1}
+standardSession.removeAttribute.ise="removeAttribute": Session d\u00e9j\u00e0 invalid\u00e9e
+standardSession.sessionEvent=L''\u00e9couteur d''\u00e9v\u00e8nement de session (session event listener) a g\u00e9n\u00e9r\u00e9 une exception
+standardSession.setAttribute.iae="setAttribute": Attribut {0} non s\u00e9rialisable
+standardSession.setAttribute.ise="setAttribute": Session d\u00e9j\u00e0 invalid\u00e9e
+standardSession.setAttribute.namenull="setAttribute": le nom de param\u00e8tre ne peut \u00eatre nul
+standardSession.sessionCreated=Cr\u00e9ation de l''Id de Session = {0}
+persistentManager.loading=Chargement de {0} sessions persistantes
+persistentManager.unloading=Sauvegarde de {0} sessions persistantes
+persistentManager.expiring=Expiration de {0} sessions avant leur sauvegarde
+persistentManager.deserializeError=Erreur lors de la d\u00e9s\u00e9rialisation de la session {0}: {1}
+persistentManager.serializeError=Erreur lors de la s\u00e9rialisation de la session {0}: {1}
+persistentManager.swapMaxIdle=Basculement de la session {0} vers le stockage (Store), en attente pour {1} secondes
+persistentManager.backupMaxIdle=Sauvegarde de la session {0} vers le stockage (Store), en attente pour {1} secondes
+persistentManager.backupException=Exception lors de la sauvegarde de la session {0}: {1}
+persistentManager.tooManyActive=Trop de sessions actives, {0}, \u00e0 la recherche de sessions en attente pour basculement vers stockage (swap out)
+persistentManager.swapTooManyActive=Basculement vers stockage (swap out) de la session {0}, en attente pour {1} secondes trop de sessions actives
+persistentManager.processSwaps=Recherche de sessions \u00e0 basculer vers stockage (swap out), {0} sessions actives en m\u00e9moire
+persistentManager.activeSession=La session {0} a \u00e9t\u00e9 en attente durant {1} secondes
+persistentManager.swapIn=Basculement depuis le stockage (swap in) de la session {0}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/session/LocalStrings_ja.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/session/LocalStrings_ja.properties
new file mode 100644
index 0000000..bf11e71
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/session/LocalStrings_ja.properties
@@ -0,0 +1,72 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+applicationSession.session.ise=\u7121\u52b9\u306a\u30bb\u30c3\u30b7\u30e7\u30f3\u72b6\u614b\u3067\u3059
+applicationSession.value.iae=null\u5024\u3067\u3059
+fileStore.saving=\u30bb\u30c3\u30b7\u30e7\u30f3 {0} \u3092\u30d5\u30a1\u30a4\u30eb {1} \u306b\u4fdd\u5b58\u3057\u307e\u3059
+fileStore.loading=\u30bb\u30c3\u30b7\u30e7\u30f3 {0} \u3092\u30d5\u30a1\u30a4\u30eb {1} \u304b\u3089\u30ed\u30fc\u30c9\u3057\u307e\u3059
+fileStore.removing=\u30bb\u30c3\u30b7\u30e7\u30f3 {0} \u3092\u30d5\u30a1\u30a4\u30eb {1} \u304b\u3089\u524a\u9664\u3057\u307e\u3059
+JDBCStore.close=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u63a5\u7d9a {0} \u3092\u30af\u30ed\u30fc\u30ba\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+JDBCStore.saving=\u30bb\u30c3\u30b7\u30e7\u30f3 {0} \u3092\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9 {1} \u306b\u4fdd\u5b58\u3057\u307e\u3059
+JDBCStore.loading=\u30bb\u30c3\u30b7\u30e7\u30f3 {0} \u3092\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9 {1} \u304b\u3089\u30ed\u30fc\u30c9\u3057\u307e\u3059
+JDBCStore.removing= \u30bb\u30c3\u30b7\u30e7\u30f3 {0} \u3092\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9 {1} \u304b\u3089\u524a\u9664\u3057\u307e\u3059
+JDBCStore.SQLException=SQL\u30a8\u30e9\u30fc {0}
+JDBCStore.checkConnectionDBClosed=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u63a5\u7d9a\u304cnull\u3067\u3042\u308b\u304b\u3001\u30af\u30ed\u30fc\u30ba\u3055\u308c\u3066\u3044\u308b\u306e\u304c\u898b\u3064\u304b\u308a\u307e\u3057\u305f\u3002\u518d\u30aa\u30fc\u30d7\u30f3\u3057\u3066\u304f\u3060\u3055\u3044\u3002
+JDBCStore.checkConnectionDBReOpenFail=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306e\u518d\u30aa\u30fc\u30d7\u30f3\u304c\u5931\u6557\u3057\u307e\u3057\u305f\u3002\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u304c\u30c0\u30a6\u30f3\u3057\u3066\u3044\u308b\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002
+JDBCStore.checkConnectionSQLException=SQL\u4f8b\u5916\u304c\u767a\u751f\u3057\u307e\u3057\u305f {0}
+JDBCStore.checkConnectionClassNotFoundException=JDBC\u30c9\u30e9\u30a4\u30d0\u30af\u30e9\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093 {0}
+managerBase.createSession.ise=createSession: \u30a2\u30af\u30c6\u30a3\u30d6\u30bb\u30c3\u30b7\u30e7\u30f3\u304c\u591a\u3059\u304e\u307e\u3059
+managerBase.sessionTimeout=\u7121\u52b9\u306a\u30bb\u30c3\u30b7\u30e7\u30f3\u30bf\u30a4\u30e0\u30a2\u30a6\u30c8\u8a2d\u5b9a\u3067\u3059 {0}
+serverSession.value.iae=null\u5024\u3067\u3059
+standardManager.expireException=processsExpire: \u30bb\u30c3\u30b7\u30e7\u30f3\u306e\u7d42\u4e86\u51e6\u7406\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+standardManager.loading={0} \u304b\u3089\u6301\u7d9a\u3055\u308c\u305f\u30bb\u30c3\u30b7\u30e7\u30f3\u3092\u30ed\u30fc\u30c9\u3057\u3066\u3044\u307e\u3059
+standardManager.loading.cnfe=\u6301\u7d9a\u3055\u308c\u305f\u30bb\u30c3\u30b7\u30e7\u30f3\u3092\u30ed\u30fc\u30c9\u4e2d\u306bClassNotFoundException\u304c\u767a\u751f\u3057\u307e\u3057\u305f: {0}
+standardManager.loading.ioe=\u6301\u7d9a\u3055\u308c\u305f\u30bb\u30c3\u30b7\u30e7\u30f3\u3092\u30ed\u30fc\u30c9\u4e2d\u306eIOException\u3067\u3059: {0}
+standardManager.unloading=\u6301\u7d9a\u3055\u308c\u305f\u30bb\u30c3\u30b7\u30e7\u30f3\u3092 {0} \u306b\u4fdd\u5b58\u3057\u307e\u3059
+standardManager.unloading.ioe=\u6301\u7d9a\u3055\u308c\u305f\u30bb\u30c3\u30b7\u30e7\u30f3\u306e\u4fdd\u5b58\u4e2d\u306eIOException\u3067\u3059: {0}
+standardManager.managerLoad=\u6c38\u7d9a\u8a18\u61b6\u88c5\u7f6e\u304b\u3089\u30bb\u30c3\u30b7\u30e7\u30f3\u3092\u30ed\u30fc\u30c9\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+standardManager.managerUnload=\u6c38\u7d9a\u8a18\u61b6\u88c5\u7f6e\u306b\u30bb\u30c3\u30b7\u30e7\u30f3\u3092\u30a2\u30f3\u30ed\u30fc\u30c9\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+standardSession.attributeEvent=\u30bb\u30c3\u30b7\u30e7\u30f3\u5c5e\u6027\u30a4\u30d9\u30f3\u30c8\u30ea\u30b9\u30ca\u304c\u4f8b\u5916\u3092\u6295\u3052\u307e\u3057\u305f
+standardSession.bindingEvent=\u30bb\u30c3\u30b7\u30e7\u30f3\u30d0\u30a4\u30f3\u30c7\u30a3\u30f3\u30b0\u30a4\u30d9\u30f3\u30c8\u30ea\u30b9\u30ca\u304c\u4f8b\u5916\u3092\u6295\u3052\u307e\u3057\u305f
+standardSession.invalidate.ise=invalidate: \u30bb\u30c3\u30b7\u30e7\u30f3\u306f\u65e2\u306b\u7121\u52b9\u5316\u3055\u308c\u3066\u3044\u307e\u3059
+standardSession.isNew.ise=isNew: \u30bb\u30c3\u30b7\u30e7\u30f3\u306f\u65e2\u306b\u7121\u52b9\u5316\u3055\u308c\u3066\u3044\u307e\u3059
+standardSession.getAttribute.ise=getAttribute: \u30bb\u30c3\u30b7\u30e7\u30f3\u306f\u65e2\u306b\u7121\u52b9\u5316\u3055\u308c\u3066\u3044\u307e\u3059
+standardSession.getAttributeNames.ise=getAttributeNames: \u30bb\u30c3\u30b7\u30e7\u30f3\u306f\u65e2\u306b\u7121\u52b9\u5316\u3055\u308c\u3066\u3044\u307e\u3059
+standardSession.getCreationTime.ise=getCreationTime: \u30bb\u30c3\u30b7\u30e7\u30f3\u306f\u65e2\u306b\u7121\u52b9\u5316\u3055\u308c\u3066\u3044\u307e\u3059
+standardSession.getThisAccessedTime.ise=getThisAccessedTime: \u30bb\u30c3\u30b7\u30e7\u30f3\u306f\u65e2\u306b\u7121\u52b9\u5316\u3055\u308c\u3066\u3044\u307e\u3059
+standardSession.getLastAccessedTime.ise=getLastAccessedTime: \u30bb\u30c3\u30b7\u30e7\u30f3\u306f\u65e2\u306b\u7121\u52b9\u5316\u3055\u308c\u3066\u3044\u307e\u3059
+standardSession.getId.ise=getId: \u30bb\u30c3\u30b7\u30e7\u30f3\u306f\u65e2\u306b\u7121\u52b9\u5316\u3055\u308c\u3066\u3044\u307e\u3059
+standardSession.getMaxInactiveInterval.ise=getMaxInactiveInterval: \u30bb\u30c3\u30b7\u30e7\u30f3\u306f\u65e2\u306b\u7121\u52b9\u5316\u3055\u308c\u3066\u3044\u307e\u3059
+standardSession.getValueNames.ise=getValueNames: \u30bb\u30c3\u30b7\u30e7\u30f3\u306f\u65e2\u306b\u7121\u52b9\u5316\u3055\u308c\u3066\u3044\u307e\u3059
+standardSession.notSerializable=\u30bb\u30c3\u30b7\u30e7\u30f3 {1} \u306e\u305f\u3081\u306b\u30bb\u30c3\u30b7\u30e7\u30f3\u5c5e\u6027 {0} \u3092\u30b7\u30ea\u30a2\u30e9\u30a4\u30ba\u3067\u304d\u307e\u305b\u3093
+standardSession.removeAttribute.ise=removeAttribute: \u30bb\u30c3\u30b7\u30e7\u30f3\u306f\u65e2\u306b\u7121\u52b9\u5316\u3055\u308c\u3066\u3044\u307e\u3059
+standardSession.sessionEvent=\u30bb\u30c3\u30b7\u30e7\u30f3\u30a4\u30d9\u30f3\u30c8\u30ea\u30b9\u30ca\u304c\u4f8b\u5916\u3092\u6295\u3052\u307e\u3057\u305f
+standardSession.setAttribute.iae=setAttribute: \u30b7\u30ea\u30a2\u30e9\u30a4\u30ba\u3067\u304d\u306a\u3044\u5c5e\u6027\u3067\u3059
+standardSession.setAttribute.ise=setAttribute: \u30bb\u30c3\u30b7\u30e7\u30f3\u306f\u65e2\u306b\u7121\u52b9\u5316\u3055\u308c\u3066\u3044\u307e\u3059
+standardSession.setAttribute.namenull=setAttribute: name\u30d1\u30e9\u30e1\u30bf\u306fnull\u3067\u3042\u3063\u3066\u306f\u3044\u3051\u307e\u305b\u3093
+standardSession.sessionCreated=\u30bb\u30c3\u30b7\u30e7\u30f3ID = {0} \u3092\u751f\u6210\u3057\u307e\u3057\u305f
+persistentManager.loading={0} \u306e\u6301\u7d9a\u3055\u308c\u305f\u30bb\u30c3\u30b7\u30e7\u30f3\u3092\u30ed\u30fc\u30c9\u3057\u307e\u3059
+persistentManager.unloading={0} \u306e\u6301\u7d9a\u3055\u308c\u305f\u30bb\u30c3\u30b7\u30e7\u30f3\u3092\u4fdd\u5b58\u3057\u307e\u3059
+persistentManager.expiring= {0} \u306e\u30bb\u30c3\u30b7\u30e7\u30f3\u3092\u4fdd\u5b58\u3059\u308b\u524d\u306b\u671f\u9650\u5207\u308c\u306b\u306a\u308a\u307e\u3057\u305f
+persistentManager.deserializeError=\u30bb\u30c3\u30b7\u30e7\u30f3 {0} \u3092\u30c7\u30b7\u30ea\u30a2\u30e9\u30a4\u30ba\u4e2d\u306e\u30a8\u30e9\u30fc\u3067\u3059: {1}
+persistentManager.serializeError=\u30bb\u30c3\u30b7\u30e7\u30f3 {0} \u3092\u30b7\u30ea\u30a2\u30e9\u30a4\u30ba\u4e2d\u306e\u30a8\u30e9\u30fc\u3067\u3059: {1}
+persistentManager.swapMaxIdle={1}\u79d2\u9593\u30a2\u30a4\u30c9\u30eb\u3057\u3066\u3044\u308b\u30bb\u30c3\u30b7\u30e7\u30f3 {0} \u3092\u4fdd\u5b58\u3059\u308b\u305f\u3081\u306b\u30b9\u30ef\u30c3\u30d7\u3057\u3066\u3044\u307e\u3059
+persistentManager.backupMaxIdle={1}\u79d2\u9593\u30a2\u30a4\u30c9\u30eb\u3057\u3066\u3044\u308b\u30bb\u30c3\u30b7\u30e7\u30f3 {0} \u3092\u4fdd\u5b58\u3059\u308b\u305f\u3081\u306b\u30d0\u30c3\u30af\u30a2\u30c3\u30d7\u3057\u3066\u3044\u307e\u3059
+persistentManager.backupException=\u30bb\u30c3\u30b7\u30e7\u30f3 {0} \u3092\u30d0\u30c3\u30af\u30a2\u30c3\u30d7\u3059\u308b\u6642\u306b\u4f8b\u5916\u304c\u767a\u751f\u3057\u307e\u3057\u305f: {1}
+persistentManager.tooManyActive=\u30a2\u30af\u30c6\u30a3\u30d6\u30bb\u30c3\u30b7\u30e7\u30f3\u304c\u591a\u3059\u304e\u307e\u3059\u3001{0}\u3001\u30b9\u30ef\u30c3\u30d7\u30a2\u30a6\u30c8\u3059\u308b\u305f\u3081\u306b\u30a2\u30a4\u30c9\u30eb\u30bb\u30c3\u30b7\u30e7\u30f3\u3092\u63a2\u3057\u3066\u3044\u307e\u3059
+persistentManager.swapTooManyActive=\u30bb\u30c3\u30b7\u30e7\u30f3\u304c\u591a\u3059\u304e\u308b\u306e\u3067\u3001{1}\u79d2\u30a2\u30a4\u30c9\u30eb\u3057\u3066\u3044\u308b\u30bb\u30c3\u30b7\u30e7\u30f3 {0} \u3092\u30b9\u30ef\u30c3\u30d7\u30a2\u30a6\u30c8\u3057\u307e\u3059
+persistentManager.processSwaps=\u30bb\u30c3\u30b7\u30e7\u30f3\u3092\u30b9\u30ef\u30c3\u30d7\u3059\u308b\u305f\u3081\u306b\u30c1\u30a7\u30c3\u30af\u3057\u3066\u3044\u307e\u3059, \u30e1\u30e2\u30ea\u4e2d\u306b {0} \u30a2\u30af\u30c6\u30a3\u30d6\u30bb\u30c3\u30b7\u30e7\u30f3\u304c\u5b58\u5728\u3057\u307e\u3059
+persistentManager.activeSession=\u30bb\u30c3\u30b7\u30e7\u30f3 {0} \u306f{1}\u79d2\u9593\u30a2\u30a4\u30c9\u30eb\u3057\u3066\u3044\u307e\u3059
+persistentManager.swapIn=\u30bb\u30c3\u30b7\u30e7\u30f3 {0} \u3092\u30b9\u30ef\u30c3\u30d7\u30a4\u30f3\u3057\u3066\u3044\u307e\u3059
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/session/ManagerBase.java b/bundles/org.apache.tomcat/src/org/apache/catalina/session/ManagerBase.java
new file mode 100644
index 0000000..fc2a6eb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/session/ManagerBase.java
@@ -0,0 +1,1330 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.session;
+
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Deque;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.Manager;
+import org.apache.catalina.Session;
+import org.apache.catalina.mbeans.MBeanUtils;
+import org.apache.catalina.util.LifecycleMBeanBase;
+import org.apache.catalina.util.SessionIdGenerator;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Minimal implementation of the <b>Manager</b> interface that supports
+ * no session persistence or distributable capabilities.  This class may
+ * be subclassed to create more sophisticated Manager implementations.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: ManagerBase.java,v 1.1 2011/06/28 21:08:18 rherrmann Exp $
+ */
+
+public abstract class ManagerBase extends LifecycleMBeanBase
+        implements Manager, PropertyChangeListener {
+
+    private final Log log = LogFactory.getLog(ManagerBase.class); // must not be static
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * The Container with which this Manager is associated.
+     */
+    protected Container container;
+
+
+    /**
+     * The distributable flag for Sessions created by this Manager.  If this
+     * flag is set to <code>true</code>, any user attributes added to a
+     * session controlled by this Manager must be Serializable.
+     */
+    protected boolean distributable;
+
+
+    /**
+     * The descriptive information string for this implementation.
+     */
+    private static final String info = "ManagerBase/1.0";
+
+
+    /**
+     * The default maximum inactive interval for Sessions created by
+     * this Manager.
+     */
+    protected int maxInactiveInterval = 30 * 60;
+
+
+    /**
+     * The session id length of Sessions created by this Manager.
+     */
+    protected int sessionIdLength = 16;
+
+
+    /**
+     * The descriptive name of this Manager implementation (for logging).
+     */
+    protected static String name = "ManagerBase";
+
+
+    /**
+     * The Java class name of the secure random number generator class to be
+     * used when generating session identifiers. The random number generator
+     * class must be self-seeding and have a zero-argument constructor. If not
+     * specified, an instance of {@link java.security.SecureRandom} will be
+     * generated.
+     */
+    protected String secureRandomClass = null;
+
+    /**
+     * The name of the algorithm to use to create instances of
+     * {@link java.security.SecureRandom} which are used to generate session IDs.
+     * If no algorithm is specified, SHA1PRNG is used. To use the platform
+     * default (which may be SHA1PRNG), specify the empty string. If an invalid
+     * algorithm and/or provider is specified the SecureRandom instances will be
+     * created using the defaults. If that fails, the SecureRandom instances
+     * will be created using platform defaults.
+     */
+    protected String secureRandomAlgorithm = "SHA1PRNG";
+
+    /**
+     * The name of the provider to use to create instances of
+     * {@link java.security.SecureRandom} which are used to generate session IDs. 
+     * If no algorithm is specified the of SHA1PRNG default is used. If an
+     * invalid algorithm and/or provider is specified the SecureRandom instances
+     * will be created using the defaults. If that fails, the SecureRandom
+     * instances will be created using platform defaults.
+     */
+    protected String secureRandomProvider = null;
+
+    protected SessionIdGenerator sessionIdGenerator = null;
+
+    /**
+     * The longest time (in seconds) that an expired session had been alive.
+     */
+    protected volatile int sessionMaxAliveTime;
+    private final Object sessionMaxAliveTimeUpdateLock = new Object();
+
+
+    protected static final int TIMING_STATS_CACHE_SIZE = 100;
+
+    protected Deque<SessionTiming> sessionCreationTiming =
+        new LinkedList<SessionTiming>();
+
+    protected Deque<SessionTiming> sessionExpirationTiming =
+        new LinkedList<SessionTiming>();
+
+    /**
+     * Number of sessions that have expired.
+     */
+    protected AtomicLong expiredSessions = new AtomicLong(0);
+
+
+    /**
+     * The set of currently active Sessions for this Manager, keyed by
+     * session identifier.
+     */
+    protected Map<String, Session> sessions = new ConcurrentHashMap<String, Session>();
+
+    // Number of sessions created by this manager
+    protected long sessionCounter=0;
+
+    protected volatile int maxActive=0;
+
+    private final Object maxActiveUpdateLock = new Object();
+
+    /**
+     * The maximum number of active Sessions allowed, or -1 for no limit.
+     */
+    protected int maxActiveSessions = -1;
+
+    /**
+     * Number of session creations that failed due to maxActiveSessions.
+     */
+    protected int rejectedSessions = 0;
+
+    // number of duplicated session ids - anything >0 means we have problems
+    protected volatile int duplicates=0;
+
+    /**
+     * Processing time during session expiration.
+     */
+    protected long processingTime = 0;
+
+    /**
+     * Iteration count for background processing.
+     */
+    private int count = 0;
+
+
+    /**
+     * Frequency of the session expiration, and related manager operations.
+     * Manager operations 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 processExpiresFrequency = 6;
+
+    /**
+     * 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);
+    
+
+    // ------------------------------------------------------------- Properties
+
+    /**
+     * Return the Container with which this Manager is associated.
+     */
+    @Override
+    public Container getContainer() {
+
+        return (this.container);
+
+    }
+
+
+    /**
+     * Set the Container with which this Manager is associated.
+     *
+     * @param container The newly associated Container
+     */
+    @Override
+    public void setContainer(Container container) {
+
+        // De-register from the old Container (if any)
+        if ((this.container != null) && (this.container instanceof Context))
+            ((Context) this.container).removePropertyChangeListener(this);
+
+        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)) {
+            setMaxInactiveInterval
+                ( ((Context) this.container).getSessionTimeout()*60 );
+            ((Context) this.container).addPropertyChangeListener(this);
+        }
+
+    }
+
+
+    /** Returns the name of the implementation class.
+     */
+    public String getClassName() {
+        return this.getClass().getName();
+    }
+
+
+    /**
+     * Return the distributable flag for the sessions supported by
+     * this Manager.
+     */
+    @Override
+    public boolean getDistributable() {
+
+        return (this.distributable);
+
+    }
+
+
+    /**
+     * Set the distributable flag for the sessions supported by this
+     * Manager.  If this flag is set, all user data objects added to
+     * sessions associated with this manager must implement Serializable.
+     *
+     * @param distributable The new distributable flag
+     */
+    @Override
+    public void setDistributable(boolean distributable) {
+
+        boolean oldDistributable = this.distributable;
+        this.distributable = distributable;
+        support.firePropertyChange("distributable",
+                                   Boolean.valueOf(oldDistributable),
+                                   Boolean.valueOf(this.distributable));
+    }
+
+
+    /**
+     * Return descriptive information about this Manager 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 default maximum inactive interval (in seconds)
+     * for Sessions created by this Manager.
+     */
+    @Override
+    public int getMaxInactiveInterval() {
+
+        return (this.maxInactiveInterval);
+
+    }
+
+
+    /**
+     * Set the default maximum inactive interval (in seconds)
+     * for Sessions created by this Manager.
+     *
+     * @param interval The new default value
+     */
+    @Override
+    public void setMaxInactiveInterval(int interval) {
+
+        int oldMaxInactiveInterval = this.maxInactiveInterval;
+        this.maxInactiveInterval = interval;
+        support.firePropertyChange("maxInactiveInterval",
+                                   Integer.valueOf(oldMaxInactiveInterval),
+                                   Integer.valueOf(this.maxInactiveInterval));
+
+    }
+
+
+    /**
+     * Gets the session id length (in bytes) of Sessions created by
+     * this Manager.
+     *
+     * @return The session id length
+     */
+    @Override
+    public int getSessionIdLength() {
+
+        return (this.sessionIdLength);
+
+    }
+
+
+    /**
+     * Sets the session id length (in bytes) for Sessions created by this
+     * Manager.
+     *
+     * @param idLength The session id length
+     */
+    @Override
+    public void setSessionIdLength(int idLength) {
+
+        int oldSessionIdLength = this.sessionIdLength;
+        this.sessionIdLength = idLength;
+        support.firePropertyChange("sessionIdLength",
+                                   Integer.valueOf(oldSessionIdLength),
+                                   Integer.valueOf(this.sessionIdLength));
+
+    }
+
+
+    /**
+     * Return the descriptive short name of this Manager implementation.
+     */
+    public String getName() {
+
+        return (name);
+
+    }
+
+    /**
+     * Return the secure random number generator class name.
+     */
+    public String getSecureRandomClass() {
+
+        return (this.secureRandomClass);
+
+    }
+
+
+    /**
+     * Set the secure random number generator class name.
+     *
+     * @param secureRandomClass The new secure random number generator class
+     *                          name
+     */
+    public void setSecureRandomClass(String secureRandomClass) {
+
+        String oldSecureRandomClass = this.secureRandomClass;
+        this.secureRandomClass = secureRandomClass;
+        support.firePropertyChange("secureRandomClass", oldSecureRandomClass,
+                                   this.secureRandomClass);
+
+    }
+
+
+    /**
+     * Return the secure random number generator algorithm name.
+     */
+    public String getSecureRandomAlgorithm() {
+        return secureRandomAlgorithm;
+    }
+
+
+    /**
+     * Set the secure random number generator algorithm name.
+     *
+     * @param secureRandomAlgorithm The new secure random number generator
+     *                              algorithm name
+     */
+    public void setSecureRandomAlgorithm(String secureRandomAlgorithm) {
+        this.secureRandomAlgorithm = secureRandomAlgorithm;
+    }
+
+
+    /**
+     * Return the secure random number generator provider name.
+     */
+    public String getSecureRandomProvider() {
+        return secureRandomProvider;
+    }
+
+
+    /**
+     * Set the secure random number generator provider name.
+     *
+     * @param secureRandomProvider The new secure random number generator
+     *                             provider name
+     */
+    public void setSecureRandomProvider(String secureRandomProvider) {
+        this.secureRandomProvider = secureRandomProvider;
+    }
+
+
+    /**
+     * Number of session creations that failed due to maxActiveSessions
+     * 
+     * @return The count
+     */
+    @Override
+    public int getRejectedSessions() {
+        return rejectedSessions;
+    }
+
+    /**
+     * Gets the number of sessions that have expired.
+     *
+     * @return Number of sessions that have expired
+     */
+    @Override
+    public long getExpiredSessions() {
+        return expiredSessions.get();
+    }
+
+
+    /**
+     * Sets the number of sessions that have expired.
+     *
+     * @param expiredSessions Number of sessions that have expired
+     */
+    @Override
+    public void setExpiredSessions(long expiredSessions) {
+        this.expiredSessions.set(expiredSessions);
+    }
+
+    public long getProcessingTime() {
+        return processingTime;
+    }
+
+
+    public void setProcessingTime(long processingTime) {
+        this.processingTime = processingTime;
+    }
+    
+    /**
+     * Return the frequency of manager checks.
+     */
+    public int getProcessExpiresFrequency() {
+
+        return (this.processExpiresFrequency);
+
+    }
+
+    /**
+     * Set the manager checks frequency.
+     *
+     * @param processExpiresFrequency the new manager checks frequency
+     */
+    public void setProcessExpiresFrequency(int processExpiresFrequency) {
+
+        if (processExpiresFrequency <= 0) {
+            return;
+        }
+
+        int oldProcessExpiresFrequency = this.processExpiresFrequency;
+        this.processExpiresFrequency = processExpiresFrequency;
+        support.firePropertyChange("processExpiresFrequency",
+                                   Integer.valueOf(oldProcessExpiresFrequency),
+                                   Integer.valueOf(this.processExpiresFrequency));
+
+    }
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Implements the Manager interface, direct call to processExpires
+     */
+    @Override
+    public void backgroundProcess() {
+        count = (count + 1) % processExpiresFrequency;
+        if (count == 0)
+            processExpires();
+    }
+
+    /**
+     * Invalidate all sessions that have expired.
+     */
+    public void processExpires() {
+
+        long timeNow = System.currentTimeMillis();
+        Session sessions[] = findSessions();
+        int expireHere = 0 ;
+        
+        if(log.isDebugEnabled())
+            log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length);
+        for (int i = 0; i < sessions.length; i++) {
+            if (sessions[i]!=null && !sessions[i].isValid()) {
+                expireHere++;
+            }
+        }
+        long timeEnd = System.currentTimeMillis();
+        if(log.isDebugEnabled())
+             log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow) + " expired sessions: " + expireHere);
+        processingTime += ( timeEnd - timeNow );
+
+    }
+
+    @Override
+    protected void initInternal() throws LifecycleException {
+        
+        super.initInternal();
+        
+        setDistributable(((Context) getContainer()).getDistributable());
+    }
+
+    @Override
+    protected void startInternal() throws LifecycleException {
+
+        // Ensure caches for timing stats are the right size by filling with
+        // nulls.
+        while (sessionCreationTiming.size() < TIMING_STATS_CACHE_SIZE) {
+            sessionCreationTiming.add(null);
+        }
+        while (sessionExpirationTiming.size() < TIMING_STATS_CACHE_SIZE) {
+            sessionExpirationTiming.add(null);
+        }
+
+        sessionIdGenerator = new SessionIdGenerator();
+        sessionIdGenerator.setJvmRoute(getJvmRoute());
+        sessionIdGenerator.setSecureRandomAlgorithm(getSecureRandomAlgorithm());
+        sessionIdGenerator.setSecureRandomClass(getSecureRandomClass());
+        sessionIdGenerator.setSecureRandomProvider(getSecureRandomProvider());
+        sessionIdGenerator.setSessionIdLength(getSessionIdLength());
+
+        // Force initialization of the random number generator
+        if (log.isDebugEnabled())
+            log.debug("Force random number initialization starting");
+        sessionIdGenerator.generateSessionId();
+        if (log.isDebugEnabled())
+            log.debug("Force random number initialization completed");
+    }
+
+    @Override
+    protected void stopInternal() throws LifecycleException {
+        this.sessionIdGenerator = null;
+    }
+
+
+    /**
+     * Add this Session to the set of active Sessions for this Manager.
+     *
+     * @param session Session to be added
+     */
+    @Override
+    public void add(Session session) {
+
+        sessions.put(session.getIdInternal(), session);
+        int size = getActiveSessions();
+        if( size > maxActive ) {
+            synchronized(maxActiveUpdateLock) {
+                if( size > maxActive ) {
+                    maxActive = size;
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Add a property change listener to this component.
+     *
+     * @param listener The listener to add
+     */
+    @Override
+    public void addPropertyChangeListener(PropertyChangeListener listener) {
+
+        support.addPropertyChangeListener(listener);
+
+    }
+
+
+    /**
+     * Construct and return a new session object, based on the default
+     * settings specified by this Manager's properties.  The session
+     * id specified will be used as the session id.  
+     * If a new session cannot be created for any reason, return 
+     * <code>null</code>.
+     * 
+     * @param sessionId The session id which should be used to create the
+     *  new session; if <code>null</code>, a new session id will be
+     *  generated
+     * @exception IllegalStateException if a new session cannot be
+     *  instantiated for any reason
+     */
+    @Override
+    public Session createSession(String sessionId) {
+        
+        if ((maxActiveSessions >= 0) &&
+                (getActiveSessions() >= maxActiveSessions)) {
+            rejectedSessions++;
+            throw new IllegalStateException(
+                    sm.getString("managerBase.createSession.ise"));
+        }
+        
+        // Recycle or create a Session instance
+        Session session = createEmptySession();
+
+        // Initialize the properties of the new session and return it
+        session.setNew(true);
+        session.setValid(true);
+        session.setCreationTime(System.currentTimeMillis());
+        session.setMaxInactiveInterval(this.maxInactiveInterval);
+        String id = sessionId;
+        if (id == null) {
+            id = generateSessionId();
+        }
+        session.setId(id);
+        sessionCounter++;
+
+        SessionTiming timing = new SessionTiming(session.getCreationTime(), 0);
+        synchronized (sessionCreationTiming) {
+            sessionCreationTiming.add(timing);
+            sessionCreationTiming.poll();
+        }
+        return (session);
+
+    }
+    
+    
+    /**
+     * Get a session from the recycled ones or create a new empty one.
+     * The PersistentManager manager does not need to create session data
+     * because it reads it from the Store.
+     */
+    @Override
+    public Session createEmptySession() {
+        return (getNewSession());
+    }
+
+
+    /**
+     * Return the active Session, associated with this Manager, with the
+     * specified session id (if any); otherwise return <code>null</code>.
+     *
+     * @param id The session id for the session to be returned
+     *
+     * @exception IllegalStateException if a new session cannot be
+     *  instantiated for any reason
+     * @exception IOException if an input/output error occurs while
+     *  processing this request
+     */
+    @Override
+    public Session findSession(String id) throws IOException {
+
+        if (id == null)
+            return (null);
+        return sessions.get(id);
+
+    }
+
+
+    /**
+     * Return the set of active Sessions associated with this Manager.
+     * If this Manager has no active Sessions, a zero-length array is returned.
+     */
+    @Override
+    public Session[] findSessions() {
+
+        return sessions.values().toArray(new Session[0]);
+
+    }
+
+
+    /**
+     * Remove this Session from the active Sessions for this Manager.
+     *
+     * @param session Session to be removed
+     */
+    @Override
+    public void remove(Session session) {
+        remove(session, false);
+    }
+    
+    /**
+     * Remove this Session from the active Sessions for this Manager.
+     *
+     * @param session   Session to be removed
+     * @param update    Should the expiration statistics be updated
+     */
+    @Override
+    public void remove(Session session, boolean update) {
+        
+        // If the session has expired - as opposed to just being removed from
+        // the manager because it is being persisted - update the expired stats
+        if (update) {
+            long timeNow = System.currentTimeMillis();
+            int timeAlive =
+                (int) (timeNow - session.getCreationTimeInternal())/1000;
+            updateSessionMaxAliveTime(timeAlive);
+            expiredSessions.incrementAndGet();
+            SessionTiming timing = new SessionTiming(timeNow, timeAlive);
+            synchronized (sessionExpirationTiming) {
+                sessionExpirationTiming.add(timing);
+                sessionExpirationTiming.poll();
+            }
+        }
+
+        if (session.getIdInternal() != null) {
+            sessions.remove(session.getIdInternal());
+        }
+    }
+
+
+    /**
+     * Remove a property change listener from this component.
+     *
+     * @param listener The listener to remove
+     */
+    @Override
+    public void removePropertyChangeListener(PropertyChangeListener listener) {
+
+        support.removePropertyChangeListener(listener);
+
+    }
+
+
+    /**
+     * Change the session ID of the current session to a new randomly generated
+     * session ID.
+     * 
+     * @param session   The session to change the session ID for
+     */
+    @Override
+    public void changeSessionId(Session session) {
+        session.setId(generateSessionId());
+    }
+    
+    
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Get new session class to be used in the doLoad() method.
+     */
+    protected StandardSession getNewSession() {
+        return new StandardSession(this);
+    }
+
+
+    /**
+     * Generate and return a new session identifier.
+     */
+    protected String generateSessionId() {
+
+        String result = null;
+
+        do {
+            if (result != null) {
+                duplicates++;
+            }
+
+            result = sessionIdGenerator.generateSessionId();
+            
+        } while (sessions.containsKey(result));
+        
+        return result;
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Retrieve the enclosing Engine for this Manager.
+     *
+     * @return an Engine object (or null).
+     */
+    public Engine getEngine() {
+        Engine e = null;
+        for (Container c = getContainer(); e == null && c != null ; c = c.getParent()) {
+            if (c instanceof Engine) {
+                e = (Engine)c;
+            }
+        }
+        return e;
+    }
+
+
+    /**
+     * Retrieve the JvmRoute for the enclosing Engine.
+     * @return the JvmRoute or null.
+     */
+    public String getJvmRoute() {
+        Engine e = getEngine();
+        return e == null ? null : e.getJvmRoute();
+    }
+
+
+    // -------------------------------------------------------- Package Methods
+
+
+    @Override
+    public void setSessionCounter(long sessionCounter) {
+        this.sessionCounter = sessionCounter;
+    }
+
+
+    /** 
+     * Total sessions created by this manager.
+     *
+     * @return sessions created
+     */
+    @Override
+    public long getSessionCounter() {
+        return sessionCounter;
+    }
+
+
+    /** 
+     * Number of duplicated session IDs generated by the random source.
+     * Anything bigger than 0 means problems.
+     *
+     * @return The count of duplicates
+     */
+    public int getDuplicates() {
+        return duplicates;
+    }
+
+
+    public void setDuplicates(int duplicates) {
+        this.duplicates = duplicates;
+    }
+
+
+    /** 
+     * Returns the number of active sessions
+     *
+     * @return number of sessions active
+     */
+    @Override
+    public int getActiveSessions() {
+        return sessions.size();
+    }
+
+
+    /**
+     * Max number of concurrent active sessions
+     *
+     * @return The highest number of concurrent active sessions
+     */
+    @Override
+    public int getMaxActive() {
+        return maxActive;
+    }
+
+
+    @Override
+    public void setMaxActive(int maxActive) {
+        synchronized (maxActiveUpdateLock) {
+            this.maxActive = maxActive;
+        }
+    }
+
+
+    /**
+     * Return the maximum number of active Sessions allowed, or -1 for
+     * no limit.
+     */
+    public int getMaxActiveSessions() {
+
+        return (this.maxActiveSessions);
+
+    }
+
+
+    /**
+     * Set the maximum number of active Sessions allowed, or -1 for
+     * no limit.
+     *
+     * @param max The new maximum number of sessions
+     */
+    public void setMaxActiveSessions(int max) {
+
+        int oldMaxActiveSessions = this.maxActiveSessions;
+        this.maxActiveSessions = max;
+        support.firePropertyChange("maxActiveSessions",
+                                   Integer.valueOf(oldMaxActiveSessions),
+                                   Integer.valueOf(this.maxActiveSessions));
+
+    }
+
+
+    /**
+     * Gets the longest time (in seconds) that an expired session had been
+     * alive.
+     *
+     * @return Longest time (in seconds) that an expired session had been
+     * alive.
+     */
+    @Override
+    public int getSessionMaxAliveTime() {
+        return sessionMaxAliveTime;
+    }
+
+
+    /**
+     * Sets the longest time (in seconds) that an expired session had been
+     * alive. Typically used for resetting the current value.
+     *
+     * @param sessionMaxAliveTime Longest time (in seconds) that an expired
+     * session had been alive.
+     */
+    @Override
+    public void setSessionMaxAliveTime(int sessionMaxAliveTime) {
+        synchronized (sessionMaxAliveTimeUpdateLock) {
+            this.sessionMaxAliveTime = sessionMaxAliveTime;
+        }
+    }
+
+
+    /**
+     * Updates the sessionMaxAliveTime attribute if the candidate value is
+     * larger than the current value.
+     * 
+     * @param sessionAliveTime  The candidate value (in seconds) for the new 
+     *                          sessionMaxAliveTime value.
+     */
+    public void updateSessionMaxAliveTime(int sessionAliveTime) {
+        if (sessionAliveTime > this.sessionMaxAliveTime) {
+            synchronized (sessionMaxAliveTimeUpdateLock) {
+                if (sessionAliveTime > this.sessionMaxAliveTime) {
+                    this.sessionMaxAliveTime = sessionAliveTime;
+                }
+            }
+        }
+    }
+
+    /**
+     * Gets the average time (in seconds) that expired sessions had been
+     * alive based on the last 100 sessions to expire. If less than
+     * 100 sessions have expired then all available data is used.
+     * 
+     * @return Average time (in seconds) that expired sessions had been
+     * alive.
+     */
+    @Override
+    public int getSessionAverageAliveTime() {
+        // Copy current stats
+        List<SessionTiming> copy = new ArrayList<SessionTiming>();
+        synchronized (sessionExpirationTiming) {
+            copy.addAll(sessionExpirationTiming);
+        }
+        
+        // Init
+        int counter = 0;
+        int result = 0;
+        Iterator<SessionTiming> iter = copy.iterator();
+        
+        // Calculate average
+        while (iter.hasNext()) {
+            SessionTiming timing = iter.next();
+            if (timing != null) {
+                int timeAlive = timing.getDuration();
+                counter++;
+                // Very careful not to overflow - probably not necessary
+                result =
+                    (result * ((counter - 1)/counter)) + (timeAlive/counter);
+            }
+        }
+        return result;
+    }
+
+    
+    /**
+     * Gets the current rate of session creation (in session per minute) based
+     * on the creation time of the previous 100 sessions created. If less than
+     * 100 sessions have been created then all available data is used.
+     * 
+     * @return  The current rate (in sessions per minute) of session creation
+     */
+    @Override
+    public int getSessionCreateRate() {
+        long now = System.currentTimeMillis();
+        // Copy current stats
+        List<SessionTiming> copy = new ArrayList<SessionTiming>();
+        synchronized (sessionCreationTiming) {
+            copy.addAll(sessionCreationTiming);
+        }
+        
+        // Init
+        long oldest = now;
+        int counter = 0;
+        int result = 0;
+        Iterator<SessionTiming> iter = copy.iterator();
+        
+        // Calculate rate
+        while (iter.hasNext()) {
+            SessionTiming timing = iter.next();
+            if (timing != null) {
+                counter++;
+                if (timing.getTimestamp() < oldest) {
+                    oldest = timing.getTimestamp();
+                }
+            }
+        }
+        if (counter > 0) {
+            if (oldest < now) {
+                result = (1000*60*counter)/(int) (now - oldest);
+            } else {
+                result = Integer.MAX_VALUE;
+            }
+        }
+        return result;
+    }
+    
+
+    /**
+     * Gets the current rate of session expiration (in session per minute) based
+     * on the expiry time of the previous 100 sessions expired. If less than
+     * 100 sessions have expired then all available data is used.
+     * 
+     * @return  The current rate (in sessions per minute) of session expiration
+     */
+    @Override
+    public int getSessionExpireRate() {
+        long now = System.currentTimeMillis();
+        // Copy current stats
+        List<SessionTiming> copy = new ArrayList<SessionTiming>();
+        synchronized (sessionExpirationTiming) {
+            copy.addAll(sessionExpirationTiming);
+        }
+        
+        // Init
+        long oldest = now;
+        int counter = 0;
+        int result = 0;
+        Iterator<SessionTiming> iter = copy.iterator();
+        
+        // Calculate rate
+        while (iter.hasNext()) {
+            SessionTiming timing = iter.next();
+            if (timing != null) {
+                counter++;
+                if (timing.getTimestamp() < oldest) {
+                    oldest = timing.getTimestamp();
+                }
+            }
+        }
+        if (counter > 0) {
+            if (oldest < now) {
+                result = (1000*60*counter)/(int) (now - oldest);
+            } else {
+                // Better than reporting zero
+                result = Integer.MAX_VALUE;
+            }
+        }
+        return result;
+    }
+
+
+    /** 
+     * For debugging: return a list of all session ids currently active
+     *
+     */
+    public String listSessionIds() {
+        StringBuilder sb=new StringBuilder();
+        Iterator<String> keys = sessions.keySet().iterator();
+        while (keys.hasNext()) {
+            sb.append(keys.next()).append(" ");
+        }
+        return sb.toString();
+    }
+
+
+    /** 
+     * For debugging: get a session attribute
+     *
+     * @param sessionId
+     * @param key
+     * @return The attribute value, if found, null otherwise
+     */
+    public String getSessionAttribute( String sessionId, String key ) {
+        Session s = sessions.get(sessionId);
+        if( s==null ) {
+            if(log.isInfoEnabled())
+                log.info("Session not found " + sessionId);
+            return null;
+        }
+        Object o=s.getSession().getAttribute(key);
+        if( o==null ) return null;
+        return o.toString();
+    }
+
+
+    /**
+     * Returns information about the session with the given session id.
+     * 
+     * <p>The session information is organized as a HashMap, mapping 
+     * session attribute names to the String representation of their values.
+     *
+     * @param sessionId Session id
+     * 
+     * @return HashMap mapping session attribute names to the String
+     * representation of their values, or null if no session with the
+     * specified id exists, or if the session does not have any attributes
+     */
+    public HashMap<String, String> getSession(String sessionId) {
+        Session s = sessions.get(sessionId);
+        if (s == null) {
+            if (log.isInfoEnabled()) {
+                log.info("Session not found " + sessionId);
+            }
+            return null;
+        }
+
+        Enumeration<String> ee = s.getSession().getAttributeNames();
+        if (ee == null || !ee.hasMoreElements()) {
+            return null;
+        }
+
+        HashMap<String, String> map = new HashMap<String, String>();
+        while (ee.hasMoreElements()) {
+            String attrName = ee.nextElement();
+            map.put(attrName, getSessionAttribute(sessionId, attrName));
+        }
+
+        return map;
+    }
+
+
+    public void expireSession( String sessionId ) {
+        Session s=sessions.get(sessionId);
+        if( s==null ) {
+            if(log.isInfoEnabled())
+                log.info("Session not found " + sessionId);
+            return;
+        }
+        s.expire();
+    }
+
+    public long getThisAccessedTimestamp( String sessionId ) {
+        Session s=sessions.get(sessionId);
+        if(s== null)
+            return -1 ;
+        return s.getThisAccessedTime();
+    }
+
+    public String getThisAccessedTime( String sessionId ) {
+        Session s=sessions.get(sessionId);
+        if( s==null ) {
+            if(log.isInfoEnabled())
+                log.info("Session not found " + sessionId);
+            return "";
+        }
+        return new Date(s.getThisAccessedTime()).toString();
+    }
+
+    public long getLastAccessedTimestamp( String sessionId ) {
+        Session s=sessions.get(sessionId);
+        if(s== null)
+            return -1 ;
+        return s.getLastAccessedTime();
+    }
+
+    public String getLastAccessedTime( String sessionId ) {
+        Session s=sessions.get(sessionId);
+        if( s==null ) {
+            if(log.isInfoEnabled())
+                log.info("Session not found " + sessionId);
+            return "";
+        }
+        return new Date(s.getLastAccessedTime()).toString();
+    }
+
+    public String getCreationTime( String sessionId ) {
+        Session s=sessions.get(sessionId);
+        if( s==null ) {
+            if(log.isInfoEnabled())
+                log.info("Session not found " + sessionId);
+            return "";
+        }
+        return new Date(s.getCreationTime()).toString();
+    }
+
+    public long getCreationTimestamp( String sessionId ) {
+        Session s=sessions.get(sessionId);
+        if(s== null)
+            return -1 ;
+        return s.getCreationTime();
+    }
+
+    
+    /**
+     * Return a String rendering of this object.
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(this.getClass().getName());
+        sb.append('[');
+        if (container == null) {
+            sb.append("Container is null");
+        } else {
+            sb.append(container.getName());
+        }
+        sb.append(']');
+        return sb.toString();
+    }
+    
+    
+    // -------------------- JMX and Registration  --------------------
+    @Override
+    public String getObjectNameKeyProperties() {
+        
+        StringBuilder name = new StringBuilder("type=Manager");
+        
+        if (container instanceof Context) {
+            name.append(",context=");
+            String contextName = container.getName();
+            if (!contextName.startsWith("/")) {
+                name.append('/');
+            }
+            name.append(contextName);
+            
+            Context context = (Context) container;
+            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();
+    }
+
+    @Override
+    public String getDomainInternal() {
+        return MBeanUtils.getDomain(container);
+    }
+
+    // ----------------------------------------- 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("sessionTimeout")) {
+            try {
+                setMaxInactiveInterval(
+                        ((Integer) event.getNewValue()).intValue() * 60);
+            } catch (NumberFormatException e) {
+                log.error(sm.getString("managerBase.sessionTimeout",
+                        event.getNewValue()));
+            }
+        }
+    }
+    
+    // ----------------------------------------------------------- Inner classes
+    
+    protected static final class SessionTiming {
+        private long timestamp;
+        private int duration;
+        
+        public SessionTiming(long timestamp, int duration) {
+            this.timestamp = timestamp;
+            this.duration = duration;
+        }
+        
+        /**
+         * Time stamp associated with this piece of timing information in
+         * milliseconds.
+         */
+        public long getTimestamp() {
+            return timestamp;
+        }
+        
+        /**
+         * Duration associated with this piece of timing information in seconds.
+         */
+        public int getDuration() {
+            return duration;
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/session/PersistentManager.java b/bundles/org.apache.tomcat/src/org/apache/catalina/session/PersistentManager.java
new file mode 100644
index 0000000..c9b979d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/session/PersistentManager.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.catalina.session;
+
+/**
+ * Implementation of the <b>Manager</b> interface that makes use of
+ * a Store to swap active Sessions to disk. It can be configured to
+ * achieve several different goals:
+ *
+ * <li>Persist sessions across restarts of the Container</li>
+ * <li>Fault tolerance, keep sessions backed up on disk to allow
+ *     recovery in the event of unplanned restarts.</li>
+ * <li>Limit the number of active sessions kept in memory by
+ *     swapping less active sessions out to disk.</li>
+ *
+ * @version $Revision: 1.1 $
+ * @author Kief Morris (kief@kief.com)
+ */
+
+public final class PersistentManager extends PersistentManagerBase {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The descriptive information about this implementation.
+     */
+    private static final String info = "PersistentManager/1.0";
+
+
+    /**
+     * The descriptive name of this Manager implementation (for logging).
+     */
+    protected static String name = "PersistentManager";
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return descriptive information about this Manager 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 descriptive short name of this Manager implementation.
+     */
+    @Override
+    public String getName() {
+
+        return (name);
+
+    }
+ }
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/session/PersistentManagerBase.java b/bundles/org.apache.tomcat/src/org/apache/catalina/session/PersistentManagerBase.java
new file mode 100644
index 0000000..05a187b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/session/PersistentManagerBase.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.catalina.session;
+
+import java.io.IOException;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.Session;
+import org.apache.catalina.Store;
+import org.apache.catalina.security.SecurityUtil;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+/**
+ * Extends the <b>ManagerBase</b> class to implement most of the
+ * functionality required by a Manager which supports any kind of
+ * persistence, even if only for  restarts.
+ * <p>
+ * <b>IMPLEMENTATION NOTE</b>:  Correct behavior of session storing and
+ * reloading depends upon external calls to the <code>start()</code> and
+ * <code>stop()</code> methods of this class at the correct times.
+ *
+ * @author Craig R. McClanahan
+ * @author Jean-Francois Arcand
+ * @version $Id: PersistentManagerBase.java,v 1.1 2011/06/28 21:08:18 rherrmann Exp $
+ */
+
+public abstract class PersistentManagerBase extends ManagerBase {
+
+    private static final Log log = LogFactory.getLog(PersistentManagerBase.class);
+
+    // ---------------------------------------------------- Security Classes
+
+    private class PrivilegedStoreClear
+        implements PrivilegedExceptionAction<Void> {
+
+        PrivilegedStoreClear() {
+            // NOOP
+        }
+
+        @Override
+        public Void run() throws Exception{
+           store.clear();
+           return null;
+        }                       
+    }   
+     
+    private class PrivilegedStoreRemove
+        implements PrivilegedExceptionAction<Void> {
+
+        private String id;    
+            
+        PrivilegedStoreRemove(String id) {     
+            this.id = id;
+        }
+
+        @Override
+        public Void run() throws Exception{
+           store.remove(id);
+           return null;
+        }                       
+    }   
+     
+    private class PrivilegedStoreLoad
+        implements PrivilegedExceptionAction<Session> {
+
+        private String id;    
+            
+        PrivilegedStoreLoad(String id) {     
+            this.id = id;
+        }
+
+        @Override
+        public Session run() throws Exception{
+           return store.load(id);
+        }                       
+    }   
+          
+    private class PrivilegedStoreSave
+        implements PrivilegedExceptionAction<Void> {
+
+        private Session session;    
+            
+        PrivilegedStoreSave(Session session) {     
+            this.session = session;
+        }
+
+        @Override
+        public Void run() throws Exception{
+           store.save(session);
+           return null;
+        }                       
+    }   
+     
+    private class PrivilegedStoreKeys
+        implements PrivilegedExceptionAction<String[]> {
+
+        PrivilegedStoreKeys() {
+            // NOOP
+        }
+
+        @Override
+        public String[] run() throws Exception{
+           return store.keys();
+        }                       
+    }
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The descriptive information about this implementation.
+     */
+    private static final String info = "PersistentManagerBase/1.1";
+
+
+    /**
+     * The descriptive name of this Manager implementation (for logging).
+     */
+    private static String name = "PersistentManagerBase";
+
+
+    /**
+     * Store object which will manage the Session store.
+     */
+    protected Store store = null;
+
+
+    /**
+     * Whether to save and reload sessions when the Manager <code>unload</code>
+     * and <code>load</code> methods are called.
+     */
+    protected boolean saveOnRestart = true;
+
+
+    /**
+     * How long a session must be idle before it should be backed up.
+     * -1 means sessions won't be backed up.
+     */
+    protected int maxIdleBackup = -1;
+
+
+    /**
+     * Minimum time a session must be idle before it is swapped to disk.
+     * This overrides maxActiveSessions, to prevent thrashing if there are lots
+     * of active sessions. Setting to -1 means it's ignored.
+     */
+    protected int minIdleSwap = -1;
+
+    /**
+     * The maximum time a session may be idle before it should be swapped
+     * to file just on general principle. Setting this to -1 means sessions
+     * should not be forced out.
+     */
+    protected int maxIdleSwap = -1;
+
+
+    /**
+     * Sessions currently being swapped in and the associated locks
+     */
+    private final Map<String,Object> sessionSwapInLocks =
+        new HashMap<String,Object>();
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Indicates how many seconds old a session can get, after its last use in a
+     * request, before it should be backed up to the store. -1 means sessions
+     * are not backed up.
+     */
+    public int getMaxIdleBackup() {
+
+        return maxIdleBackup;
+
+    }
+
+
+    /**
+     * Sets the option to back sessions up to the Store after they
+     * are used in a request. Sessions remain available in memory
+     * after being backed up, so they are not passivated as they are
+     * when swapped out. The value set indicates how old a session
+     * may get (since its last use) before it must be backed up: -1
+     * means sessions are not backed up.
+     * <p>
+     * Note that this is not a hard limit: sessions are checked
+     * against this age limit periodically according to <b>processExpiresFrequency</b>.
+     * This value should be considered to indicate when a session is
+     * ripe for backing up.
+     * <p>
+     * So it is possible that a session may be idle for maxIdleBackup +
+     * processExpiresFrequency * engine.backgroundProcessorDelay seconds, plus the time it takes to handle other
+     * session expiration, swapping, etc. tasks.
+     *
+     * @param backup The number of seconds after their last accessed
+     * time when they should be written to the Store.
+     */
+    public void setMaxIdleBackup (int backup) {
+
+        if (backup == this.maxIdleBackup)
+            return;
+        int oldBackup = this.maxIdleBackup;
+        this.maxIdleBackup = backup;
+        support.firePropertyChange("maxIdleBackup",
+                                   Integer.valueOf(oldBackup),
+                                   Integer.valueOf(this.maxIdleBackup));
+
+    }
+
+
+    /**
+     * The time in seconds after which a session should be swapped out of
+     * memory to disk.
+     */
+    public int getMaxIdleSwap() {
+
+        return maxIdleSwap;
+
+    }
+
+
+    /**
+     * Sets the time in seconds after which a session should be swapped out of
+     * memory to disk.
+     */
+    public void setMaxIdleSwap(int max) {
+
+        if (max == this.maxIdleSwap)
+            return;
+        int oldMaxIdleSwap = this.maxIdleSwap;
+        this.maxIdleSwap = max;
+        support.firePropertyChange("maxIdleSwap",
+                                   Integer.valueOf(oldMaxIdleSwap),
+                                   Integer.valueOf(this.maxIdleSwap));
+
+    }
+
+
+    /**
+     * The minimum time in seconds that a session must be idle before
+     * it can be swapped out of memory, or -1 if it can be swapped out
+     * at any time.
+     */
+    public int getMinIdleSwap() {
+
+        return minIdleSwap;
+
+    }
+
+
+    /**
+     * Sets the minimum time in seconds that a session must be idle before
+     * it can be swapped out of memory due to maxActiveSession. Set it to -1
+     * if it can be swapped out at any time.
+     */
+    public void setMinIdleSwap(int min) {
+
+        if (this.minIdleSwap == min)
+            return;
+        int oldMinIdleSwap = this.minIdleSwap;
+        this.minIdleSwap = min;
+        support.firePropertyChange("minIdleSwap",
+                                   Integer.valueOf(oldMinIdleSwap),
+                                   Integer.valueOf(this.minIdleSwap));
+
+    }
+
+
+    /**
+     * Return descriptive information about this Manager implementation and
+     * the corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    /**
+     * Return true, if the session id is loaded in memory
+     * otherwise false is returned
+     *
+     * @param id The session id for the session to be searched for
+     */
+    public boolean isLoaded( String id ){
+        try {
+            if ( super.findSession(id) != null )
+                return true;
+        } catch (IOException e) {
+            log.error("checking isLoaded for id, " + id + ", "+e.getMessage(), e);
+        }
+        return false;
+    }
+
+
+    /**
+     * Return the descriptive short name of this Manager implementation.
+     */
+    @Override
+    public String getName() {
+
+        return (name);
+
+    }
+
+
+    /**
+     * Set the Store object which will manage persistent Session
+     * storage for this Manager.
+     *
+     * @param store the associated Store
+     */
+    public void setStore(Store store) {
+        this.store = store;
+        store.setManager(this);
+
+    }
+
+
+    /**
+     * Return the Store object which manages persistent Session
+     * storage for this Manager.
+     */
+    public Store getStore() {
+
+        return (this.store);
+
+    }
+
+
+    /**
+     * Indicates whether sessions are saved when the Manager is shut down
+     * properly. This requires the unload() method to be called.
+     */
+    public boolean getSaveOnRestart() {
+
+        return saveOnRestart;
+
+    }
+
+
+    /**
+     * Set the option to save sessions to the Store when the Manager is
+     * shut down, then loaded when the Manager starts again. If set to
+     * false, any sessions found in the Store may still be picked up when
+     * the Manager is started again.
+     *
+     * @param saveOnRestart true if sessions should be saved on restart, false if
+     *     they should be ignored.
+     */
+    public void setSaveOnRestart(boolean saveOnRestart) {
+
+        if (saveOnRestart == this.saveOnRestart)
+            return;
+
+        boolean oldSaveOnRestart = this.saveOnRestart;
+        this.saveOnRestart = saveOnRestart;
+        support.firePropertyChange("saveOnRestart",
+                                   Boolean.valueOf(oldSaveOnRestart),
+                                   Boolean.valueOf(this.saveOnRestart));
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Clear all sessions from the Store.
+     */
+    public void clearStore() {
+
+        if (store == null)
+            return;
+
+        try {     
+            if (SecurityUtil.isPackageProtectionEnabled()){
+                try{
+                    AccessController.doPrivileged(new PrivilegedStoreClear());
+                }catch(PrivilegedActionException ex){
+                    Exception exception = ex.getException();
+                    log.error("Exception clearing the Store: " + exception,
+                            exception);
+                }
+            } else {
+                store.clear();
+            }
+        } catch (IOException e) {
+            log.error("Exception clearing the Store: " + e, e);
+        }
+
+    }
+
+
+    /**
+     * Implements the Manager interface, direct call to processExpires and processPersistenceChecks
+     */
+    @Override
+    public void processExpires() {
+        
+        long timeNow = System.currentTimeMillis();
+        Session sessions[] = findSessions();
+        int expireHere = 0 ;
+        if(log.isDebugEnabled())
+             log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length);
+        for (int i = 0; i < sessions.length; i++) {
+            if (!sessions[i].isValid()) {
+                expiredSessions.incrementAndGet();
+                expireHere++;
+            }
+        }
+        processPersistenceChecks();
+        if ((getStore() != null) && (getStore() instanceof StoreBase)) {
+            ((StoreBase) getStore()).processExpires();
+        }
+        
+        long timeEnd = System.currentTimeMillis();
+        if(log.isDebugEnabled())
+             log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow) + " expired sessions: " + expireHere);
+        processingTime += (timeEnd - timeNow);
+         
+    }
+
+
+    /**
+     * Called by the background thread after active sessions have been checked
+     * for expiration, to allow sessions to be swapped out, backed up, etc.
+     */
+    public void processPersistenceChecks() {
+
+        processMaxIdleSwaps();
+        processMaxActiveSwaps();
+        processMaxIdleBackups();
+
+    }
+
+
+    /**
+     * Return the active Session, associated with this Manager, with the
+     * specified session id (if any); otherwise return <code>null</code>.
+     * This method checks the persistence store if persistence is enabled,
+     * otherwise just uses the functionality from ManagerBase.
+     *
+     * @param id The session id for the session to be returned
+     *
+     * @exception IllegalStateException if a new session cannot be
+     *  instantiated for any reason
+     * @exception IOException if an input/output error occurs while
+     *  processing this request
+     */
+    @Override
+    public Session findSession(String id) throws IOException {
+
+        Session session = super.findSession(id);
+        // OK, at this point, we're not sure if another thread is trying to
+        // remove the session or not so the only way around this is to lock it
+        // (or attempt to) and then try to get it by this session id again. If
+        // the other code ran swapOut, then we should get a null back during
+        // this run, and if not, we lock it out so we can access the session
+        // safely.
+        if(session != null) {
+            synchronized(session){
+                session = super.findSession(session.getIdInternal());
+                if(session != null){
+                   // To keep any external calling code from messing up the
+                   // concurrency.
+                   session.access();
+                   session.endAccess();
+                }
+            }
+        }
+        if (session != null)
+            return (session);
+
+        // See if the Session is in the Store
+        session = swapIn(id);
+        return (session);
+
+    }
+
+    /**
+     * Remove this Session from the active Sessions for this Manager,
+     * but not from the Store. (Used by the PersistentValve)
+     *
+     * @param session Session to be removed
+     */
+    public void removeSuper(Session session) {
+        super.remove (session);
+    }
+
+    /**
+     * Load all sessions found in the persistence mechanism, assuming
+     * they are marked as valid and have not passed their expiration
+     * limit. If persistence is not supported, this method returns
+     * without doing anything.
+     * <p>
+     * Note that by default, this method is not called by the MiddleManager
+     * class. In order to use it, a subclass must specifically call it,
+     * for example in the start() and/or processPersistenceChecks() methods.
+     */
+    @Override
+    public void load() {
+
+        // Initialize our internal data structures
+        sessions.clear();
+
+        if (store == null)
+            return;
+
+        String[] ids = null;
+        try {
+            if (SecurityUtil.isPackageProtectionEnabled()){
+                try{
+                    ids = AccessController.doPrivileged(
+                            new PrivilegedStoreKeys());
+                }catch(PrivilegedActionException ex){
+                    Exception exception = ex.getException();
+                    log.error("Exception in the Store during load: "
+                              + exception, exception);
+                    return;
+                }
+            } else {
+                ids = store.keys();
+            }
+        } catch (IOException e) {
+            log.error("Can't load sessions from store, " + e.getMessage(), e);
+            return;
+        }
+
+        int n = ids.length;
+        if (n == 0)
+            return;
+
+        if (log.isDebugEnabled())
+            log.debug(sm.getString("persistentManager.loading", String.valueOf(n)));
+
+        for (int i = 0; i < n; i++)
+            try {
+                swapIn(ids[i]);
+            } catch (IOException e) {
+                log.error("Failed load session from store, " + e.getMessage(), e);
+            }
+
+    }
+
+
+    /**
+     * Remove this Session from the active Sessions for this Manager,
+     * and from the Store.
+     *
+     * @param session Session to be removed
+     */
+    @Override
+    public void remove(Session session, boolean update) {
+
+        super.remove (session, update);
+
+        if (store != null){
+            removeSession(session.getIdInternal());
+        }
+    }
+
+    
+    /**
+     * Remove this Session from the active Sessions for this Manager,
+     * and from the Store.
+     *
+     * @param id Session's id to be removed
+     */    
+    protected void removeSession(String id){
+        try {
+            if (SecurityUtil.isPackageProtectionEnabled()){
+                try{
+                    AccessController.doPrivileged(new PrivilegedStoreRemove(id));
+                }catch(PrivilegedActionException ex){
+                    Exception exception = ex.getException();
+                    log.error("Exception in the Store during removeSession: "
+                              + exception, exception);
+                }
+            } else {
+                 store.remove(id);
+            }               
+        } catch (IOException e) {
+            log.error("Exception removing session  " + e.getMessage(), e);
+        }        
+    }
+
+    /**
+     * Save all currently active sessions in the appropriate persistence
+     * mechanism, if any.  If persistence is not supported, this method
+     * returns without doing anything.
+     * <p>
+     * Note that by default, this method is not called by the MiddleManager
+     * class. In order to use it, a subclass must specifically call it,
+     * for example in the stop() and/or processPersistenceChecks() methods.
+     */
+    @Override
+    public void unload() {
+
+        if (store == null)
+            return;
+
+        Session sessions[] = findSessions();
+        int n = sessions.length;
+        if (n == 0)
+            return;
+
+        if (log.isDebugEnabled())
+            log.debug(sm.getString("persistentManager.unloading",
+                             String.valueOf(n)));
+
+        for (int i = 0; i < n; i++)
+            try {
+                swapOut(sessions[i]);
+            } catch (IOException e) {
+                // This is logged in writeSession()
+            }
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Look for a session in the Store and, if found, restore
+     * it in the Manager's list of active sessions if appropriate.
+     * The session will be removed from the Store after swapping
+     * in, but will not be added to the active session list if it
+     * is invalid or past its expiration.
+     */
+    protected Session swapIn(String id) throws IOException {
+
+        if (store == null)
+            return null;
+
+        Object swapInLock = null;
+
+        /*
+         * The purpose of this sync and these locks is to make sure that a
+         * session is only loaded once. It doesn't matter if the lock is removed
+         * and then another thread enters this method and tries to load the same
+         * session. That thread will re-create a swapIn lock for that session,
+         * quickly find that the session is already in sessions, use it and
+         * carry on.
+         */
+        synchronized (this) {
+            swapInLock = sessionSwapInLocks.get(id);
+            if (swapInLock == null) {
+                swapInLock = new Object();
+                sessionSwapInLocks.put(id, swapInLock);
+            }
+        }
+
+        Session session = null;
+
+        synchronized (swapInLock) {
+            // First check to see if another thread has loaded the session into
+            // the manager
+            session = sessions.get(id);
+
+            if (session == null) {
+                try {
+                    if (SecurityUtil.isPackageProtectionEnabled()){
+                        try {
+                            session = AccessController.doPrivileged(
+                                    new PrivilegedStoreLoad(id));
+                        } catch (PrivilegedActionException ex) {
+                            Exception e = ex.getException();
+                            log.error(sm.getString(
+                                    "persistentManager.swapInException", id),
+                                    e);
+                            if (e instanceof IOException){
+                                throw (IOException)e;
+                            } else if (e instanceof ClassNotFoundException) {
+                                throw (ClassNotFoundException)e;
+                            }
+                        }
+                    } else {
+                         session = store.load(id);
+                    }
+                } catch (ClassNotFoundException e) {
+                    String msg = sm.getString(
+                            "persistentManager.deserializeError", id);
+                    log.error(msg, e);
+                    throw new IllegalStateException(msg, e);
+                }
+
+                if (session != null && !session.isValid()) {
+                    log.error(sm.getString(
+                            "persistentManager.swapInInvalid", id));
+                    session.expire();
+                    removeSession(id);
+                    session = null;
+                }
+
+                if (session != null) {
+                    if(log.isDebugEnabled())
+                        log.debug(sm.getString("persistentManager.swapIn", id));
+
+                    session.setManager(this);
+                    // make sure the listeners know about it.
+                    ((StandardSession)session).tellNew();
+                    add(session);
+                    ((StandardSession)session).activate();
+                    // endAccess() to ensure timeouts happen correctly.
+                    // access() to keep access count correct or it will end up
+                    // negative
+                    session.access();
+                    session.endAccess();
+                }
+            }
+        }
+
+        // Make sure the lock is removed
+        synchronized (this) {
+            sessionSwapInLocks.remove(id);
+        }
+
+        return (session);
+
+    }
+
+
+    /**
+     * Remove the session from the Manager's list of active
+     * sessions and write it out to the Store. If the session
+     * is past its expiration or invalid, this method does
+     * nothing.
+     *
+     * @param session The Session to write out.
+     */
+    protected void swapOut(Session session) throws IOException {
+
+        if (store == null || !session.isValid()) {
+            return;
+        }
+
+        ((StandardSession)session).passivate();
+        writeSession(session);
+        super.remove(session, true);
+        session.recycle();
+
+    }
+
+
+    /**
+     * Write the provided session to the Store without modifying
+     * the copy in memory or triggering passivation events. Does
+     * nothing if the session is invalid or past its expiration.
+     */
+    protected void writeSession(Session session) throws IOException {
+
+        if (store == null || !session.isValid()) {
+            return;
+        }
+
+        try {
+            if (SecurityUtil.isPackageProtectionEnabled()){
+                try{
+                    AccessController.doPrivileged(new PrivilegedStoreSave(session));
+                }catch(PrivilegedActionException ex){
+                    Exception exception = ex.getException();
+                    if (exception instanceof IOException) {
+                        throw (IOException) exception;
+                    }
+                    log.error("Exception in the Store during writeSession: "
+                              + exception, exception);
+                }
+            } else {
+                 store.save(session);
+            }   
+        } catch (IOException e) {
+            log.error(sm.getString
+                ("persistentManager.serializeError", session.getIdInternal(), e));
+            throw e;
+        }
+
+    }
+
+
+    /**
+     * 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 {
+
+        super.startInternal();
+
+        if (store == null)
+            log.error("No Store configured, persistence disabled");
+        else if (store instanceof Lifecycle)
+            ((Lifecycle)store).start();
+
+        setState(LifecycleState.STARTING);
+    }
+
+
+    /**
+     * 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 {
+
+        if (log.isDebugEnabled())
+            log.debug("Stopping");
+
+        setState(LifecycleState.STOPPING);
+        
+        if (getStore() != null && saveOnRestart) {
+            unload();
+        } else {
+            // Expire all active sessions
+            Session sessions[] = findSessions();
+            for (int i = 0; i < sessions.length; i++) {
+                StandardSession session = (StandardSession) sessions[i];
+                if (!session.isValid())
+                    continue;
+                session.expire();
+            }
+        }
+
+        if (getStore() != null && getStore() instanceof Lifecycle)
+            ((Lifecycle)getStore()).stop();
+
+        // Require a new random number generator if we are restarted
+        super.stopInternal();
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Swap idle sessions out to Store if they are idle too long.
+     */
+    protected void processMaxIdleSwaps() {
+
+        if (!getState().isAvailable() || maxIdleSwap < 0)
+            return;
+
+        Session sessions[] = findSessions();
+        long timeNow = System.currentTimeMillis();
+
+        // Swap out all sessions idle longer than maxIdleSwap
+        if (maxIdleSwap >= 0) {
+            for (int i = 0; i < sessions.length; i++) {
+                StandardSession session = (StandardSession) sessions[i];
+                synchronized (session) {
+                    if (!session.isValid())
+                        continue;
+                    int timeIdle = // Truncate, do not round up
+                        (int) ((timeNow - session.getThisAccessedTime()) / 1000L);
+                    if (timeIdle > maxIdleSwap && timeIdle > minIdleSwap) {
+                        if (session.accessCount != null &&
+                                session.accessCount.get() > 0) {
+                            // Session is currently being accessed - skip it
+                            continue;
+                        }
+                        if (log.isDebugEnabled())
+                            log.debug(sm.getString
+                                ("persistentManager.swapMaxIdle",
+                                 session.getIdInternal(),
+                                 Integer.valueOf(timeIdle)));
+                        try {
+                            swapOut(session);
+                        } catch (IOException e) {
+                            // This is logged in writeSession()
+                        }
+                    }
+                }
+            }
+        }
+
+    }
+
+
+    /**
+     * Swap idle sessions out to Store if too many are active
+     */
+    protected void processMaxActiveSwaps() {
+
+        if (!getState().isAvailable() || getMaxActiveSessions() < 0)
+            return;
+
+        Session sessions[] = findSessions();
+
+        // FIXME: Smarter algorithm (LRU)
+        if (getMaxActiveSessions() >= sessions.length)
+            return;
+
+        if(log.isDebugEnabled())
+            log.debug(sm.getString
+                ("persistentManager.tooManyActive",
+                 Integer.valueOf(sessions.length)));
+
+        int toswap = sessions.length - getMaxActiveSessions();
+        long timeNow = System.currentTimeMillis();
+
+        for (int i = 0; i < sessions.length && toswap > 0; i++) {
+            StandardSession session =  (StandardSession) sessions[i];
+            synchronized (session) {
+                int timeIdle = // Truncate, do not round up
+                    (int) ((timeNow - session.getThisAccessedTime()) / 1000L);
+                if (timeIdle > minIdleSwap) {
+                    if (session.accessCount != null &&
+                            session.accessCount.get() > 0) {
+                        // Session is currently being accessed - skip it
+                        continue;
+                    }
+                    if(log.isDebugEnabled())
+                        log.debug(sm.getString
+                            ("persistentManager.swapTooManyActive",
+                             session.getIdInternal(),
+                             Integer.valueOf(timeIdle)));
+                    try {
+                        swapOut(session);
+                    } catch (IOException e) {
+                        // This is logged in writeSession()
+                    }
+                    toswap--;
+                }
+            }
+        }
+
+    }
+
+
+    /**
+     * Back up idle sessions.
+     */
+    protected void processMaxIdleBackups() {
+
+        if (!getState().isAvailable() || maxIdleBackup < 0)
+            return;
+
+        Session sessions[] = findSessions();
+        long timeNow = System.currentTimeMillis();
+
+        // Back up all sessions idle longer than maxIdleBackup
+        if (maxIdleBackup >= 0) {
+            for (int i = 0; i < sessions.length; i++) {
+                StandardSession session = (StandardSession) sessions[i];
+                synchronized (session) {
+                    if (!session.isValid())
+                        continue;
+                    int timeIdle = // Truncate, do not round up
+                        (int) ((timeNow - session.getThisAccessedTime()) / 1000L);
+                    if (timeIdle > maxIdleBackup) {
+                        if (log.isDebugEnabled())
+                            log.debug(sm.getString
+                                ("persistentManager.backupMaxIdle",
+                                session.getIdInternal(),
+                                Integer.valueOf(timeIdle)));
+    
+                        try {
+                            writeSession(session);
+                        } catch (IOException e) {
+                            // This is logged in writeSession()
+                        }
+                    }
+                }
+            }
+        }
+
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/session/StandardManager.java b/bundles/org.apache.tomcat/src/org/apache/catalina/session/StandardManager.java
new file mode 100644
index 0000000..ae21bec
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/session/StandardManager.java
@@ -0,0 +1,552 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.session;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import javax.servlet.ServletContext;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.Loader;
+import org.apache.catalina.Session;
+import org.apache.catalina.security.SecurityUtil;
+import org.apache.catalina.util.CustomObjectInputStream;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+/**
+ * Standard implementation of the <b>Manager</b> interface that provides
+ * simple session persistence across restarts of this component (such as
+ * when the entire server is shut down and restarted, or when a particular
+ * web application is reloaded.
+ * <p>
+ * <b>IMPLEMENTATION NOTE</b>:  Correct behavior of session storing and
+ * reloading depends upon external calls to the <code>start()</code> and
+ * <code>stop()</code> methods of this class at the correct times.
+ *
+ * @author Craig R. McClanahan
+ * @author Jean-Francois Arcand
+ * @version $Id: StandardManager.java,v 1.1 2011/06/28 21:08:19 rherrmann Exp $
+ */
+
+public class StandardManager extends ManagerBase {
+
+    private final Log log = LogFactory.getLog(StandardManager.class); // must not be static
+
+    // ---------------------------------------------------- Security Classes
+    private class PrivilegedDoLoad
+        implements PrivilegedExceptionAction<Void> {
+
+        PrivilegedDoLoad() {
+            // NOOP
+        }
+
+        @Override
+        public Void run() throws Exception{
+           doLoad();
+           return null;
+        }
+    }
+
+    private class PrivilegedDoUnload
+        implements PrivilegedExceptionAction<Void> {
+
+        PrivilegedDoUnload() {
+            // NOOP
+        }
+
+        @Override
+        public Void run() throws Exception{
+            doUnload();
+            return null;
+        }
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The descriptive information about this implementation.
+     */
+    protected static final String info = "StandardManager/1.0";
+
+
+    /**
+     * The descriptive name of this Manager implementation (for logging).
+     */
+    protected static String name = "StandardManager";
+
+
+    /**
+     * Path name of the disk file in which active sessions are saved
+     * when we stop, and from which these sessions are loaded when we start.
+     * A <code>null</code> value indicates that no persistence is desired.
+     * If this pathname is relative, it will be resolved against the
+     * temporary working directory provided by our context, available via
+     * the <code>javax.servlet.context.tempdir</code> context attribute.
+     */
+    protected String pathname = "SESSIONS.ser";
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return descriptive information about this Manager 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 descriptive short name of this Manager implementation.
+     */
+    @Override
+    public String getName() {
+
+        return (name);
+
+    }
+
+
+    /**
+     * Return the session persistence pathname, if any.
+     */
+    public String getPathname() {
+
+        return (this.pathname);
+
+    }
+
+
+    /**
+     * Set the session persistence pathname to the specified value.  If no
+     * persistence support is desired, set the pathname to <code>null</code>.
+     *
+     * @param pathname New session persistence pathname
+     */
+    public void setPathname(String pathname) {
+
+        String oldPathname = this.pathname;
+        this.pathname = pathname;
+        support.firePropertyChange("pathname", oldPathname, this.pathname);
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Load any currently active sessions that were previously unloaded
+     * to the appropriate persistence mechanism, if any.  If persistence is not
+     * supported, this method returns without doing anything.
+     *
+     * @exception ClassNotFoundException if a serialized class cannot be
+     *  found during the reload
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public void load() throws ClassNotFoundException, IOException {
+        if (SecurityUtil.isPackageProtectionEnabled()){
+            try{
+                AccessController.doPrivileged( new PrivilegedDoLoad() );
+            } catch (PrivilegedActionException ex){
+                Exception exception = ex.getException();
+                if (exception instanceof ClassNotFoundException){
+                    throw (ClassNotFoundException)exception;
+                } else if (exception instanceof IOException){
+                    throw (IOException)exception;
+                }
+                if (log.isDebugEnabled())
+                    log.debug("Unreported exception in load() "
+                        + exception);
+            }
+        } else {
+            doLoad();
+        }
+    }
+
+
+    /**
+     * Load any currently active sessions that were previously unloaded
+     * to the appropriate persistence mechanism, if any.  If persistence is not
+     * supported, this method returns without doing anything.
+     *
+     * @exception ClassNotFoundException if a serialized class cannot be
+     *  found during the reload
+     * @exception IOException if an input/output error occurs
+     */
+    protected void doLoad() throws ClassNotFoundException, IOException {
+        if (log.isDebugEnabled())
+            log.debug("Start: Loading persisted sessions");
+
+        // Initialize our internal data structures
+        sessions.clear();
+
+        // Open an input stream to the specified pathname, if any
+        File file = file();
+        if (file == null)
+            return;
+        if (log.isDebugEnabled())
+            log.debug(sm.getString("standardManager.loading", pathname));
+        FileInputStream fis = null;
+        BufferedInputStream bis = null;
+        ObjectInputStream ois = null;
+        Loader loader = null;
+        ClassLoader classLoader = null;
+        try {
+            fis = new FileInputStream(file.getAbsolutePath());
+            bis = new BufferedInputStream(fis);
+            if (container != null)
+                loader = container.getLoader();
+            if (loader != null)
+                classLoader = loader.getClassLoader();
+            if (classLoader != null) {
+                if (log.isDebugEnabled())
+                    log.debug("Creating custom object input stream for class loader ");
+                ois = new CustomObjectInputStream(bis, classLoader);
+            } else {
+                if (log.isDebugEnabled())
+                    log.debug("Creating standard object input stream");
+                ois = new ObjectInputStream(bis);
+            }
+        } catch (FileNotFoundException e) {
+            if (log.isDebugEnabled())
+                log.debug("No persisted data file found");
+            return;
+        } catch (IOException e) {
+            log.error(sm.getString("standardManager.loading.ioe", e), e);
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (IOException f) {
+                    // Ignore
+                }
+            }
+            if (bis != null) {
+                try {
+                    bis.close();
+                } catch (IOException f) {
+                    // Ignore
+                }
+            }
+            throw e;
+        }
+
+        // Load the previously unloaded active sessions
+        synchronized (sessions) {
+            try {
+                Integer count = (Integer) ois.readObject();
+                int n = count.intValue();
+                if (log.isDebugEnabled())
+                    log.debug("Loading " + n + " persisted sessions");
+                for (int i = 0; i < n; i++) {
+                    StandardSession session = getNewSession();
+                    session.readObjectData(ois);
+                    session.setManager(this);
+                    sessions.put(session.getIdInternal(), session);
+                    session.activate();
+                    if (!session.isValidInternal()) {
+                        // If session is already invalid,
+                        // expire session to prevent memory leak.
+                        session.setValid(true);
+                        session.expire();
+                    }
+                    sessionCounter++;
+                }
+            } catch (ClassNotFoundException e) {
+                log.error(sm.getString("standardManager.loading.cnfe", e), e);
+                try {
+                    ois.close();
+                } catch (IOException f) {
+                    // Ignore
+                }
+                throw e;
+            } catch (IOException e) {
+                log.error(sm.getString("standardManager.loading.ioe", e), e);
+                try {
+                    ois.close();
+                } catch (IOException f) {
+                    // Ignore
+                }
+                throw e;
+            } finally {
+                // Close the input stream
+                try {
+                    ois.close();
+                } catch (IOException f) {
+                    // ignored
+                }
+
+                // Delete the persistent storage file
+                if (file.exists() )
+                    file.delete();
+            }
+        }
+
+        if (log.isDebugEnabled())
+            log.debug("Finish: Loading persisted sessions");
+    }
+
+
+    /**
+     * Save any currently active sessions in the appropriate persistence
+     * mechanism, if any.  If persistence is not supported, this method
+     * returns without doing anything.
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public void unload() throws IOException {
+        if (SecurityUtil.isPackageProtectionEnabled()){
+            try{
+                AccessController.doPrivileged( new PrivilegedDoUnload() );
+            } catch (PrivilegedActionException ex){
+                Exception exception = ex.getException();
+                if (exception instanceof IOException){
+                    throw (IOException)exception;
+                }
+                if (log.isDebugEnabled())
+                    log.debug("Unreported exception in unLoad() "
+                        + exception);
+            }
+        } else {
+            doUnload();
+        }
+    }
+
+
+    /**
+     * Save any currently active sessions in the appropriate persistence
+     * mechanism, if any.  If persistence is not supported, this method
+     * returns without doing anything.
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    protected void doUnload() throws IOException {
+
+        if (log.isDebugEnabled())
+            log.debug(sm.getString("standardManager.unloading.debug"));
+
+        if (sessions.isEmpty()) {
+            log.debug(sm.getString("standardManager.unloading.nosessions"));
+            return; // nothing to do
+        }
+
+        // Open an output stream to the specified pathname, if any
+        File file = file();
+        if (file == null)
+            return;
+        if (log.isDebugEnabled())
+            log.debug(sm.getString("standardManager.unloading", pathname));
+        FileOutputStream fos = null;
+        ObjectOutputStream oos = null;
+        try {
+            fos = new FileOutputStream(file.getAbsolutePath());
+            oos = new ObjectOutputStream(new BufferedOutputStream(fos));
+        } catch (IOException e) {
+            log.error(sm.getString("standardManager.unloading.ioe", e), e);
+            if (fos != null) {
+                try {
+                    fos.close();
+                } catch (IOException f) {
+                    // Ignore
+                }
+            }
+            throw e;
+        }
+
+        // Write the number of active sessions, followed by the details
+        ArrayList<StandardSession> list = new ArrayList<StandardSession>();
+        synchronized (sessions) {
+            if (log.isDebugEnabled())
+                log.debug("Unloading " + sessions.size() + " sessions");
+            try {
+                oos.writeObject(new Integer(sessions.size()));
+                Iterator<Session> elements = sessions.values().iterator();
+                while (elements.hasNext()) {
+                    StandardSession session =
+                        (StandardSession) elements.next();
+                    list.add(session);
+                    session.passivate();
+                    session.writeObjectData(oos);
+                }
+            } catch (IOException e) {
+                log.error(sm.getString("standardManager.unloading.ioe", e), e);
+                try {
+                    oos.close();
+                } catch (IOException f) {
+                    // Ignore
+                }
+                throw e;
+            }
+        }
+
+        // Flush and close the output stream
+        try {
+            oos.flush();
+        } finally {
+            try {
+                oos.close();
+            } catch (IOException f) {
+                // Ignore
+            }
+        }
+
+        // Expire all the sessions we just wrote
+        if (log.isDebugEnabled())
+            log.debug("Expiring " + list.size() + " persisted sessions");
+        Iterator<StandardSession> expires = list.iterator();
+        while (expires.hasNext()) {
+            StandardSession session = expires.next();
+            try {
+                session.expire(false);
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+            } finally {
+                session.recycle();
+            }
+        }
+
+        if (log.isDebugEnabled())
+            log.debug("Unloading complete");
+
+    }
+
+
+    /**
+     * 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 {
+
+        super.startInternal();
+
+        // Load unloaded sessions, if any
+        try {
+            load();
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.error(sm.getString("standardManager.managerLoad"), t);
+        }
+
+        setState(LifecycleState.STARTING);
+    }
+
+
+    /**
+     * 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 {
+
+        if (log.isDebugEnabled())
+            log.debug("Stopping");
+
+        setState(LifecycleState.STOPPING);
+        
+        // Write out sessions
+        try {
+            unload();
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.error(sm.getString("standardManager.managerUnload"), t);
+        }
+
+        // Expire all active sessions
+        Session sessions[] = findSessions();
+        for (int i = 0; i < sessions.length; i++) {
+            Session session = sessions[i];
+            try {
+                if (session.isValid()) {
+                    session.expire();
+                }
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+            } finally {
+                // Measure against memory leaking if references to the session
+                // object are kept in a shared field somewhere
+                session.recycle();
+            }
+        }
+
+        // Require a new random number generator if we are restarted
+        super.stopInternal();
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Return a File object representing the pathname to our
+     * persistence file, if any.
+     */
+    protected File file() {
+
+        if ((pathname == null) || (pathname.length() == 0))
+            return (null);
+        File file = new File(pathname);
+        if (!file.isAbsolute()) {
+            if (container instanceof Context) {
+                ServletContext servletContext =
+                    ((Context) container).getServletContext();
+                File tempdir = (File)
+                    servletContext.getAttribute(ServletContext.TEMPDIR);
+                if (tempdir != null)
+                    file = new File(tempdir, pathname);
+            }
+        }
+//        if (!file.isAbsolute())
+//            return (null);
+        return (file);
+
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/session/StandardSession.java b/bundles/org.apache.tomcat/src/org/apache/catalina/session/StandardSession.java
new file mode 100644
index 0000000..6dec74c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/session/StandardSession.java
@@ -0,0 +1,1906 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.session;
+
+
+import java.beans.PropertyChangeSupport;
+import java.io.IOException;
+import java.io.NotSerializableException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.security.AccessController;
+import java.security.Principal;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionActivationListener;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Globals;
+import org.apache.catalina.Manager;
+import org.apache.catalina.Session;
+import org.apache.catalina.SessionEvent;
+import org.apache.catalina.SessionListener;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.apache.catalina.security.SecurityUtil;
+import org.apache.catalina.util.Enumerator;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Standard implementation of the <b>Session</b> interface.  This object is
+ * serializable, so that it can be stored in persistent storage or transferred
+ * to a different JVM for distributable session support.
+ * <p>
+ * <b>IMPLEMENTATION NOTE</b>:  An instance of this class represents both the
+ * internal (Session) and application level (HttpSession) view of the session.
+ * However, because the class itself is not declared public, Java logic outside
+ * of the <code>org.apache.catalina.session</code> package cannot cast an
+ * HttpSession view of this instance back to a Session view.
+ * <p>
+ * <b>IMPLEMENTATION NOTE</b>:  If you add fields to this class, you must
+ * make sure that you carry them over in the read/writeObject methods so
+ * that this class is properly serialized.
+ *
+ * @author Craig R. McClanahan
+ * @author Sean Legassick
+ * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
+ * @version $Id: StandardSession.java,v 1.1 2011/06/28 21:08:19 rherrmann Exp $
+ */
+
+public class StandardSession implements HttpSession, Session, Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    protected static final boolean STRICT_SERVLET_COMPLIANCE;
+
+    protected static final boolean ACTIVITY_CHECK;
+
+    protected static final boolean LAST_ACCESS_AT_START;
+
+    static {
+        STRICT_SERVLET_COMPLIANCE = Globals.STRICT_SERVLET_COMPLIANCE;
+        
+        String activityCheck = System.getProperty(
+                "org.apache.catalina.session.StandardSession.ACTIVITY_CHECK");
+        if (activityCheck == null) {
+            ACTIVITY_CHECK = STRICT_SERVLET_COMPLIANCE;
+        } else {
+            ACTIVITY_CHECK =
+                Boolean.valueOf(activityCheck).booleanValue();
+        }
+
+        String lastAccessAtStart = System.getProperty(
+                "org.apache.catalina.session.StandardSession.LAST_ACCESS_AT_START");
+        if (lastAccessAtStart == null) {
+            LAST_ACCESS_AT_START = STRICT_SERVLET_COMPLIANCE;
+        } else {
+            LAST_ACCESS_AT_START =
+                Boolean.valueOf(lastAccessAtStart).booleanValue();
+        }
+    }
+    
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new Session associated with the specified Manager.
+     *
+     * @param manager The manager with which this Session is associated
+     */
+    public StandardSession(Manager manager) {
+
+        super();
+        this.manager = manager;
+
+        // Initialize access count
+        if (ACTIVITY_CHECK) {
+            accessCount = new AtomicInteger();
+        }
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Type array.
+     */
+    protected static final String EMPTY_ARRAY[] = new String[0];
+
+
+    /**
+     * The dummy attribute value serialized when a NotSerializableException is
+     * encountered in <code>writeObject()</code>.
+     */
+    protected static final String NOT_SERIALIZED =
+        "___NOT_SERIALIZABLE_EXCEPTION___";
+
+
+    /**
+     * The collection of user data attributes associated with this Session.
+     */
+    protected Map<String, Object> attributes = new ConcurrentHashMap<String, Object>();
+
+
+    /**
+     * The authentication type used to authenticate our cached Principal,
+     * if any.  NOTE:  This value is not included in the serialized
+     * version of this object.
+     */
+    protected transient String authType = null;
+
+
+    /**
+     * The time this session was created, in milliseconds since midnight,
+     * January 1, 1970 GMT.
+     */
+    protected long creationTime = 0L;
+
+
+    /**
+     * Set of attribute names which are not allowed to be persisted.
+     */
+    protected static final String[] excludedAttributes = {
+        Globals.SUBJECT_ATTR,
+        Globals.GSS_CREDENTIAL_ATTR
+    };
+
+
+    /**
+     * We are currently processing a session expiration, so bypass
+     * certain IllegalStateException tests.  NOTE:  This value is not
+     * included in the serialized version of this object.
+     */
+    protected transient volatile boolean expiring = false;
+
+
+    /**
+     * The facade associated with this session.  NOTE:  This value is not
+     * included in the serialized version of this object.
+     */
+    protected transient StandardSessionFacade facade = null;
+
+
+    /**
+     * The session identifier of this Session.
+     */
+    protected String id = null;
+
+
+    /**
+     * Descriptive information describing this Session implementation.
+     */
+    protected static final String info = "StandardSession/1.0";
+
+
+    /**
+     * The last accessed time for this Session.
+     */
+    protected volatile long lastAccessedTime = creationTime;
+
+
+    /**
+     * The session event listeners for this Session.
+     */
+    protected transient ArrayList<SessionListener> listeners =
+        new ArrayList<SessionListener>();
+
+
+    /**
+     * The Manager with which this Session is associated.
+     */
+    protected transient Manager manager = null;
+
+
+    /**
+     * The maximum time interval, in seconds, between client requests before
+     * the servlet container may invalidate this session.  A negative time
+     * indicates that the session should never time out.
+     */
+    protected int maxInactiveInterval = -1;
+
+
+    /**
+     * Flag indicating whether this session is new or not.
+     */
+    protected boolean isNew = false;
+
+
+    /**
+     * Flag indicating whether this session is valid or not.
+     */
+    protected volatile boolean isValid = false;
+
+    
+    /**
+     * Internal notes associated with this session by Catalina components
+     * and event listeners.  <b>IMPLEMENTATION NOTE:</b> This object is
+     * <em>not</em> saved and restored across session serializations!
+     */
+    protected transient Map<String, Object> notes = new Hashtable<String, Object>();
+
+
+    /**
+     * The authenticated Principal associated with this session, if any.
+     * <b>IMPLEMENTATION NOTE:</b>  This object is <i>not</i> saved and
+     * restored across session serializations!
+     */
+    protected transient Principal principal = null;
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
+     * The HTTP session context associated with this session.
+     */
+    @Deprecated
+    protected static volatile
+            javax.servlet.http.HttpSessionContext sessionContext = null;
+
+
+    /**
+     * The property change support for this component.  NOTE:  This value
+     * is not included in the serialized version of this object.
+     */
+    protected transient PropertyChangeSupport support =
+        new PropertyChangeSupport(this);
+
+
+    /**
+     * The current accessed time for this session.
+     */
+    protected volatile long thisAccessedTime = creationTime;
+
+
+    /**
+     * The access count for this session.
+     */
+    protected transient AtomicInteger accessCount = null;
+
+    
+    // ----------------------------------------------------- Session Properties
+
+
+    /**
+     * Return the authentication type used to authenticate our cached
+     * Principal, if any.
+     */
+    @Override
+    public String getAuthType() {
+
+        return (this.authType);
+
+    }
+
+
+    /**
+     * Set the authentication type used to authenticate our cached
+     * Principal, if any.
+     *
+     * @param authType The new cached authentication type
+     */
+    @Override
+    public void setAuthType(String authType) {
+
+        String oldAuthType = this.authType;
+        this.authType = authType;
+        support.firePropertyChange("authType", oldAuthType, this.authType);
+
+    }
+
+
+    /**
+     * Set the creation time for this session.  This method is called by the
+     * Manager when an existing Session instance is reused.
+     *
+     * @param time The new creation time
+     */
+    @Override
+    public void setCreationTime(long time) {
+
+        this.creationTime = time;
+        this.lastAccessedTime = time;
+        this.thisAccessedTime = time;
+
+    }
+
+
+    /**
+     * Return the session identifier for this session.
+     */
+    @Override
+    public String getId() {
+
+        return (this.id);
+
+    }
+
+
+    /**
+     * Return the session identifier for this session.
+     */
+    @Override
+    public String getIdInternal() {
+
+        return (this.id);
+
+    }
+
+
+    /**
+     * Set the session identifier for this session.
+     *
+     * @param id The new session identifier
+     */
+    @Override
+    public void setId(String id) {
+
+        if ((this.id != null) && (manager != null))
+            manager.remove(this);
+
+        this.id = id;
+
+        if (manager != null)
+            manager.add(this);
+        tellNew();
+    }
+
+
+    /**
+     * Inform the listeners about the new session.
+     *
+     */
+    public void tellNew() {
+
+        // Notify interested session event listeners
+        fireSessionEvent(Session.SESSION_CREATED_EVENT, null);
+
+        // Notify interested application event listeners
+        Context context = (Context) manager.getContainer();
+        Object listeners[] = context.getApplicationLifecycleListeners();
+        if (listeners != null) {
+            HttpSessionEvent event =
+                new HttpSessionEvent(getSession());
+            for (int i = 0; i < listeners.length; i++) {
+                if (!(listeners[i] instanceof HttpSessionListener))
+                    continue;
+                HttpSessionListener listener =
+                    (HttpSessionListener) listeners[i];
+                try {
+                    fireContainerEvent(context,
+                                       "beforeSessionCreated",
+                                       listener);
+                    listener.sessionCreated(event);
+                    fireContainerEvent(context,
+                                       "afterSessionCreated",
+                                       listener);
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    try {
+                        fireContainerEvent(context,
+                                           "afterSessionCreated",
+                                           listener);
+                    } catch (Exception e) {
+                        // Ignore
+                    }
+                    manager.getContainer().getLogger().error
+                        (sm.getString("standardSession.sessionEvent"), t);
+                }
+            }
+        }
+
+    }
+
+
+    /**
+     * Return descriptive information about this Session 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 last time the client sent a request associated with this
+     * session, as the number of milliseconds since midnight, January 1, 1970
+     * GMT.  Actions that your application takes, such as getting or setting
+     * a value associated with the session, do not affect the access time.
+     * This one gets updated whenever a request starts.
+     */
+    @Override
+    public long getThisAccessedTime() {
+
+        if (!isValidInternal()) {
+            throw new IllegalStateException
+                (sm.getString("standardSession.getThisAccessedTime.ise"));
+        }
+
+        return (this.thisAccessedTime);
+    }
+
+    /**
+     * Return the last client access time without invalidation check
+     * @see #getThisAccessedTime()
+     */
+    @Override
+    public long getThisAccessedTimeInternal() {
+        return (this.thisAccessedTime);
+    }
+
+    /**
+     * Return the last time the client sent a request associated with this
+     * session, as the number of milliseconds since midnight, January 1, 1970
+     * GMT.  Actions that your application takes, such as getting or setting
+     * a value associated with the session, do not affect the access time.
+     * This one gets updated whenever a request finishes.
+     */
+    @Override
+    public long getLastAccessedTime() {
+
+        if (!isValidInternal()) {
+            throw new IllegalStateException
+                (sm.getString("standardSession.getLastAccessedTime.ise"));
+        }
+
+        return (this.lastAccessedTime);
+    }
+
+    /**
+     * Return the last client access time without invalidation check
+     * @see #getLastAccessedTime()
+     */
+    @Override
+    public long getLastAccessedTimeInternal() {
+        return (this.lastAccessedTime);
+    }
+
+    /**
+     * Return the Manager within which this Session is valid.
+     */
+    @Override
+    public Manager getManager() {
+
+        return (this.manager);
+
+    }
+
+
+    /**
+     * Set the Manager within which this Session is valid.
+     *
+     * @param manager The new Manager
+     */
+    @Override
+    public void setManager(Manager manager) {
+
+        this.manager = manager;
+
+    }
+
+
+    /**
+     * Return the maximum time interval, in seconds, between client requests
+     * before the servlet container will invalidate the session.  A negative
+     * time indicates that the session should never time out.
+     */
+    @Override
+    public int getMaxInactiveInterval() {
+
+        return (this.maxInactiveInterval);
+
+    }
+
+
+    /**
+     * Set the maximum time interval, in seconds, between client requests
+     * before the servlet container will invalidate the session.  A negative
+     * time indicates that the session should never time out.
+     *
+     * @param interval The new maximum interval
+     */
+    @Override
+    public void setMaxInactiveInterval(int interval) {
+
+        this.maxInactiveInterval = interval;
+        if (isValid && interval == 0) {
+            expire();
+        }
+
+    }
+
+
+    /**
+     * Set the <code>isNew</code> flag for this session.
+     *
+     * @param isNew The new value for the <code>isNew</code> flag
+     */
+    @Override
+    public void setNew(boolean isNew) {
+
+        this.isNew = isNew;
+
+    }
+
+
+    /**
+     * Return the authenticated Principal that is associated with this Session.
+     * This provides an <code>Authenticator</code> with a means to cache a
+     * previously authenticated Principal, and avoid potentially expensive
+     * <code>Realm.authenticate()</code> calls on every request.  If there
+     * is no current associated Principal, return <code>null</code>.
+     */
+    @Override
+    public Principal getPrincipal() {
+
+        return (this.principal);
+
+    }
+
+
+    /**
+     * Set the authenticated Principal that is associated with this Session.
+     * This provides an <code>Authenticator</code> with a means to cache a
+     * previously authenticated Principal, and avoid potentially expensive
+     * <code>Realm.authenticate()</code> calls on every request.
+     *
+     * @param principal The new Principal, or <code>null</code> if none
+     */
+    @Override
+    public void setPrincipal(Principal principal) {
+
+        Principal oldPrincipal = this.principal;
+        this.principal = principal;
+        support.firePropertyChange("principal", oldPrincipal, this.principal);
+
+    }
+
+
+    /**
+     * Return the <code>HttpSession</code> for which this object
+     * is the facade.
+     */
+    @Override
+    public HttpSession getSession() {
+
+        if (facade == null){
+            if (SecurityUtil.isPackageProtectionEnabled()){
+                final StandardSession fsession = this;
+                facade = AccessController.doPrivileged(
+                        new PrivilegedAction<StandardSessionFacade>(){
+                    @Override
+                    public StandardSessionFacade run(){
+                        return new StandardSessionFacade(fsession);
+                    }
+                });
+            } else {
+                facade = new StandardSessionFacade(this);
+            }
+        }
+        return (facade);
+
+    }
+
+
+    /**
+     * Return the <code>isValid</code> flag for this session.
+     */
+    @Override
+    public boolean isValid() {
+
+        if (this.expiring) {
+            return true;
+        }
+
+        if (!this.isValid) {
+            return false;
+        }
+
+        if (ACTIVITY_CHECK && accessCount.get() > 0) {
+            return true;
+        }
+
+        if (maxInactiveInterval >= 0) { 
+            long timeNow = System.currentTimeMillis();
+            int timeIdle;
+            if (LAST_ACCESS_AT_START) {
+                timeIdle = (int) ((timeNow - lastAccessedTime) / 1000L);
+            } else {
+                timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L);
+            }
+            if (timeIdle >= maxInactiveInterval) {
+                expire(true);
+            }
+        }
+
+        return (this.isValid);
+    }
+
+
+    /**
+     * Set the <code>isValid</code> flag for this session.
+     *
+     * @param isValid The new value for the <code>isValid</code> flag
+     */
+    @Override
+    public void setValid(boolean isValid) {
+        this.isValid = isValid;
+    }
+
+
+    // ------------------------------------------------- Session Public Methods
+
+
+    /**
+     * Update the accessed time information for this session.  This method
+     * should be called by the context when a request comes in for a particular
+     * session, even if the application does not reference it.
+     */
+    @Override
+    public void access() {
+
+        this.thisAccessedTime = System.currentTimeMillis();
+
+        if (ACTIVITY_CHECK) {
+            accessCount.incrementAndGet();
+        }
+
+    }
+
+
+    /**
+     * End the access.
+     */
+    @Override
+    public void endAccess() {
+
+        isNew = false;
+
+        /**
+         * The servlet spec mandates to ignore request handling time
+         * in lastAccessedTime.
+         */
+        if (LAST_ACCESS_AT_START) {
+            this.lastAccessedTime = this.thisAccessedTime;
+            this.thisAccessedTime = System.currentTimeMillis();
+        } else {
+            this.thisAccessedTime = System.currentTimeMillis();
+            this.lastAccessedTime = this.thisAccessedTime;
+        }
+
+        if (ACTIVITY_CHECK) {
+            accessCount.decrementAndGet();
+        }
+
+    }
+
+
+    /**
+     * Add a session event listener to this component.
+     */
+    @Override
+    public void addSessionListener(SessionListener listener) {
+
+        listeners.add(listener);
+
+    }
+
+
+    /**
+     * Perform the internal processing required to invalidate this session,
+     * without triggering an exception if the session has already expired.
+     */
+    @Override
+    public void expire() {
+
+        expire(true);
+
+    }
+
+
+    /**
+     * Perform the internal processing required to invalidate this session,
+     * without triggering an exception if the session has already expired.
+     *
+     * @param notify Should we notify listeners about the demise of
+     *  this session?
+     */
+    public void expire(boolean notify) {
+
+        // Check to see if expire is in progress or has previously been called
+        if (expiring || !isValid)
+            return;
+
+        synchronized (this) {
+            // Check again, now we are inside the sync so this code only runs once
+            // Double check locking - expiring and isValid need to be volatile
+            if (expiring || !isValid)
+                return;
+
+            if (manager == null)
+                return;
+
+            // Mark this session as "being expired"
+            expiring = true;
+        
+            // Notify interested application event listeners
+            // FIXME - Assumes we call listeners in reverse order
+            Context context = (Context) manager.getContainer();
+            
+            // The call to expire() may not have been triggered by the webapp.
+            // Make sure the webapp's class loader is set when calling the
+            // listeners
+            ClassLoader oldTccl = null;
+            if (context.getLoader() != null &&
+                    context.getLoader().getClassLoader() != null) {
+                oldTccl = Thread.currentThread().getContextClassLoader();
+                if (Globals.IS_SECURITY_ENABLED) {
+                    PrivilegedAction<Void> pa = new PrivilegedSetTccl(
+                            context.getLoader().getClassLoader());
+                    AccessController.doPrivileged(pa);
+                } else {
+                    Thread.currentThread().setContextClassLoader(
+                            context.getLoader().getClassLoader());
+                }
+            }
+            try {
+                Object listeners[] = context.getApplicationLifecycleListeners();
+                if (notify && (listeners != null)) {
+                    HttpSessionEvent event =
+                        new HttpSessionEvent(getSession());
+                    for (int i = 0; i < listeners.length; i++) {
+                        int j = (listeners.length - 1) - i;
+                        if (!(listeners[j] instanceof HttpSessionListener))
+                            continue;
+                        HttpSessionListener listener =
+                            (HttpSessionListener) listeners[j];
+                        try {
+                            fireContainerEvent(context,
+                                               "beforeSessionDestroyed",
+                                               listener);
+                            listener.sessionDestroyed(event);
+                            fireContainerEvent(context,
+                                               "afterSessionDestroyed",
+                                               listener);
+                        } catch (Throwable t) {
+                            ExceptionUtils.handleThrowable(t);
+                            try {
+                                fireContainerEvent(context,
+                                                   "afterSessionDestroyed",
+                                                   listener);
+                            } catch (Exception e) {
+                                // Ignore
+                            }
+                            manager.getContainer().getLogger().error
+                                (sm.getString("standardSession.sessionEvent"), t);
+                        }
+                    }
+                }
+            } finally {
+                if (oldTccl != null) {
+                    if (Globals.IS_SECURITY_ENABLED) {
+                        PrivilegedAction<Void> pa =
+                            new PrivilegedSetTccl(oldTccl);
+                        AccessController.doPrivileged(pa);
+                    } else {
+                        Thread.currentThread().setContextClassLoader(oldTccl);
+                    }
+                }
+            }
+
+            if (ACTIVITY_CHECK) {
+                accessCount.set(0);
+            }
+            setValid(false);
+
+            // Remove this session from our manager's active sessions
+            manager.remove(this, true);
+
+            // Notify interested session event listeners
+            if (notify) {
+                fireSessionEvent(Session.SESSION_DESTROYED_EVENT, null);
+            }
+
+            // Call the logout method
+            if (principal instanceof GenericPrincipal) {
+                GenericPrincipal gp = (GenericPrincipal) principal;
+                try {
+                    gp.logout();
+                } catch (Exception e) {
+                    manager.getContainer().getLogger().error(
+                            sm.getString("standardSession.logoutfail"),
+                            e);
+                }
+            }
+
+            // We have completed expire of this session
+            expiring = false;
+
+            // Unbind any objects associated with this session
+            String keys[] = keys();
+            for (int i = 0; i < keys.length; i++)
+                removeAttributeInternal(keys[i], notify);
+
+        }
+
+    }
+
+
+    /**
+     * Perform the internal processing required to passivate
+     * this session.
+     */
+    public void passivate() {
+
+        // Notify interested session event listeners
+        fireSessionEvent(Session.SESSION_PASSIVATED_EVENT, null);
+
+        // Notify ActivationListeners
+        HttpSessionEvent event = null;
+        String keys[] = keys();
+        for (int i = 0; i < keys.length; i++) {
+            Object attribute = attributes.get(keys[i]);
+            if (attribute instanceof HttpSessionActivationListener) {
+                if (event == null)
+                    event = new HttpSessionEvent(getSession());
+                try {
+                    ((HttpSessionActivationListener)attribute)
+                        .sessionWillPassivate(event);
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    manager.getContainer().getLogger().error
+                        (sm.getString("standardSession.attributeEvent"), t);
+                }
+            }
+        }
+
+    }
+
+
+    /**
+     * Perform internal processing required to activate this
+     * session.
+     */
+    public void activate() {
+
+        // Initialize access count
+        if (ACTIVITY_CHECK) {
+            accessCount = new AtomicInteger();
+        }
+        
+        // Notify interested session event listeners
+        fireSessionEvent(Session.SESSION_ACTIVATED_EVENT, null);
+
+        // Notify ActivationListeners
+        HttpSessionEvent event = null;
+        String keys[] = keys();
+        for (int i = 0; i < keys.length; i++) {
+            Object attribute = attributes.get(keys[i]);
+            if (attribute instanceof HttpSessionActivationListener) {
+                if (event == null)
+                    event = new HttpSessionEvent(getSession());
+                try {
+                    ((HttpSessionActivationListener)attribute)
+                        .sessionDidActivate(event);
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    manager.getContainer().getLogger().error
+                        (sm.getString("standardSession.attributeEvent"), t);
+                }
+            }
+        }
+
+    }
+
+
+    /**
+     * Return the object bound with the specified name to the internal notes
+     * for this session, or <code>null</code> if no such binding exists.
+     *
+     * @param name Name of the note to be returned
+     */
+    @Override
+    public Object getNote(String name) {
+
+        return (notes.get(name));
+
+    }
+
+
+    /**
+     * Return an Iterator containing the String names of all notes bindings
+     * that exist for this session.
+     */
+    @Override
+    public Iterator<String> getNoteNames() {
+
+        return (notes.keySet().iterator());
+
+    }
+
+
+    /**
+     * Release all object references, and initialize instance variables, in
+     * preparation for reuse of this object.
+     */
+    @Override
+    public void recycle() {
+
+        // Reset the instance variables associated with this Session
+        attributes.clear();
+        setAuthType(null);
+        creationTime = 0L;
+        expiring = false;
+        id = null;
+        lastAccessedTime = 0L;
+        maxInactiveInterval = -1;
+        notes.clear();
+        setPrincipal(null);
+        isNew = false;
+        isValid = false;
+        manager = null;
+
+    }
+
+
+    /**
+     * Remove any object bound to the specified name in the internal notes
+     * for this session.
+     *
+     * @param name Name of the note to be removed
+     */
+    @Override
+    public void removeNote(String name) {
+
+        notes.remove(name);
+
+    }
+
+
+    /**
+     * Remove a session event listener from this component.
+     */
+    @Override
+    public void removeSessionListener(SessionListener listener) {
+
+        listeners.remove(listener);
+
+    }
+
+
+    /**
+     * Bind an object to a specified name in the internal notes associated
+     * with this session, replacing any existing binding for this name.
+     *
+     * @param name Name to which the object should be bound
+     * @param value Object to be bound to the specified name
+     */
+    @Override
+    public void setNote(String name, Object value) {
+
+        notes.put(name, value);
+
+    }
+
+
+    /**
+     * Return a string representation of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder();
+        sb.append("StandardSession[");
+        sb.append(id);
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+    // ------------------------------------------------ Session Package Methods
+
+
+    /**
+     * Read a serialized version of the contents of this session object from
+     * the specified object input stream, without requiring that the
+     * StandardSession itself have been serialized.
+     *
+     * @param stream The object input stream to read from
+     *
+     * @exception ClassNotFoundException if an unknown class is specified
+     * @exception IOException if an input/output error occurs
+     */
+    public void readObjectData(ObjectInputStream stream)
+        throws ClassNotFoundException, IOException {
+
+        readObject(stream);
+
+    }
+
+
+    /**
+     * Write a serialized version of the contents of this session object to
+     * the specified object output stream, without requiring that the
+     * StandardSession itself have been serialized.
+     *
+     * @param stream The object output stream to write to
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public void writeObjectData(ObjectOutputStream stream)
+        throws IOException {
+
+        writeObject(stream);
+
+    }
+
+
+    // ------------------------------------------------- HttpSession Properties
+
+
+    /**
+     * Return the time when this session was created, in milliseconds since
+     * midnight, January 1, 1970 GMT.
+     *
+     * @exception IllegalStateException if this method is called on an
+     *  invalidated session
+     */
+    @Override
+    public long getCreationTime() {
+
+        if (!isValidInternal())
+            throw new IllegalStateException
+                (sm.getString("standardSession.getCreationTime.ise"));
+
+        return (this.creationTime);
+
+    }
+
+
+    /**
+     * Return the time when this session was created, in milliseconds since
+     * midnight, January 1, 1970 GMT, bypassing the session validation checks.
+     */
+    @Override
+    public long getCreationTimeInternal() {
+        return this.creationTime;
+    }
+
+
+    /**
+     * Return the ServletContext to which this session belongs.
+     */
+    @Override
+    public ServletContext getServletContext() {
+
+        if (manager == null)
+            return (null);
+        Context context = (Context) manager.getContainer();
+        if (context == null)
+            return (null);
+        else
+            return (context.getServletContext());
+
+    }
+
+
+    /**
+     * Return the session context with which this session is associated.
+     *
+     * @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.
+     */
+    @Override
+    @Deprecated
+    public javax.servlet.http.HttpSessionContext getSessionContext() {
+
+        if (sessionContext == null)
+            sessionContext = new StandardSessionContext();
+        return (sessionContext);
+
+    }
+
+
+    // ----------------------------------------------HttpSession Public Methods
+
+
+    /**
+     * Return the object bound with the specified name in this session, or
+     * <code>null</code> if no object is bound with that name.
+     *
+     * @param name Name of the attribute to be returned
+     *
+     * @exception IllegalStateException if this method is called on an
+     *  invalidated session
+     */
+    @Override
+    public Object getAttribute(String name) {
+
+        if (!isValidInternal())
+            throw new IllegalStateException
+                (sm.getString("standardSession.getAttribute.ise"));
+
+        if (name == null) return null;
+
+        return (attributes.get(name));
+
+    }
+
+
+    /**
+     * Return an <code>Enumeration</code> of <code>String</code> objects
+     * containing the names of the objects bound to this session.
+     *
+     * @exception IllegalStateException if this method is called on an
+     *  invalidated session
+     */
+    @Override
+    public Enumeration<String> getAttributeNames() {
+
+        if (!isValidInternal())
+            throw new IllegalStateException
+                (sm.getString("standardSession.getAttributeNames.ise"));
+
+        return (new Enumerator<String>(attributes.keySet(), true));
+
+    }
+
+
+    /**
+     * Return the object bound with the specified name in this session, or
+     * <code>null</code> if no object is bound with that name.
+     *
+     * @param name Name of the value to be returned
+     *
+     * @exception IllegalStateException if this method is called on an
+     *  invalidated session
+     *
+     * @deprecated As of Version 2.2, this method is replaced by
+     *  <code>getAttribute()</code>
+     */
+    @Override
+    @Deprecated
+    public Object getValue(String name) {
+
+        return (getAttribute(name));
+
+    }
+
+
+    /**
+     * Return the set of names of objects bound to this session.  If there
+     * are no such objects, a zero-length array is returned.
+     *
+     * @exception IllegalStateException if this method is called on an
+     *  invalidated session
+     *
+     * @deprecated As of Version 2.2, this method is replaced by
+     *  <code>getAttributeNames()</code>
+     */
+    @Override
+    @Deprecated
+    public String[] getValueNames() {
+
+        if (!isValidInternal())
+            throw new IllegalStateException
+                (sm.getString("standardSession.getValueNames.ise"));
+
+        return (keys());
+
+    }
+
+
+    /**
+     * Invalidates this session and unbinds any objects bound to it.
+     *
+     * @exception IllegalStateException if this method is called on
+     *  an invalidated session
+     */
+    @Override
+    public void invalidate() {
+
+        if (!isValidInternal())
+            throw new IllegalStateException
+                (sm.getString("standardSession.invalidate.ise"));
+
+        // Cause this session to expire
+        expire();
+
+    }
+
+
+    /**
+     * Return <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
+     * has disabled the use of cookies, then a session would be new on each
+     * request.
+     *
+     * @exception IllegalStateException if this method is called on an
+     *  invalidated session
+     */
+    @Override
+    public boolean isNew() {
+
+        if (!isValidInternal())
+            throw new IllegalStateException
+                (sm.getString("standardSession.isNew.ise"));
+
+        return (this.isNew);
+
+    }
+
+
+    /**
+     * Bind an object to this session, using the specified name.  If an object
+     * of the same name is already bound to this session, the object is
+     * replaced.
+     * <p>
+     * After this method executes, and if the object implements
+     * <code>HttpSessionBindingListener</code>, the container calls
+     * <code>valueBound()</code> on the object.
+     *
+     * @param name Name to which the object is bound, cannot be null
+     * @param value 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
+     *  <code>setAttribute()</code>
+     */
+    @Override
+    @Deprecated
+    public void putValue(String name, Object value) {
+
+        setAttribute(name, value);
+
+    }
+
+
+    /**
+     * Remove the object bound with the specified name from this session.  If
+     * the session does not have an object bound with this name, this method
+     * does nothing.
+     * <p>
+     * After this method executes, and if the object implements
+     * <code>HttpSessionBindingListener</code>, the container calls
+     * <code>valueUnbound()</code> on the object.
+     *
+     * @param name Name of the object to remove from this session.
+     *
+     * @exception IllegalStateException if this method is called on an
+     *  invalidated session
+     */
+    @Override
+    public void removeAttribute(String name) {
+
+        removeAttribute(name, true);
+
+    }
+
+
+    /**
+     * Remove the object bound with the specified name from this session.  If
+     * the session does not have an object bound with this name, this method
+     * does nothing.
+     * <p>
+     * After this method executes, and if the object implements
+     * <code>HttpSessionBindingListener</code>, the container calls
+     * <code>valueUnbound()</code> on the object.
+     *
+     * @param name Name of the object to remove from this session.
+     * @param notify Should we notify interested listeners that this
+     *  attribute is being removed?
+     *
+     * @exception IllegalStateException if this method is called on an
+     *  invalidated session
+     */
+    public void removeAttribute(String name, boolean notify) {
+
+        // Validate our current state
+        if (!isValidInternal())
+            throw new IllegalStateException
+                (sm.getString("standardSession.removeAttribute.ise"));
+
+        removeAttributeInternal(name, notify);
+
+    }
+
+
+    /**
+     * Remove the object bound with the specified name from this session.  If
+     * the session does not have an object bound with this name, this method
+     * does nothing.
+     * <p>
+     * After this method executes, and if the object implements
+     * <code>HttpSessionBindingListener</code>, the container calls
+     * <code>valueUnbound()</code> on the object.
+     *
+     * @param name 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
+     *  <code>removeAttribute()</code>
+     */
+    @Override
+    @Deprecated
+    public void removeValue(String name) {
+
+        removeAttribute(name);
+
+    }
+
+
+    /**
+     * Bind an object to this session, using the specified name.  If an object
+     * of the same name is already bound to this session, the object is
+     * replaced.
+     * <p>
+     * After this method executes, and if the object implements
+     * <code>HttpSessionBindingListener</code>, the container calls
+     * <code>valueBound()</code> on the object.
+     *
+     * @param name Name to which the object is bound, cannot be null
+     * @param value Object to be bound, cannot be null
+     *
+     * @exception IllegalArgumentException if an attempt is made to add a
+     *  non-serializable object in an environment marked distributable.
+     * @exception IllegalStateException if this method is called on an
+     *  invalidated session
+     */
+    @Override
+    public void setAttribute(String name, Object value) {
+        setAttribute(name,value,true);
+    }
+    /**
+     * Bind an object to this session, using the specified name.  If an object
+     * of the same name is already bound to this session, the object is
+     * replaced.
+     * <p>
+     * After this method executes, and if the object implements
+     * <code>HttpSessionBindingListener</code>, the container calls
+     * <code>valueBound()</code> on the object.
+     *
+     * @param name Name to which the object is bound, cannot be null
+     * @param value Object to be bound, cannot be null
+     * @param notify whether to notify session listeners
+     * @exception IllegalArgumentException if an attempt is made to add a
+     *  non-serializable object in an environment marked distributable.
+     * @exception IllegalStateException if this method is called on an
+     *  invalidated session
+     */
+
+    public void setAttribute(String name, Object value, boolean notify) {
+
+        // Name cannot be null
+        if (name == null)
+            throw new IllegalArgumentException
+                (sm.getString("standardSession.setAttribute.namenull"));
+
+        // Null value is the same as removeAttribute()
+        if (value == null) {
+            removeAttribute(name);
+            return;
+        }
+
+        // Validate our current state
+        if (!isValidInternal())
+            throw new IllegalStateException(sm.getString(
+                    "standardSession.setAttribute.ise", getIdInternal()));
+        if ((manager != null) && manager.getDistributable() &&
+          !(value instanceof Serializable))
+            throw new IllegalArgumentException
+                (sm.getString("standardSession.setAttribute.iae", name));
+        // Construct an event with the new value
+        HttpSessionBindingEvent event = null;
+
+        // Call the valueBound() method if necessary
+        if (notify && value instanceof HttpSessionBindingListener) {
+            // Don't call any notification if replacing with the same value
+            Object oldValue = attributes.get(name);
+            if (value != oldValue) {
+                event = new HttpSessionBindingEvent(getSession(), name, value);
+                try {
+                    ((HttpSessionBindingListener) value).valueBound(event);
+                } catch (Throwable t){
+                    manager.getContainer().getLogger().error
+                    (sm.getString("standardSession.bindingEvent"), t); 
+                }
+            }
+        }
+
+        // Replace or add this attribute
+        Object unbound = attributes.put(name, value);
+
+        // Call the valueUnbound() method if necessary
+        if (notify && (unbound != null) && (unbound != value) &&
+            (unbound instanceof HttpSessionBindingListener)) {
+            try {
+                ((HttpSessionBindingListener) unbound).valueUnbound
+                    (new HttpSessionBindingEvent(getSession(), name));
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                manager.getContainer().getLogger().error
+                    (sm.getString("standardSession.bindingEvent"), t);
+            }
+        }
+        
+        if ( !notify ) return;
+        
+        // Notify interested application event listeners
+        Context context = (Context) manager.getContainer();
+        Object listeners[] = context.getApplicationEventListeners();
+        if (listeners == null)
+            return;
+        for (int i = 0; i < listeners.length; i++) {
+            if (!(listeners[i] instanceof HttpSessionAttributeListener))
+                continue;
+            HttpSessionAttributeListener listener =
+                (HttpSessionAttributeListener) listeners[i];
+            try {
+                if (unbound != null) {
+                    fireContainerEvent(context,
+                                       "beforeSessionAttributeReplaced",
+                                       listener);
+                    if (event == null) {
+                        event = new HttpSessionBindingEvent
+                            (getSession(), name, unbound);
+                    }
+                    listener.attributeReplaced(event);
+                    fireContainerEvent(context,
+                                       "afterSessionAttributeReplaced",
+                                       listener);
+                } else {
+                    fireContainerEvent(context,
+                                       "beforeSessionAttributeAdded",
+                                       listener);
+                    if (event == null) {
+                        event = new HttpSessionBindingEvent
+                            (getSession(), name, value);
+                    }
+                    listener.attributeAdded(event);
+                    fireContainerEvent(context,
+                                       "afterSessionAttributeAdded",
+                                       listener);
+                }
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                try {
+                    if (unbound != null) {
+                        fireContainerEvent(context,
+                                           "afterSessionAttributeReplaced",
+                                           listener);
+                    } else {
+                        fireContainerEvent(context,
+                                           "afterSessionAttributeAdded",
+                                           listener);
+                    }
+                } catch (Exception e) {
+                    // Ignore
+                }
+                manager.getContainer().getLogger().error
+                    (sm.getString("standardSession.attributeEvent"), t);
+            }
+        }
+
+    }
+
+
+    // ------------------------------------------ HttpSession Protected Methods
+
+
+    /**
+     * Return the <code>isValid</code> flag for this session without any expiration
+     * check.
+     */
+    protected boolean isValidInternal() {
+        return (this.isValid || this.expiring);
+    }
+
+
+    /**
+     * Read a serialized version of this session object from the specified
+     * object input stream.
+     * <p>
+     * <b>IMPLEMENTATION NOTE</b>:  The reference to the owning Manager
+     * is not restored by this method, and must be set explicitly.
+     *
+     * @param stream The input stream to read from
+     *
+     * @exception ClassNotFoundException if an unknown class is specified
+     * @exception IOException if an input/output error occurs
+     */
+    protected void readObject(ObjectInputStream stream)
+        throws ClassNotFoundException, IOException {
+
+        // Deserialize the scalar instance variables (except Manager)
+        authType = null;        // Transient only
+        creationTime = ((Long) stream.readObject()).longValue();
+        lastAccessedTime = ((Long) stream.readObject()).longValue();
+        maxInactiveInterval = ((Integer) stream.readObject()).intValue();
+        isNew = ((Boolean) stream.readObject()).booleanValue();
+        isValid = ((Boolean) stream.readObject()).booleanValue();
+        thisAccessedTime = ((Long) stream.readObject()).longValue();
+        principal = null;        // Transient only
+        //        setId((String) stream.readObject());
+        id = (String) stream.readObject();
+        if (manager.getContainer().getLogger().isDebugEnabled())
+            manager.getContainer().getLogger().debug
+                ("readObject() loading session " + id);
+
+        // Deserialize the attribute count and attribute values
+        if (attributes == null)
+            attributes = new ConcurrentHashMap<String, Object>();
+        int n = ((Integer) stream.readObject()).intValue();
+        boolean isValidSave = isValid;
+        isValid = true;
+        for (int i = 0; i < n; i++) {
+            String name = (String) stream.readObject();
+            Object value = stream.readObject();
+            if ((value instanceof String) && (value.equals(NOT_SERIALIZED)))
+                continue;
+            if (manager.getContainer().getLogger().isDebugEnabled())
+                manager.getContainer().getLogger().debug("  loading attribute '" + name +
+                    "' with value '" + value + "'");
+            attributes.put(name, value);
+        }
+        isValid = isValidSave;
+
+        if (listeners == null) {
+            listeners = new ArrayList<SessionListener>();
+        }
+
+        if (notes == null) {
+            notes = new Hashtable<String, Object>();
+        }
+    }
+
+
+    /**
+     * Write a serialized version of this session object to the specified
+     * object output stream.
+     * <p>
+     * <b>IMPLEMENTATION NOTE</b>:  The owning Manager will not be stored
+     * in the serialized representation of this Session.  After calling
+     * <code>readObject()</code>, you must set the associated Manager
+     * explicitly.
+     * <p>
+     * <b>IMPLEMENTATION NOTE</b>:  Any attribute that is not Serializable
+     * will be unbound from the session, with appropriate actions if it
+     * implements HttpSessionBindingListener.  If you do not want any such
+     * attributes, be sure the <code>distributable</code> property of the
+     * associated Manager is set to <code>true</code>.
+     *
+     * @param stream The output stream to write to
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    protected void writeObject(ObjectOutputStream stream) throws IOException {
+
+        // Write the scalar instance variables (except Manager)
+        stream.writeObject(Long.valueOf(creationTime));
+        stream.writeObject(Long.valueOf(lastAccessedTime));
+        stream.writeObject(Integer.valueOf(maxInactiveInterval));
+        stream.writeObject(Boolean.valueOf(isNew));
+        stream.writeObject(Boolean.valueOf(isValid));
+        stream.writeObject(Long.valueOf(thisAccessedTime));
+        stream.writeObject(id);
+        if (manager.getContainer().getLogger().isDebugEnabled())
+            manager.getContainer().getLogger().debug
+                ("writeObject() storing session " + id);
+
+        // Accumulate the names of serializable and non-serializable attributes
+        String keys[] = keys();
+        ArrayList<String> saveNames = new ArrayList<String>();
+        ArrayList<Object> saveValues = new ArrayList<Object>();
+        for (int i = 0; i < keys.length; i++) {
+            Object value = attributes.get(keys[i]);
+            if (value == null)
+                continue;
+            else if ( (value instanceof Serializable) 
+                    && (!exclude(keys[i]) )) {
+                saveNames.add(keys[i]);
+                saveValues.add(value);
+            } else {
+                removeAttributeInternal(keys[i], true);
+            }
+        }
+
+        // Serialize the attribute count and the Serializable attributes
+        int n = saveNames.size();
+        stream.writeObject(Integer.valueOf(n));
+        for (int i = 0; i < n; i++) {
+            stream.writeObject(saveNames.get(i));
+            try {
+                stream.writeObject(saveValues.get(i));
+                if (manager.getContainer().getLogger().isDebugEnabled())
+                    manager.getContainer().getLogger().debug
+                        ("  storing attribute '" + saveNames.get(i) +
+                        "' with value '" + saveValues.get(i) + "'");
+            } catch (NotSerializableException e) {
+                manager.getContainer().getLogger().warn
+                    (sm.getString("standardSession.notSerializable",
+                     saveNames.get(i), id), e);
+                stream.writeObject(NOT_SERIALIZED);
+                if (manager.getContainer().getLogger().isDebugEnabled())
+                    manager.getContainer().getLogger().debug
+                       ("  storing attribute '" + saveNames.get(i) +
+                        "' with value NOT_SERIALIZED");
+            }
+        }
+
+    }
+
+
+    /**
+     * Exclude attribute that cannot be serialized.
+     * @param name the attribute's name
+     */
+    protected boolean exclude(String name){
+
+        for (int i = 0; i < excludedAttributes.length; i++) {
+            if (name.equalsIgnoreCase(excludedAttributes[i]))
+                return true;
+        }
+
+        return false;
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Fire container events if the Context implementation is the
+     * <code>org.apache.catalina.core.StandardContext</code>.
+     *
+     * @param context Context for which to fire events
+     * @param type Event type
+     * @param data Event data
+     *
+     * @exception Exception occurred during event firing
+     */
+    protected void fireContainerEvent(Context context,
+                                    String type, Object data)
+        throws Exception {
+
+        if (context instanceof StandardContext) {
+            ((StandardContext) context).fireContainerEvent(type, data);
+        }
+    }
+
+
+    /**
+     * Notify all session event listeners that a particular event has
+     * occurred for this Session.  The default implementation performs
+     * this notification synchronously using the calling thread.
+     *
+     * @param type Event type
+     * @param data Event data
+     */
+    public void fireSessionEvent(String type, Object data) {
+        if (listeners.size() < 1)
+            return;
+        SessionEvent event = new SessionEvent(this, type, data);
+        SessionListener list[] = new SessionListener[0];
+        synchronized (listeners) {
+            list = listeners.toArray(list);
+        }
+
+        for (int i = 0; i < list.length; i++){
+            (list[i]).sessionEvent(event);
+        }
+
+    }
+
+
+    /**
+     * Return the names of all currently defined session attributes
+     * as an array of Strings.  If there are no defined attributes, a
+     * zero-length array is returned.
+     */
+    protected String[] keys() {
+
+        return attributes.keySet().toArray(EMPTY_ARRAY);
+
+    }
+
+
+    /**
+     * Remove the object bound with the specified name from this session.  If
+     * the session does not have an object bound with this name, this method
+     * does nothing.
+     * <p>
+     * After this method executes, and if the object implements
+     * <code>HttpSessionBindingListener</code>, the container calls
+     * <code>valueUnbound()</code> on the object.
+     *
+     * @param name Name of the object to remove from this session.
+     * @param notify Should we notify interested listeners that this
+     *  attribute is being removed?
+     */
+    protected void removeAttributeInternal(String name, boolean notify) {
+
+        // Avoid NPE
+        if (name == null) return;
+
+        // Remove this attribute from our collection
+        Object value = attributes.remove(name);
+
+        // Do we need to do valueUnbound() and attributeRemoved() notification?
+        if (!notify || (value == null)) {
+            return;
+        }
+
+        // Call the valueUnbound() method if necessary
+        HttpSessionBindingEvent event = null;
+        if (value instanceof HttpSessionBindingListener) {
+            event = new HttpSessionBindingEvent(getSession(), name, value);
+            ((HttpSessionBindingListener) value).valueUnbound(event);
+        }
+
+        // Notify interested application event listeners
+        Context context = (Context) manager.getContainer();
+        Object listeners[] = context.getApplicationEventListeners();
+        if (listeners == null)
+            return;
+        for (int i = 0; i < listeners.length; i++) {
+            if (!(listeners[i] instanceof HttpSessionAttributeListener))
+                continue;
+            HttpSessionAttributeListener listener =
+                (HttpSessionAttributeListener) listeners[i];
+            try {
+                fireContainerEvent(context,
+                                   "beforeSessionAttributeRemoved",
+                                   listener);
+                if (event == null) {
+                    event = new HttpSessionBindingEvent
+                        (getSession(), name, value);
+                }
+                listener.attributeRemoved(event);
+                fireContainerEvent(context,
+                                   "afterSessionAttributeRemoved",
+                                   listener);
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                try {
+                    fireContainerEvent(context,
+                                       "afterSessionAttributeRemoved",
+                                       listener);
+                } catch (Exception e) {
+                    // Ignore
+                }
+                manager.getContainer().getLogger().error
+                    (sm.getString("standardSession.attributeEvent"), t);
+            }
+        }
+
+    }
+
+
+    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;
+        }
+    }
+
+}
+
+
+// ------------------------------------------------------------ Protected Class
+
+
+/**
+ * This class is a dummy implementation of the <code>HttpSessionContext</code>
+ * interface, to conform to the requirement that such an object be returned
+ * when <code>HttpSession.getSessionContext()</code> is called.
+ *
+ * @author Craig R. McClanahan
+ *
+ * @deprecated As of Java Servlet API 2.1 with no replacement.  The
+ *  interface will be removed in a future version of this API.
+ */
+
+@Deprecated
+final class StandardSessionContext
+        implements javax.servlet.http.HttpSessionContext {
+
+
+    protected HashMap<?,String> dummy = new HashMap<String,String>();
+
+    /**
+     * Return the session identifiers of all sessions defined
+     * within this context.
+     *
+     * @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 the API.
+     */
+    @Override
+    @Deprecated
+    public Enumeration<String> getIds() {
+
+        return (new Enumerator<String>(dummy));
+
+    }
+
+
+    /**
+     * Return the <code>HttpSession</code> associated with the
+     * specified session identifier.
+     *
+     * @param id Session identifier for which to look up a session
+     *
+     * @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 the API.
+     */
+    @Override
+    @Deprecated
+    public HttpSession getSession(String id) {
+
+        return (null);
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/session/StandardSessionFacade.java b/bundles/org.apache.tomcat/src/org/apache/catalina/session/StandardSessionFacade.java
new file mode 100644
index 0000000..f1ccfc1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/session/StandardSessionFacade.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.session;
+
+
+import java.util.Enumeration;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionContext;
+
+
+/**
+ * Facade for the StandardSession object.
+ *
+ * @author Remy Maucherat
+ * @version $Id: StandardSessionFacade.java,v 1.1 2011/06/28 21:08:19 rherrmann Exp $
+ */
+
+public class StandardSessionFacade
+    implements HttpSession {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new session facade.
+     */
+    public StandardSessionFacade(StandardSession session) {
+        super();
+        this.session = session;
+    }
+
+
+    /**
+     * Construct a new session facade.
+     */
+    public StandardSessionFacade(HttpSession session) {
+        super();
+        this.session = session;
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Wrapped session object.
+     */
+    private HttpSession session = null;
+
+
+    // ---------------------------------------------------- HttpSession Methods
+
+
+    @Override
+    public long getCreationTime() {
+        return session.getCreationTime();
+    }
+
+
+    @Override
+    public String getId() {
+        return session.getId();
+    }
+
+
+    @Override
+    public long getLastAccessedTime() {
+        return session.getLastAccessedTime();
+    }
+
+
+    @Override
+    public ServletContext getServletContext() {
+        // FIXME : Facade this object ?
+        return session.getServletContext();
+    }
+
+
+    @Override
+    public void setMaxInactiveInterval(int interval) {
+        session.setMaxInactiveInterval(interval);
+    }
+
+
+    @Override
+    public int getMaxInactiveInterval() {
+        return session.getMaxInactiveInterval();
+    }
+
+
+    /**
+     * @deprecated
+     */
+    @Override
+    @Deprecated
+    public HttpSessionContext getSessionContext() {
+        return session.getSessionContext();
+    }
+
+
+    @Override
+    public Object getAttribute(String name) {
+        return session.getAttribute(name);
+    }
+
+
+    /**
+     * @deprecated
+     */
+    @Override
+    @Deprecated
+    public Object getValue(String name) {
+        return session.getAttribute(name);
+    }
+
+
+    @Override
+    public Enumeration<String> getAttributeNames() {
+        return session.getAttributeNames();
+    }
+
+
+    /**
+     * @deprecated
+     */
+    @Override
+    @Deprecated
+    public String[] getValueNames() {
+        return session.getValueNames();
+    }
+
+
+    @Override
+    public void setAttribute(String name, Object value) {
+        session.setAttribute(name, value);
+    }
+
+
+    /**
+     * @deprecated
+     */
+    @Override
+    @Deprecated
+    public void putValue(String name, Object value) {
+        session.setAttribute(name, value);
+    }
+
+
+    @Override
+    public void removeAttribute(String name) {
+        session.removeAttribute(name);
+    }
+
+
+    /**
+     * @deprecated
+     */
+    @Override
+    @Deprecated
+    public void removeValue(String name) {
+        session.removeAttribute(name);
+    }
+
+
+    @Override
+    public void invalidate() {
+        session.invalidate();
+    }
+
+
+    @Override
+    public boolean isNew() {
+        return session.isNew();
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/session/StoreBase.java b/bundles/org.apache.tomcat/src/org/apache/catalina/session/StoreBase.java
new file mode 100644
index 0000000..097206d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/session/StoreBase.java
@@ -0,0 +1,253 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.session;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.IOException;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.Manager;
+import org.apache.catalina.Store;
+import org.apache.catalina.util.LifecycleBase;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Abstract implementation of the Store interface to
+ * support most of the functionality required by a Store.
+ *
+ * @author Bip Thelin
+ * @version $Id: StoreBase.java,v 1.1 2011/06/28 21:08:18 rherrmann Exp $
+ */
+
+public abstract class StoreBase extends LifecycleBase implements Store {
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * The descriptive information about this implementation.
+     */
+    protected static final String info = "StoreBase/1.0";
+
+    /**
+     * Name to register for this Store, used for logging.
+     */
+    protected static String storeName = "StoreBase";
+
+    /**
+     * The property change support for this component.
+     */
+    protected PropertyChangeSupport support = new PropertyChangeSupport(this);
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm = StringManager.getManager(Constants.Package);
+
+    /**
+     * The Manager with which this JDBCStore is associated.
+     */
+    protected Manager manager;
+
+    // ------------------------------------------------------------- Properties
+
+    /**
+     * Return the info for this Store.
+     */
+    @Override
+    public String getInfo() {
+        return(info);
+    }
+
+
+    /**
+     * Return the name for this Store, used for logging.
+     */
+    public String getStoreName() {
+        return(storeName);
+    }
+
+
+    /**
+     * Set the Manager with which this Store is associated.
+     *
+     * @param manager The newly associated Manager
+     */
+    @Override
+    public void setManager(Manager manager) {
+        Manager oldManager = this.manager;
+        this.manager = manager;
+        support.firePropertyChange("manager", oldManager, this.manager);
+    }
+
+    /**
+     * Return the Manager with which the Store is associated.
+     */
+    @Override
+    public Manager getManager() {
+        return(this.manager);
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Add a property change listener to this component.
+     *
+     * @param listener a value of type 'PropertyChangeListener'
+     */
+    @Override
+    public void addPropertyChangeListener(PropertyChangeListener listener) {
+        support.addPropertyChangeListener(listener);
+    }
+
+    /**
+     * Remove a property change listener from this component.
+     *
+     * @param listener The listener to remove
+     */
+    @Override
+    public void removePropertyChangeListener(PropertyChangeListener listener) {
+        support.removePropertyChangeListener(listener);
+    }
+
+    // --------------------------------------------------------- Protected Methods
+
+    /**
+     * Called by our background reaper thread to check if Sessions
+     * saved in our store are subject of being expired. If so expire
+     * the Session and remove it from the Store.
+     *
+     */
+    public void processExpires() {
+        String[] keys = null;
+
+        if(!getState().isAvailable()) {
+            return;
+        }
+
+        try {
+            keys = keys();
+        } catch (IOException e) {
+            manager.getContainer().getLogger().error("Error getting keys", e);
+            return;
+        }
+        if (manager.getContainer().getLogger().isDebugEnabled()) {
+            manager.getContainer().getLogger().debug(getStoreName()+ ": processExpires check number of " + keys.length + " sessions" );
+        }
+
+        long timeNow = System.currentTimeMillis();
+
+        for (int i = 0; i < keys.length; i++) {
+            try {
+                StandardSession session = (StandardSession) load(keys[i]);
+                if (session == null) {
+                    continue;
+                }
+                int timeIdle = (int) ((timeNow - session.getThisAccessedTime()) / 1000L);
+                if (timeIdle < session.getMaxInactiveInterval()) {
+                    continue;
+                }
+                if (manager.getContainer().getLogger().isDebugEnabled()) {
+                    manager.getContainer().getLogger().debug(getStoreName()+ ": processExpires expire store session " + keys[i] );
+                }
+                boolean isLoaded = false;
+                try {
+                    if (manager.findSession(keys[i]) != null) {
+                        isLoaded = true;
+                    }
+                } catch (IOException ioe) {
+                    // Ignore - session will be expired
+                }
+                if (isLoaded) {
+                    // recycle old backup session
+                    session.recycle();
+                } else {
+                    // expire swapped out session
+                    session.expire();
+                }
+                remove(keys[i]);
+            } catch (Exception e) {
+                manager.getContainer().getLogger().error("Session: "+keys[i]+"; ", e);
+                try {
+                    remove(keys[i]);
+                } catch (IOException e2) {
+                    manager.getContainer().getLogger().error("Error removing key", e2);
+                }
+            }
+        }
+    }
+
+
+    @Override
+    protected void initInternal() {
+        // NOOP
+    }
+    
+    
+    /**
+     * Start this component and implement the requirements
+     * of {@link 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 {
+        
+        setState(LifecycleState.STARTING);
+    }
+
+
+    /**
+     * Stop this component and implement the requirements
+     * of {@link 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 {
+
+        setState(LifecycleState.STOPPING);
+    }
+    
+    
+    @Override
+    protected void destroyInternal() {
+        // NOOP
+    }
+    
+    
+    /**
+     * Return a String rendering of this object.
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(this.getClass().getName());
+        sb.append('[');
+        if (manager == null) {
+            sb.append("Manager is null");
+        } else {
+            sb.append(manager);
+        }
+        sb.append(']');
+        return sb.toString();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/session/mbeans-descriptors.xml b/bundles/org.apache.tomcat/src/org/apache/catalina/session/mbeans-descriptors.xml
new file mode 100644
index 0000000..5c055be
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/session/mbeans-descriptors.xml
@@ -0,0 +1,413 @@
+<?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="StandardManager"
+          description="Standard implementation of the Manager interface"
+               domain="Catalina"
+                group="Manager"
+                 type="org.apache.catalina.session.StandardManager">
+                 
+    <attribute   name="activeSessions"
+          description="Number of active sessions at this moment"
+                 type="int" 
+            writeable="false"/>
+
+    <attribute   name="className"
+          description="Fully qualified class name of the managed object"
+                 type="java.lang.String"
+            writeable="false"/>
+
+    <attribute   name="distributable"
+          description="The distributable flag for Sessions created by this
+                       Manager"
+                 type="boolean"/>
+                 
+    <attribute   name="duplicates"
+          description="Number of duplicated session ids generated"
+                 type="int" />
+
+    <attribute   name="expiredSessions"
+          description="Number of sessions that expired ( doesn't include explicit invalidations )"
+                 type="long" />
+                 
+    <attribute   name="jvmRoute"
+          description="Retrieve the JvmRoute for the enclosing Engine"
+                 type="java.lang.String"
+           writeable = "false" />
+                 
+    <attribute   name="maxActive"
+          description="Maximum number of active sessions so far"
+                 type="int" />
+
+    <attribute   name="maxActiveSessions"
+          description="The maximum number of active Sessions allowed, or -1
+                       for no limit"
+                 type="int"/>
+
+    <attribute   name="maxInactiveInterval"
+          description="The default maximum inactive interval for Sessions
+                       created by this Manager"
+                 type="int"/>
+
+    <attribute   name="name"
+          description="The descriptive name of this Manager implementation
+                       (for logging)"
+                 type="java.lang.String"
+            writeable="false"/>
+            
+    <attribute   name="pathname"
+          description="Path name of the disk file in which active sessions"
+                 type="java.lang.String"/>
+
+    <attribute   name="processExpiresFrequency"
+          description="The frequency of the manager checks (expiration and passivation)"
+                 type="int"/>
+
+    <attribute   name="processingTime"
+          description="Time spent doing housekeeping and expiration"
+                 type="long" />
+               
+    <attribute   name="secureRandomAlgorithm"
+          description="The secure random number generator algorithm name"
+                 type="java.lang.String"/>
+
+    <attribute   name="secureRandomClass"
+          description="The secure random number generator class name"
+                 type="java.lang.String"/>
+
+    <attribute   name="secureRandomProvider"
+          description="The secure random number generator provider name"
+                 type="java.lang.String"/>
+
+    <attribute   name="sessionAverageAliveTime"
+          description="Average time an expired session had been alive"
+                 type="int"
+            writeable="false" />
+
+    <attribute   name="sessionCreateRate"
+          description="Session creation rate in sessions per minute"
+                 type="int"
+            writeable="false" />
+
+    <attribute   name="sessionCounter"
+          description="Total number of sessions created by this manager"
+                 type="long" />
+                 
+    <attribute   name="sessionExpireRate"
+          description="Session expiration rate in sessions per minute"
+                 type="int"
+            writeable="false" />
+                 
+    <attribute   name="sessionIdLength"
+          description="The session id length (in bytes) of Sessions
+                       created by this Manager"
+                 type="int"/>
+
+    <attribute   name="sessionMaxAliveTime"
+          description="Longest time an expired session had been alive"
+                 type="int" />
+                 
+    <attribute   name="stateName"
+          description="The name of the LifecycleState that this component is currently in"
+                 type="java.lang.String"
+            writeable="false"/>
+
+    <attribute   name="randomFile"
+          description="File source of random - /dev/urandom or a pipe that will be used when the Manager is next started"
+                 type="java.lang.String"/>
+                 
+    <attribute   name="randomFileCurrent"
+          description="File source of random - /dev/urandom or a pipe currently being used"
+                 type="java.lang.String"
+            writeable="false"/>
+                 
+    <attribute   name="rejectedSessions"
+          description="Number of sessions we rejected due to maxActive beeing reached"
+                 type="int"
+            writeable="false"/>
+                 
+    <operation   name="backgroundProcess"
+          description="Invalidate all sessions that have expired."
+               impact="ACTION"
+           returnType="void">
+    </operation>
+                 
+    <operation   name="expireSession"
+          description="Expire a session"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="sessionId"
+          description="Id of the session"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation   name="getCreationTime"
+          description="Get the creation time"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="sessionId"
+          description="Id of the session"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation   name="getCreationTimestamp"
+          description="Get the creation timestamp"
+               impact="ACTION"
+           returnType="long">
+      <parameter name="sessionId"
+          description="Id of the session"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation   name="getLastAccessedTime"
+          description="Get the last access time"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="sessionId"
+          description="Id of the session"
+                 type="java.lang.String"/>
+    </operation>
+
+   <operation   name="getLastAccessedTimestamp"
+          description="Get the last access timestamp"
+               impact="ACTION"
+           returnType="long">
+      <parameter name="sessionId"
+          description="Id of the session"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation   name="getSessionAttribute"
+          description="Return a session attribute"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="sessionId"
+          description="Id of the session"
+                 type="java.lang.String"/>
+      <parameter name="key"
+          description="key of the attribute"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="listSessionIds"
+          description="Return the list of active session ids"
+               impact="ACTION"
+           returnType="java.lang.String">
+    </operation>
+
+  </mbean>
+
+  <mbean         name="PersistentManager"
+          description="Persistent Manager"
+               domain="Catalina"
+                group="Manager"
+                 type="org.apache.catalina.session.PersistentManager">
+
+    <attribute   name="activeSessions"
+          description="Number of active sessions at this moment"
+                 type="int" 
+            writeable="false"/>
+
+    <attribute   name="className"
+          description="Fully qualified class name of the managed object"
+                 type="java.lang.String"
+            writeable="false"/>
+
+    <attribute   name="distributable"
+          description="The distributable flag for Sessions created by this Manager"
+                 type="boolean"/>
+                 
+    <attribute   name="duplicates"
+          description="Number of duplicated session ids generated"
+                 type="int" />
+
+    <attribute   name="expiredSessions"
+          description="Number of sessions that expired ( doesn't include explicit invalidations )"
+                 type="long" />
+                 
+    <attribute   name="loaded"
+          description="If the session id is loaded in memory?"
+                 type="boolean"
+           writeable = "false" />
+                 
+    <attribute   name="jvmRoute"
+          description="Retrieve the JvmRoute for the enclosing Engine"
+                 type="java.lang.String"
+           writeable = "false" />
+                 
+    <attribute   name="maxActive"
+          description="Maximum number of active sessions so far"
+                 type="int" />
+
+    <attribute   name="maxActiveSessions"
+          description="The maximum number of active Sessions allowed, or -1
+                       for no limit"
+                 type="int"/>
+                 
+    <attribute   name="maxIdleBackup"
+          description="Indicates how many seconds old a session can get, after its last use in a request, before it should be backed up to the store. -1 means sessions are not backed up."
+                 type="int"/>
+                 
+    <attribute   name="maxIdleSwap"
+          description="Indicates how many seconds old a session can get, after its last use in a request, before it should be backed up to the store. -1 means sessions are not backed up."
+                 type="int"/>
+
+    <attribute   name="maxInactiveInterval"
+          description="The default maximum inactive interval for Sessions created by this Manager"
+                 type="int"/>
+                 
+    <attribute   name="minIdleSwap"
+          description=" The minimum time in seconds that a session must be idle before it can be swapped out of memory, or -1 if it can be swapped out at any time."
+                 type="int"/>
+
+    <attribute   name="name"
+          description="The descriptive name of this Manager implementation (for logging)"
+                 type="java.lang.String"
+            writeable="false"/>
+
+    <attribute   name="processExpiresFrequency"
+          description="The frequency of the manager checks (expiration and passivation)"
+                 type="int"/>
+
+    <attribute   name="processingTime"
+          description="Time spent doing housekeeping and expiration"
+                 type="long" />
+                 
+    <attribute   name="saveOnRestart"
+          description="Indicates whether sessions are saved when the Manager is shut down properly. This requires the unload() method to be called."
+                 type="boolean" />
+               
+    <attribute   name="secureRandomClass"
+          description="The random number generator class name"
+                 type="java.lang.String"/>
+
+    <attribute   name="sessionAverageAliveTime"
+          description="Average time an expired session had been alive"
+                 type="int"
+            writeable="false" />
+
+    <attribute   name="sessionCreateRate"
+          description="Session creation rate in sessions per minute"
+                 type="int"
+            writeable="false" />
+
+    <attribute   name="sessionCounter"
+          description="Total number of sessions created by this manager"
+                 type="long" />
+                 
+    <attribute   name="sessionExpireRate"
+          description="Session expiration rate in sessions per minute"
+                 type="int"
+            writeable="false" />
+
+    <attribute   name="sessionIdLength"
+          description="The session id length (in bytes) of Sessions
+                       created by this Manager"
+                 type="int"/>
+
+    <attribute   name="sessionMaxAliveTime"
+          description="Longest time an expired session had been alive"
+                 type="int" />
+                 
+    <attribute   name="stateName"
+          description="The name of the LifecycleState that this component is currently in"
+                 type="java.lang.String"
+            writeable="false"/>
+
+    <attribute   name="randomFile"
+          description="File source of random - /dev/urandom or a pipe"
+                 type="java.lang.String"/>
+                 
+    <attribute   name="rejectedSessions"
+          description="Number of sessions we rejected due to maxActive beeing reached"
+                 type="int"
+            writeable="false"/>
+
+    <operation   name="backgroundProcess"
+          description="Invalidate all sessions that have expired."
+               impact="ACTION"
+           returnType="void">
+    </operation>
+                 
+    <operation   name="expireSession"
+          description="Expire a session"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="sessionId"
+          description="Id of the session"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation   name="getCreationTime"
+          description="Get the creation time"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="sessionId"
+          description="Id of the session"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation   name="getCreationTimestamp"
+          description="Get the creation timestamp"
+               impact="ACTION"
+           returnType="long">
+      <parameter name="sessionId"
+          description="Id of the session"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation   name="getLastAccessedTime"
+          description="Get the last access time"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="sessionId"
+          description="Id of the session"
+                 type="java.lang.String"/>
+    </operation>
+
+   <operation   name="getLastAccessedTimestamp"
+          description="Get the last access timestamp"
+               impact="ACTION"
+           returnType="long">
+      <parameter name="sessionId"
+          description="Id of the session"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation   name="getSessionAttribute"
+          description="Return a session attribute"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="sessionId"
+          description="Id of the session"
+                 type="java.lang.String"/>
+      <parameter name="key"
+          description="key of the attribute"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="listSessionIds"
+          description="Return the list of active session ids"
+               impact="ACTION"
+           returnType="java.lang.String">
+    </operation>
+
+  </mbean>
+
+</mbeans-descriptors>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/session/package.html b/bundles/org.apache.tomcat/src/org/apache/catalina/session/package.html
new file mode 100644
index 0000000..a08cca2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/session/package.html
@@ -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.
+-->
+<body>
+
+<p>This package contains the standard <code>Manager</code> and
+<code>Session</code> implementations that represent the collection of
+active sessions and the individual sessions themselves, respectively,
+that are associated with a <code>Context</code>.  Additional implementations
+of the <code>Manager</code> interface can be based upon the supplied
+convenience base class (<code>ManagerBase</code>), if desired.  Different
+implementations of <code>Session</code> are possible, but a need for
+functionality beyond what is provided by the standard implementation
+(<code>StandardSession</code>) is not expected.</p>
+
+<p>The convenience <code>ManagerBase</code> base class is configured by
+setting the following properties:</p>
+<ul>
+<li><b>algorithm</b> - Message digest algorithm to be used when
+    generating session identifiers.  This must be the name of an
+    algorithm supported by the <code>java.security.MessageDigest</code>
+    class on your platform.  [DEFAULT_ALGORITHM]</li>
+<li><b>debug</b> - Debugging detail level for this component. [0]</li>
+<li><b>distributable</b> - Has the web application we are associated with
+    been marked as "distributable"?  If it has, attempts to add or replace
+    a session attribute object that does not implement the
+    <code>java.io.Serializable</code> interface will be rejected.
+    [false]</li>
+<li><b>entropy</b> - A string initialization parameter that is used to
+    increase the entropy of the seeding of the random number generator
+    used in creation of session identifiers.  [NONE]</li>
+<li><b>maxInactiveInterval</b> - The default maximum inactive interval,
+    in minutes, for sessions created by this Manager.  The standard
+    implementation automatically updates this value based on the configuration
+    settings in the web application deployment descriptor.  [60]</li>
+<li><b>randomClass</b> - The Java class name of the random number generator
+    to be used when creating session identifiers for this Manager.
+    [java.security.SecureRandom]</li>
+</ul>
+
+<p>The standard implementation of the <code>Manager</code> interface
+(<code>StandardManager</code>) supports the following additional configuration
+properties:</p>
+<ul>
+<li><b>checkInterval</b> - The interval, in seconds, between checks for
+    sessions that have expired and should be invalidated.  [60]</li>
+<li><b>maxActiveSessions</b> - The maximum number of active sessions that
+    will be allowed, or -1 for no limit.  [-1]</li>
+<li><b>pathname</b> - Pathname to the file that is used to store session
+    data persistently across container restarts.  If this pathname is relative,
+    it is resolved against the temporary working directory provided by our
+    associated Context, if any.  ["sessions.ser"]</li>
+</ul>
+
+</body>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/Authenticators.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/Authenticators.properties
new file mode 100644
index 0000000..a995844
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/Authenticators.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.
+
+BASIC=org.apache.catalina.authenticator.BasicAuthenticator
+CLIENT-CERT=org.apache.catalina.authenticator.SSLAuthenticator
+DIGEST=org.apache.catalina.authenticator.DigestAuthenticator
+FORM=org.apache.catalina.authenticator.FormAuthenticator
+NONE=org.apache.catalina.authenticator.NonLoginAuthenticator
+SPNEGO=org.apache.catalina.authenticator.SpnegoAuthenticator
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/Bootstrap.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/Bootstrap.java
new file mode 100644
index 0000000..16275c9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/Bootstrap.java
@@ -0,0 +1,539 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.startup;
+
+
+import java.io.File;
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.ObjectName;
+
+import org.apache.catalina.Globals;
+import org.apache.catalina.security.SecurityClassLoad;
+import org.apache.catalina.startup.ClassLoaderFactory.Repository;
+import org.apache.catalina.startup.ClassLoaderFactory.RepositoryType;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+
+/**
+ * Bootstrap loader for Catalina.  This application constructs a class loader
+ * for use in loading the Catalina internal classes (by accumulating all of the
+ * JAR files found in the "server" directory under "catalina.home"), and
+ * starts the regular execution of the container.  The purpose of this
+ * roundabout approach is to keep the Catalina internal classes (and any
+ * other classes they depend on, such as an XML parser) out of the system
+ * class path and therefore not visible to application level classes.
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @version $Id: Bootstrap.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public final class Bootstrap {
+
+    private static final Log log = LogFactory.getLog(Bootstrap.class);
+
+    // -------------------------------------------------------------- Constants
+
+
+    protected static final String CATALINA_HOME_TOKEN = "${" + Globals.CATALINA_HOME_PROP + "}";
+    protected static final String CATALINA_BASE_TOKEN = "${" + Globals.CATALINA_BASE_PROP + "}";
+
+
+    // ------------------------------------------------------- Static Variables
+
+
+    /**
+     * Daemon object used by main.
+     */
+    private static Bootstrap daemon = null;
+
+
+    // -------------------------------------------------------------- Variables
+
+
+    /**
+     * Daemon reference.
+     */
+    private Object catalinaDaemon = null;
+
+
+    protected ClassLoader commonLoader = null;
+    protected ClassLoader catalinaLoader = null;
+    protected ClassLoader sharedLoader = null;
+
+
+    // -------------------------------------------------------- Private Methods
+
+
+    private void initClassLoaders() {
+        try {
+            commonLoader = createClassLoader("common", null);
+            if( commonLoader == null ) {
+                // no config file, default to this loader - we might be in a 'single' env.
+                commonLoader=this.getClass().getClassLoader();
+            }
+            catalinaLoader = createClassLoader("server", commonLoader);
+            sharedLoader = createClassLoader("shared", commonLoader);
+        } catch (Throwable t) {
+            handleThrowable(t);
+            log.error("Class loader creation threw exception", t);
+            System.exit(1);
+        }
+    }
+
+
+    private ClassLoader createClassLoader(String name, ClassLoader parent)
+        throws Exception {
+
+        String value = CatalinaProperties.getProperty(name + ".loader");
+        if ((value == null) || (value.equals("")))
+            return parent;
+
+        List<Repository> repositories = new ArrayList<Repository>();
+        int i;
+
+        StringTokenizer tokenizer = new StringTokenizer(value, ",");
+        while (tokenizer.hasMoreElements()) {
+            String repository = tokenizer.nextToken();
+
+            // Local repository
+            boolean replace = false;
+            String before = repository;
+            while ((i=repository.indexOf(CATALINA_HOME_TOKEN))>=0) {
+                replace=true;
+                if (i>0) {
+                repository = repository.substring(0,i) + getCatalinaHome()
+                    + repository.substring(i+CATALINA_HOME_TOKEN.length());
+                } else {
+                    repository = getCatalinaHome()
+                        + repository.substring(CATALINA_HOME_TOKEN.length());
+                }
+            }
+            while ((i=repository.indexOf(CATALINA_BASE_TOKEN))>=0) {
+                replace=true;
+                if (i>0) {
+                repository = repository.substring(0,i) + getCatalinaBase()
+                    + repository.substring(i+CATALINA_BASE_TOKEN.length());
+                } else {
+                    repository = getCatalinaBase()
+                        + repository.substring(CATALINA_BASE_TOKEN.length());
+                }
+            }
+            if (replace && log.isDebugEnabled())
+                log.debug("Expanded " + before + " to " + repository);
+
+            // Check for a JAR URL repository
+            try {
+                new URL(repository);
+                repositories.add(
+                        new Repository(repository, RepositoryType.URL));
+                continue;
+            } catch (MalformedURLException e) {
+                // Ignore
+            }
+
+            if (repository.endsWith("*.jar")) {
+                repository = repository.substring
+                    (0, repository.length() - "*.jar".length());
+                repositories.add(
+                        new Repository(repository, RepositoryType.GLOB));
+            } else if (repository.endsWith(".jar")) {
+                repositories.add(
+                        new Repository(repository, RepositoryType.JAR));
+            } else {
+                repositories.add(
+                        new Repository(repository, RepositoryType.DIR));
+            }
+        }
+
+        ClassLoader classLoader = ClassLoaderFactory.createClassLoader
+            (repositories, parent);
+
+        // Retrieving MBean server
+        MBeanServer mBeanServer = null;
+        if (MBeanServerFactory.findMBeanServer(null).size() > 0) {
+            mBeanServer = MBeanServerFactory.findMBeanServer(null).get(0);
+        } else {
+            mBeanServer = ManagementFactory.getPlatformMBeanServer();
+        }
+
+        // Register the server classloader
+        ObjectName objectName =
+            new ObjectName("Catalina:type=ServerClassLoader,name=" + name);
+        mBeanServer.registerMBean(classLoader, objectName);
+
+        return classLoader;
+
+    }
+
+
+    /**
+     * Initialize daemon.
+     */
+    public void init()
+        throws Exception
+    {
+
+        // Set Catalina path
+        setCatalinaHome();
+        setCatalinaBase();
+
+        initClassLoaders();
+
+        Thread.currentThread().setContextClassLoader(catalinaLoader);
+
+        SecurityClassLoad.securityClassLoad(catalinaLoader);
+
+        // Load our startup class and call its process() method
+        if (log.isDebugEnabled())
+            log.debug("Loading startup class");
+        Class<?> startupClass =
+            catalinaLoader.loadClass
+            ("org.apache.catalina.startup.Catalina");
+        Object startupInstance = startupClass.newInstance();
+
+        // Set the shared extensions class loader
+        if (log.isDebugEnabled())
+            log.debug("Setting startup class properties");
+        String methodName = "setParentClassLoader";
+        Class<?> paramTypes[] = new Class[1];
+        paramTypes[0] = Class.forName("java.lang.ClassLoader");
+        Object paramValues[] = new Object[1];
+        paramValues[0] = sharedLoader;
+        Method method =
+            startupInstance.getClass().getMethod(methodName, paramTypes);
+        method.invoke(startupInstance, paramValues);
+
+        catalinaDaemon = startupInstance;
+
+    }
+
+
+    /**
+     * Load daemon.
+     */
+    private void load(String[] arguments)
+        throws Exception {
+
+        // Call the load() method
+        String methodName = "load";
+        Object param[];
+        Class<?> paramTypes[];
+        if (arguments==null || arguments.length==0) {
+            paramTypes = null;
+            param = null;
+        } else {
+            paramTypes = new Class[1];
+            paramTypes[0] = arguments.getClass();
+            param = new Object[1];
+            param[0] = arguments;
+        }
+        Method method =
+            catalinaDaemon.getClass().getMethod(methodName, paramTypes);
+        if (log.isDebugEnabled())
+            log.debug("Calling startup class " + method);
+        method.invoke(catalinaDaemon, param);
+
+    }
+
+
+    /**
+     * getServer() for configtest
+     */
+    private Object getServer() throws Exception {
+
+        String methodName = "getServer";
+        Method method =
+            catalinaDaemon.getClass().getMethod(methodName);
+        return method.invoke(catalinaDaemon);
+
+    }
+
+
+    // ----------------------------------------------------------- Main Program
+
+
+    /**
+     * Load the Catalina daemon.
+     */
+    public void init(String[] arguments)
+        throws Exception {
+
+        init();
+        load(arguments);
+
+    }
+
+
+    /**
+     * Start the Catalina daemon.
+     */
+    public void start()
+        throws Exception {
+        if( catalinaDaemon==null ) init();
+
+        Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
+        method.invoke(catalinaDaemon, (Object [])null);
+
+    }
+
+
+    /**
+     * Stop the Catalina Daemon.
+     */
+    public void stop()
+        throws Exception {
+
+        Method method = catalinaDaemon.getClass().getMethod("stop", (Class [] ) null);
+        method.invoke(catalinaDaemon, (Object [] ) null);
+
+    }
+
+
+    /**
+     * Stop the standalone server.
+     */
+    public void stopServer()
+        throws Exception {
+
+        Method method =
+            catalinaDaemon.getClass().getMethod("stopServer", (Class []) null);
+        method.invoke(catalinaDaemon, (Object []) null);
+
+    }
+
+
+   /**
+     * Stop the standalone server.
+     */
+    public void stopServer(String[] arguments)
+        throws Exception {
+
+        Object param[];
+        Class<?> paramTypes[];
+        if (arguments==null || arguments.length==0) {
+            paramTypes = null;
+            param = null;
+        } else {
+            paramTypes = new Class[1];
+            paramTypes[0] = arguments.getClass();
+            param = new Object[1];
+            param[0] = arguments;
+        }
+        Method method =
+            catalinaDaemon.getClass().getMethod("stopServer", paramTypes);
+        method.invoke(catalinaDaemon, param);
+
+    }
+
+
+    /**
+     * Set flag.
+     */
+    public void setAwait(boolean await)
+        throws Exception {
+
+        Class<?> paramTypes[] = new Class[1];
+        paramTypes[0] = Boolean.TYPE;
+        Object paramValues[] = new Object[1];
+        paramValues[0] = Boolean.valueOf(await);
+        Method method =
+            catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
+        method.invoke(catalinaDaemon, paramValues);
+
+    }
+
+    public boolean getAwait()
+        throws Exception
+    {
+        Class<?> paramTypes[] = new Class[0];
+        Object paramValues[] = new Object[0];
+        Method method =
+            catalinaDaemon.getClass().getMethod("getAwait", paramTypes);
+        Boolean b=(Boolean)method.invoke(catalinaDaemon, paramValues);
+        return b.booleanValue();
+    }
+
+
+    /**
+     * Destroy the Catalina Daemon.
+     */
+    public void destroy() {
+
+        // FIXME
+
+    }
+
+
+    /**
+     * Main method, used for testing only.
+     *
+     * @param args Command line arguments to be processed
+     */
+    public static void main(String args[]) {
+
+        if (daemon == null) {
+            // Don't set daemon until init() has completed
+            Bootstrap bootstrap = new Bootstrap();
+            try {
+                bootstrap.init();
+            } catch (Throwable t) {
+                handleThrowable(t);
+                t.printStackTrace();
+                return;
+            }
+            daemon = bootstrap;
+        }
+
+        try {
+            String command = "start";
+            if (args.length > 0) {
+                command = args[args.length - 1];
+            }
+
+            if (command.equals("startd")) {
+                args[args.length - 1] = "start";
+                daemon.load(args);
+                daemon.start();
+            } else if (command.equals("stopd")) {
+                args[args.length - 1] = "stop";
+                daemon.stop();
+            } else if (command.equals("start")) {
+                daemon.setAwait(true);
+                daemon.load(args);
+                daemon.start();
+            } else if (command.equals("stop")) {
+                daemon.stopServer(args);
+            } else if (command.equals("configtest")) {
+                daemon.load(args);
+                if (null==daemon.getServer()) {
+                    System.exit(1);
+                }
+                System.exit(0);
+            } else {
+                log.warn("Bootstrap: command \"" + command + "\" does not exist.");
+            }
+        } catch (Throwable t) {
+            handleThrowable(t);
+            // Unwrap the Exception for clearer error reporting
+            if (t instanceof InvocationTargetException &&
+                    t.getCause() != null) {
+                t = t.getCause();
+            }
+            t.printStackTrace();
+            System.exit(1);
+        }
+
+    }
+
+    public void setCatalinaHome(String s) {
+        System.setProperty(Globals.CATALINA_HOME_PROP, s);
+    }
+
+    public void setCatalinaBase(String s) {
+        System.setProperty(Globals.CATALINA_BASE_PROP, s);
+    }
+
+
+    /**
+     * Set the <code>catalina.base</code> System property to the current
+     * working directory if it has not been set.
+     */
+    private void setCatalinaBase() {
+
+        if (System.getProperty(Globals.CATALINA_BASE_PROP) != null)
+            return;
+        if (System.getProperty(Globals.CATALINA_HOME_PROP) != null)
+            System.setProperty(Globals.CATALINA_BASE_PROP,
+                               System.getProperty(Globals.CATALINA_HOME_PROP));
+        else
+            System.setProperty(Globals.CATALINA_BASE_PROP,
+                               System.getProperty("user.dir"));
+
+    }
+
+
+    /**
+     * Set the <code>catalina.home</code> System property to the current
+     * working directory if it has not been set.
+     */
+    private void setCatalinaHome() {
+
+        if (System.getProperty(Globals.CATALINA_HOME_PROP) != null)
+            return;
+        File bootstrapJar =
+            new File(System.getProperty("user.dir"), "bootstrap.jar");
+        if (bootstrapJar.exists()) {
+            try {
+                System.setProperty
+                    (Globals.CATALINA_HOME_PROP,
+                     (new File(System.getProperty("user.dir"), ".."))
+                     .getCanonicalPath());
+            } catch (Exception e) {
+                // Ignore
+                System.setProperty(Globals.CATALINA_HOME_PROP,
+                                   System.getProperty("user.dir"));
+            }
+        } else {
+            System.setProperty(Globals.CATALINA_HOME_PROP,
+                               System.getProperty("user.dir"));
+        }
+
+    }
+
+
+    /**
+     * Get the value of the catalina.home environment variable.
+     */
+    public static String getCatalinaHome() {
+        return System.getProperty(Globals.CATALINA_HOME_PROP,
+                                  System.getProperty("user.dir"));
+    }
+
+
+    /**
+     * Get the value of the catalina.base environment variable.
+     */
+    public static String getCatalinaBase() {
+        return System.getProperty(Globals.CATALINA_BASE_PROP, getCatalinaHome());
+    }
+
+
+    // Copied from ExceptionUtils since that class is not visible during start
+    private 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/catalina/startup/Catalina.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/Catalina.java
new file mode 100644
index 0000000..5271590
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/Catalina.java
@@ -0,0 +1,899 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.startup;
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.logging.LogManager;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Globals;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.Server;
+import org.apache.catalina.core.StandardServer;
+import org.apache.catalina.security.SecurityConfig;
+import org.apache.juli.ClassLoaderLogManager;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.IntrospectionUtils;
+import org.apache.tomcat.util.digester.Digester;
+import org.apache.tomcat.util.digester.Rule;
+import org.apache.tomcat.util.log.SystemLogHandler;
+import org.apache.tomcat.util.res.StringManager;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXParseException;
+
+
+/**
+ * Startup/Shutdown shell program for Catalina.  The following command line
+ * options are recognized:
+ * <ul>
+ * <li><b>-config {pathname}</b> - Set the pathname of the configuration file
+ *     to be processed.  If a relative path is specified, it will be
+ *     interpreted as relative to the directory pathname specified by the
+ *     "catalina.base" system property.   [conf/server.xml]
+ * <li><b>-help</b>      - Display usage information.
+ * <li><b>-nonaming</b>  - Disable naming support.
+ * <li><b>configtest</b> - Try to test the config
+ * <li><b>start</b>      - Start an instance of Catalina.
+ * <li><b>stop</b>       - Stop the currently running instance of Catalina.
+ * </u>
+ *
+ * Should do the same thing as Embedded, but using a server.xml file.
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @version $Id: Catalina.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public class Catalina {
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * Use await.
+     */
+    protected boolean await = false;
+
+    /**
+     * Pathname to the server configuration file.
+     */
+    protected String configFile = "conf/server.xml";
+
+    // XXX Should be moved to embedded
+    /**
+     * The shared extensions class loader for this server.
+     */
+    protected ClassLoader parentClassLoader =
+        Catalina.class.getClassLoader();
+
+
+    /**
+     * The server component we are starting or stopping.
+     */
+    protected Server server = null;
+
+
+    /**
+     * Are we starting a new server?
+     */
+    protected boolean starting = false;
+
+
+    /**
+     * Are we stopping an existing server?
+     */
+    protected boolean stopping = false;
+
+
+    /**
+     * Use shutdown hook flag.
+     */
+    protected boolean useShutdownHook = true;
+
+
+    /**
+     * Shutdown hook.
+     */
+    protected Thread shutdownHook = null;
+
+
+    /**
+     * Is naming enabled ?
+     */
+    protected boolean useNaming = true;
+
+
+    // ----------------------------------------------------------- Constructors
+
+    public Catalina() {
+        setSecurityProtection();
+    }
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    public void setConfig(String file) {
+        configFile = file;
+    }
+
+
+    public void setConfigFile(String file) {
+        configFile = file;
+    }
+
+
+    public String getConfigFile() {
+        return configFile;
+    }
+
+
+    public void setUseShutdownHook(boolean useShutdownHook) {
+        this.useShutdownHook = useShutdownHook;
+    }
+
+
+    public boolean getUseShutdownHook() {
+        return useShutdownHook;
+    }
+
+
+    /**
+     * Set the shared extensions class loader.
+     *
+     * @param parentClassLoader The shared extensions class loader.
+     */
+    public void setParentClassLoader(ClassLoader parentClassLoader) {
+        this.parentClassLoader = parentClassLoader;
+    }
+
+    public ClassLoader getParentClassLoader() {
+        return parentClassLoader;
+    }
+
+    public void setServer(Server server) {
+        this.server = server;
+    }
+
+
+    public Server getServer() {
+        return server;
+    }
+
+
+    /**
+     * Return true if naming is enabled.
+     */
+    public boolean isUseNaming() {
+        return (this.useNaming);
+    }
+
+
+    /**
+     * Enables or disables naming support.
+     *
+     * @param useNaming The new use naming value
+     */
+    public void setUseNaming(boolean useNaming) {
+        this.useNaming = useNaming;
+    }
+
+    public void setAwait(boolean b) {
+        await = b;
+    }
+
+    public boolean isAwait() {
+        return await;
+    }
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Process the specified command line arguments, and return
+     * <code>true</code> if we should continue processing; otherwise
+     * return <code>false</code>.
+     *
+     * @param args Command line arguments to process
+     */
+    protected boolean arguments(String args[]) {
+
+        boolean isConfig = false;
+
+        if (args.length < 1) {
+            usage();
+            return (false);
+        }
+
+        for (int i = 0; i < args.length; i++) {
+            if (isConfig) {
+                configFile = args[i];
+                isConfig = false;
+            } else if (args[i].equals("-config")) {
+                isConfig = true;
+            } else if (args[i].equals("-nonaming")) {
+                setUseNaming( false );
+            } else if (args[i].equals("-help")) {
+                usage();
+                return (false);
+            } else if (args[i].equals("start")) {
+                starting = true;
+                stopping = false;
+            } else if (args[i].equals("configtest")) {
+                starting = true;
+                stopping = false;
+            } else if (args[i].equals("stop")) {
+                starting = false;
+                stopping = true;
+            } else {
+                usage();
+                return (false);
+            }
+        }
+
+        return (true);
+
+    }
+
+
+    /**
+     * Return a File object representing our configuration file.
+     */
+    protected File configFile() {
+
+        File file = new File(configFile);
+        if (!file.isAbsolute())
+            file = new File(System.getProperty(Globals.CATALINA_BASE_PROP), configFile);
+        return (file);
+
+    }
+
+
+    /**
+     * Create and configure the Digester we will be using for startup.
+     */
+    protected Digester createStartDigester() {
+        long t1=System.currentTimeMillis();
+        // Initialize the digester
+        Digester digester = new Digester();
+        digester.setValidating(false);
+        digester.setRulesValidation(true);
+        HashMap<Class<?>, List<String>> fakeAttributes =
+            new HashMap<Class<?>, List<String>>();
+        ArrayList<String> attrs = new ArrayList<String>();
+        attrs.add("className");
+        fakeAttributes.put(Object.class, attrs);
+        digester.setFakeAttributes(fakeAttributes);
+        digester.setClassLoader(StandardServer.class.getClassLoader());
+
+        // Configure the actions we will be using
+        digester.addObjectCreate("Server",
+                                 "org.apache.catalina.core.StandardServer",
+                                 "className");
+        digester.addSetProperties("Server");
+        digester.addSetNext("Server",
+                            "setServer",
+                            "org.apache.catalina.Server");
+
+        digester.addObjectCreate("Server/GlobalNamingResources",
+                                 "org.apache.catalina.deploy.NamingResources");
+        digester.addSetProperties("Server/GlobalNamingResources");
+        digester.addSetNext("Server/GlobalNamingResources",
+                            "setGlobalNamingResources",
+                            "org.apache.catalina.deploy.NamingResources");
+
+        digester.addObjectCreate("Server/Listener",
+                                 null, // MUST be specified in the element
+                                 "className");
+        digester.addSetProperties("Server/Listener");
+        digester.addSetNext("Server/Listener",
+                            "addLifecycleListener",
+                            "org.apache.catalina.LifecycleListener");
+
+        digester.addObjectCreate("Server/Service",
+                                 "org.apache.catalina.core.StandardService",
+                                 "className");
+        digester.addSetProperties("Server/Service");
+        digester.addSetNext("Server/Service",
+                            "addService",
+                            "org.apache.catalina.Service");
+
+        digester.addObjectCreate("Server/Service/Listener",
+                                 null, // MUST be specified in the element
+                                 "className");
+        digester.addSetProperties("Server/Service/Listener");
+        digester.addSetNext("Server/Service/Listener",
+                            "addLifecycleListener",
+                            "org.apache.catalina.LifecycleListener");
+
+        //Executor
+        digester.addObjectCreate("Server/Service/Executor",
+                         "org.apache.catalina.core.StandardThreadExecutor",
+                         "className");
+        digester.addSetProperties("Server/Service/Executor");
+
+        digester.addSetNext("Server/Service/Executor",
+                            "addExecutor",
+                            "org.apache.catalina.Executor");
+
+
+        digester.addRule("Server/Service/Connector",
+                         new ConnectorCreateRule());
+        digester.addRule("Server/Service/Connector",
+                         new SetAllPropertiesRule(new String[]{"executor"}));
+        digester.addSetNext("Server/Service/Connector",
+                            "addConnector",
+                            "org.apache.catalina.connector.Connector");
+
+
+        digester.addObjectCreate("Server/Service/Connector/Listener",
+                                 null, // MUST be specified in the element
+                                 "className");
+        digester.addSetProperties("Server/Service/Connector/Listener");
+        digester.addSetNext("Server/Service/Connector/Listener",
+                            "addLifecycleListener",
+                            "org.apache.catalina.LifecycleListener");
+
+        // Add RuleSets for nested elements
+        digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));
+        digester.addRuleSet(new EngineRuleSet("Server/Service/"));
+        digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
+        digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
+        digester.addRuleSet(ClusterRuleSetFactory.getClusterRuleSet("Server/Service/Engine/Host/Cluster/"));
+        digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));
+
+        // When the 'engine' is found, set the parentClassLoader.
+        digester.addRule("Server/Service/Engine",
+                         new SetParentClassLoaderRule(parentClassLoader));
+        digester.addRuleSet(ClusterRuleSetFactory.getClusterRuleSet("Server/Service/Engine/Cluster/"));
+
+        long t2=System.currentTimeMillis();
+        if (log.isDebugEnabled())
+            log.debug("Digester for server.xml created " + ( t2-t1 ));
+        return (digester);
+
+    }
+
+
+    /**
+     * Create and configure the Digester we will be using for shutdown.
+     */
+    protected Digester createStopDigester() {
+
+        // Initialize the digester
+        Digester digester = new Digester();
+
+        // Configure the rules we need for shutting down
+        digester.addObjectCreate("Server",
+                                 "org.apache.catalina.core.StandardServer",
+                                 "className");
+        digester.addSetProperties("Server");
+        digester.addSetNext("Server",
+                            "setServer",
+                            "org.apache.catalina.Server");
+
+        return (digester);
+
+    }
+
+
+    public void stopServer() {
+        stopServer(null);
+    }
+
+    public void stopServer(String[] arguments) {
+
+        if (arguments != null) {
+            arguments(arguments);
+        }
+
+        Server s = getServer();
+        if( s == null ) {
+            // Create and execute our Digester
+            Digester digester = createStopDigester();
+            digester.setClassLoader(Thread.currentThread().getContextClassLoader());
+            File file = configFile();
+            try {
+                InputSource is =
+                    new InputSource("file://" + file.getAbsolutePath());
+                FileInputStream fis = new FileInputStream(file);
+                is.setByteStream(fis);
+                digester.push(this);
+                digester.parse(is);
+                fis.close();
+            } catch (Exception e) {
+                log.error("Catalina.stop: ", e);
+                System.exit(1);
+            }
+        } else {
+            // Server object already present. Must be running as a service
+            try {
+                s.stop();
+            } catch (LifecycleException e) {
+                log.error("Catalina.stop: ", e);
+            }
+            return;
+        }
+
+        // Stop the existing server
+        s = getServer();
+        try {
+            if (s.getPort()>0) {
+                Socket socket = new Socket(s.getAddress(), s.getPort());
+                OutputStream stream = socket.getOutputStream();
+                String shutdown = s.getShutdown();
+                for (int i = 0; i < shutdown.length(); i++)
+                    stream.write(shutdown.charAt(i));
+                stream.flush();
+                stream.close();
+                socket.close();
+            } else {
+                log.error(sm.getString("catalina.stopServer"));
+                System.exit(1);
+            }
+        } catch (IOException e) {
+            log.error("Catalina.stop: ", e);
+            System.exit(1);
+        }
+
+    }
+
+
+    /**
+     * Start a new server instance.
+     */
+    public void load() {
+
+        long t1 = System.nanoTime();
+
+        initDirs();
+
+        // Before digester - it may be needed
+
+        initNaming();
+
+        // Create and execute our Digester
+        Digester digester = createStartDigester();
+
+        InputSource inputSource = null;
+        InputStream inputStream = null;
+        File file = null;
+        try {
+            file = configFile();
+            inputStream = new FileInputStream(file);
+            inputSource = new InputSource("file://" + file.getAbsolutePath());
+        } catch (Exception e) {
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString("catalina.configFail", file), e);
+            }
+        }
+        if (inputStream == null) {
+            try {
+                inputStream = getClass().getClassLoader()
+                    .getResourceAsStream(getConfigFile());
+                inputSource = new InputSource
+                    (getClass().getClassLoader()
+                     .getResource(getConfigFile()).toString());
+            } catch (Exception e) {
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString("catalina.configFail",
+                            getConfigFile()), e);
+                }
+            }
+        }
+
+        // This should be included in catalina.jar
+        // Alternative: don't bother with xml, just create it manually.
+        if( inputStream==null ) {
+            try {
+                inputStream = getClass().getClassLoader()
+                        .getResourceAsStream("server-embed.xml");
+                inputSource = new InputSource
+                (getClass().getClassLoader()
+                        .getResource("server-embed.xml").toString());
+            } catch (Exception e) {
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString("catalina.configFail",
+                            "server-embed.xml"), e);
+                }
+            }
+        }
+
+
+        if (inputStream == null || inputSource == null) {
+            if  (file == null) {
+                log.warn(sm.getString("catalina.configFail",
+                        getConfigFile() + "] or [server-embed.xml]"));
+            } else {
+                log.warn(sm.getString("catalina.configFail",
+                        file.getAbsolutePath()));
+                if (file.exists() && !file.canRead()) {
+                    log.warn("Permissions incorrect, read permission is not allowed on the file.");
+                }
+            }
+            return;
+        }
+
+        try {
+            inputSource.setByteStream(inputStream);
+            digester.push(this);
+            digester.parse(inputSource);
+            inputStream.close();
+        } catch (SAXParseException spe) {
+            log.warn("Catalina.start using " + getConfigFile() + ": " +
+                    spe.getMessage());
+            return;
+        } catch (Exception e) {
+            log.warn("Catalina.start using " + getConfigFile() + ": " , e);
+            return;
+        }
+
+        getServer().setCatalina(this);
+
+        // Stream redirection
+        initStreams();
+
+        // Start the new server
+        try {
+            getServer().init();
+        } catch (LifecycleException e) {
+            if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
+                throw new java.lang.Error(e);
+            else
+                log.error("Catalina.start", e);
+
+        }
+
+        long t2 = System.nanoTime();
+        if(log.isInfoEnabled())
+            log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
+
+    }
+
+
+    /*
+     * Load using arguments
+     */
+    public void load(String args[]) {
+
+        try {
+            if (arguments(args))
+                load();
+        } catch (Exception e) {
+            e.printStackTrace(System.out);
+        }
+    }
+
+
+    /**
+     * Start a new server instance.
+     */
+    public void start() {
+
+        if (getServer() == null) {
+            load();
+        }
+
+        if (getServer() == null) {
+            log.fatal("Cannot start server. Server instance is not configured.");
+            return;
+        }
+
+        long t1 = System.nanoTime();
+
+        // Start the new server
+        try {
+            getServer().start();
+        } catch (LifecycleException e) {
+            log.error("Catalina.start: ", e);
+        }
+
+        long t2 = System.nanoTime();
+        if(log.isInfoEnabled())
+            log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
+
+        try {
+            // Register shutdown hook
+            if (useShutdownHook) {
+                if (shutdownHook == null) {
+                    shutdownHook = new CatalinaShutdownHook();
+                }
+                Runtime.getRuntime().addShutdownHook(shutdownHook);
+
+                // If JULI is being used, disable JULI's shutdown hook since
+                // shutdown hooks run in parallel and log messages may be lost
+                // if JULI's hook completes before the CatalinaShutdownHook()
+                LogManager logManager = LogManager.getLogManager();
+                if (logManager instanceof ClassLoaderLogManager) {
+                    ((ClassLoaderLogManager) logManager).setUseShutdownHook(
+                            false);
+                }
+            }
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            // This will fail on JDK 1.2. Ignoring, as Tomcat can run
+            // fine without the shutdown hook.
+        }
+
+        if (await) {
+            await();
+            stop();
+        }
+
+    }
+
+
+    /**
+     * Stop an existing server instance.
+     */
+    public void stop() {
+
+        try {
+            // Remove the ShutdownHook first so that server.stop()
+            // doesn't get invoked twice
+            if (useShutdownHook) {
+                Runtime.getRuntime().removeShutdownHook(shutdownHook);
+
+                // If JULI is being used, re-enable JULI's shutdown to ensure
+                // log messages are not lost
+                LogManager logManager = LogManager.getLogManager();
+                if (logManager instanceof ClassLoaderLogManager) {
+                    ((ClassLoaderLogManager) logManager).setUseShutdownHook(
+                            true);
+                }
+            }
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            // This will fail on JDK 1.2. Ignoring, as Tomcat can run
+            // fine without the shutdown hook.
+        }
+
+        // Shut down the server
+        try {
+            Server s = getServer();
+            LifecycleState state = s.getState();
+            if (LifecycleState.STOPPING_PREP.compareTo(state) <= 0
+                    && LifecycleState.DESTROYED.compareTo(state) >= 0) {
+                // Nothing to do. stop() was already called
+            } else {
+                s.stop();
+            }
+        } catch (LifecycleException e) {
+            log.error("Catalina.stop", e);
+        }
+
+    }
+
+
+    /**
+     * Await and shutdown.
+     */
+    public void await() {
+
+        getServer().await();
+
+    }
+
+
+    /**
+     * Print usage information for this application.
+     */
+    protected void usage() {
+
+        System.out.println
+            ("usage: java org.apache.catalina.startup.Catalina"
+             + " [ -config {pathname} ]"
+             + " [ -nonaming ] "
+             + " { -help | start | stop }");
+
+    }
+
+
+    protected void initDirs() {
+
+        String catalinaHome = System.getProperty(Globals.CATALINA_HOME_PROP);
+        if (catalinaHome == null) {
+            // Backwards compatibility patch for J2EE RI 1.3
+            String j2eeHome = System.getProperty("com.sun.enterprise.home");
+            if (j2eeHome != null) {
+                catalinaHome=System.getProperty("com.sun.enterprise.home");
+            } else if (System.getProperty(Globals.CATALINA_BASE_PROP) != null) {
+                catalinaHome = System.getProperty(Globals.CATALINA_BASE_PROP);
+            } else {
+                // Use IntrospectionUtils and guess the dir
+                catalinaHome = IntrospectionUtils.guessInstall
+                    (Globals.CATALINA_HOME_PROP, Globals.CATALINA_BASE_PROP, "catalina.jar");
+                if (catalinaHome == null) {
+                    catalinaHome = IntrospectionUtils.guessInstall
+                        ("tomcat.install", Globals.CATALINA_HOME_PROP, "tomcat.jar");
+                }
+            }
+        }
+        // last resort - for minimal/embedded cases.
+        if(catalinaHome==null) {
+            catalinaHome=System.getProperty("user.dir");
+        }
+        if (catalinaHome != null) {
+            File home = new File(catalinaHome);
+            if (!home.isAbsolute()) {
+                try {
+                    catalinaHome = home.getCanonicalPath();
+                } catch (IOException e) {
+                    catalinaHome = home.getAbsolutePath();
+                }
+            }
+            System.setProperty(Globals.CATALINA_HOME_PROP, catalinaHome);
+        }
+
+        if (System.getProperty(Globals.CATALINA_BASE_PROP) == null) {
+            System.setProperty(Globals.CATALINA_BASE_PROP,
+                               catalinaHome);
+        } else {
+            String catalinaBase = System.getProperty(Globals.CATALINA_BASE_PROP);
+            File base = new File(catalinaBase);
+            if (!base.isAbsolute()) {
+                try {
+                    catalinaBase = base.getCanonicalPath();
+                } catch (IOException e) {
+                    catalinaBase = base.getAbsolutePath();
+                }
+            }
+            System.setProperty(Globals.CATALINA_BASE_PROP, catalinaBase);
+        }
+
+        String temp = System.getProperty("java.io.tmpdir");
+        if (temp == null || (!(new File(temp)).exists())
+                || (!(new File(temp)).isDirectory())) {
+            log.error(sm.getString("embedded.notmp", temp));
+        }
+
+    }
+
+
+    protected void initStreams() {
+        // Replace System.out and System.err with a custom PrintStream
+        System.setOut(new SystemLogHandler(System.out));
+        System.setErr(new SystemLogHandler(System.err));
+    }
+
+
+    protected void initNaming() {
+        // Setting additional variables
+        if (!useNaming) {
+            log.info( "Catalina naming disabled");
+            System.setProperty("catalina.useNaming", "false");
+        } else {
+            System.setProperty("catalina.useNaming", "true");
+            String value = "org.apache.naming";
+            String oldValue =
+                System.getProperty(javax.naming.Context.URL_PKG_PREFIXES);
+            if (oldValue != null) {
+                value = value + ":" + oldValue;
+            }
+            System.setProperty(javax.naming.Context.URL_PKG_PREFIXES, value);
+            if( log.isDebugEnabled() )
+                log.debug("Setting naming prefix=" + value);
+            value = System.getProperty
+                (javax.naming.Context.INITIAL_CONTEXT_FACTORY);
+            if (value == null) {
+                System.setProperty
+                    (javax.naming.Context.INITIAL_CONTEXT_FACTORY,
+                     "org.apache.naming.java.javaURLContextFactory");
+            } else {
+                log.debug( "INITIAL_CONTEXT_FACTORY alread set " + value );
+            }
+        }
+    }
+
+
+    /**
+     * Set the security package access/protection.
+     */
+    protected void setSecurityProtection(){
+        SecurityConfig securityConfig = SecurityConfig.newInstance();
+        securityConfig.setPackageDefinition();
+        securityConfig.setPackageAccess();
+    }
+
+
+    // --------------------------------------- CatalinaShutdownHook Inner Class
+
+    // XXX Should be moved to embedded !
+    /**
+     * Shutdown hook which will perform a clean shutdown of Catalina if needed.
+     */
+    protected class CatalinaShutdownHook extends Thread {
+
+        @Override
+        public void run() {
+            try {
+                if (getServer() != null) {
+                    Catalina.this.stop();
+                }
+            } catch (Throwable ex) {
+                ExceptionUtils.handleThrowable(ex);
+                log.error(sm.getString("catalina.shutdownHookFail"), ex);
+            } finally {
+                // If JULI is used, shut JULI down *after* the server shuts down
+                // so log messages aren't lost
+                LogManager logManager = LogManager.getLogManager();
+                if (logManager instanceof ClassLoaderLogManager) {
+                    ((ClassLoaderLogManager) logManager).shutdown();
+                }
+            }
+        }
+    }
+
+
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( Catalina.class );
+
+}
+
+
+// ------------------------------------------------------------ Private Classes
+
+
+/**
+ * Rule that sets the parent class loader for the top object on the stack,
+ * which must be a <code>Container</code>.
+ */
+
+final class SetParentClassLoaderRule extends Rule {
+
+    public SetParentClassLoaderRule(ClassLoader parentClassLoader) {
+
+        this.parentClassLoader = parentClassLoader;
+
+    }
+
+    ClassLoader parentClassLoader = null;
+
+    @Override
+    public void begin(String namespace, String name, Attributes attributes)
+        throws Exception {
+
+        if (digester.getLogger().isDebugEnabled())
+            digester.getLogger().debug("Setting parent class loader");
+
+        Container top = (Container) digester.peek();
+        top.setParentClassLoader(parentClassLoader);
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/CatalinaProperties.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/CatalinaProperties.java
new file mode 100644
index 0000000..6b4708e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/CatalinaProperties.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.startup;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Properties;
+
+import org.apache.catalina.Globals;
+
+
+/**
+ * Utility class to read the bootstrap Catalina configuration.
+ *
+ * @author Remy Maucherat
+ * @version $Id: CatalinaProperties.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+public class CatalinaProperties {
+
+
+    // ------------------------------------------------------- Static Variables
+
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( CatalinaProperties.class );
+
+    private static Properties properties = null;
+
+
+    static {
+
+        loadProperties();
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return specified property value.
+     */
+    public static String getProperty(String name) {
+    
+        return properties.getProperty(name);
+
+    }
+
+
+    /**
+     * Return specified property value.
+     */
+    public static String getProperty(String name, String defaultValue) {
+
+        return properties.getProperty(name, defaultValue);
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Load properties.
+     */
+    private static void loadProperties() {
+
+        InputStream is = null;
+        Throwable error = null;
+
+        try {
+            String configUrl = getConfigUrl();
+            if (configUrl != null) {
+                is = (new URL(configUrl)).openStream();
+            }
+        } catch (Throwable t) {
+            handleThrowable(t);
+        }
+
+        if (is == null) {
+            try {
+                File home = new File(getCatalinaBase());
+                File conf = new File(home, "conf");
+                File propsFile = new File(conf, "catalina.properties");
+                is = new FileInputStream(propsFile);
+            } catch (Throwable t) {
+                handleThrowable(t);
+            }
+        }
+
+        if (is == null) {
+            try {
+                is = CatalinaProperties.class.getResourceAsStream
+                    ("/org/apache/catalina/startup/catalina.properties");
+            } catch (Throwable t) {
+                handleThrowable(t);
+            }
+        }
+
+        if (is != null) {
+            try {
+                properties = new Properties();
+                properties.load(is);
+                is.close();
+            } catch (Throwable t) {
+                handleThrowable(t);
+                error = t;
+            }
+        }
+
+        if ((is == null) || (error != null)) {
+            // Do something
+            log.warn("Failed to load catalina.properties", error);
+            // That's fine - we have reasonable defaults.
+            properties=new Properties();
+        }
+
+        // Register the properties as system properties
+        Enumeration<?> enumeration = properties.propertyNames();
+        while (enumeration.hasMoreElements()) {
+            String name = (String) enumeration.nextElement();
+            String value = properties.getProperty(name);
+            if (value != null) {
+                System.setProperty(name, value);
+            }
+        }
+
+    }
+
+
+    /**
+     * Get the value of the catalina.home environment variable.
+     */
+    private static String getCatalinaHome() {
+        return System.getProperty(Globals.CATALINA_HOME_PROP,
+                                  System.getProperty("user.dir"));
+    }
+    
+    
+    /**
+     * Get the value of the catalina.base environment variable.
+     */
+    private static String getCatalinaBase() {
+        return System.getProperty(Globals.CATALINA_BASE_PROP, getCatalinaHome());
+    }
+
+
+    /**
+     * Get the value of the configuration URL.
+     */
+    private static String getConfigUrl() {
+        return System.getProperty("catalina.config");
+    }
+
+    // Copied from ExceptionUtils since that class is not visible during start
+    private 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/catalina/startup/ClassLoaderFactory.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/ClassLoaderFactory.java
new file mode 100644
index 0000000..a35a23f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/ClassLoaderFactory.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.catalina.startup;
+
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+import org.apache.catalina.loader.StandardClassLoader;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+
+/**
+ * <p>Utility class for building class loaders for Catalina.  The factory
+ * method requires the following parameters in order to build a new class
+ * loader (with suitable defaults in all cases):</p>
+ * <ul>
+ * <li>A set of directories containing unpacked classes (and resources)
+ *     that should be included in the class loader's
+ *     repositories.</li>
+ * <li>A set of directories containing classes and resources in JAR files.
+ *     Each readable JAR file discovered in these directories will be
+ *     added to the class loader's repositories.</li>
+ * <li><code>ClassLoader</code> instance that should become the parent of
+ *     the new class loader.</li>
+ * </ul>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: ClassLoaderFactory.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public final class ClassLoaderFactory {
+
+
+    private static final Log log = LogFactory.getLog(ClassLoaderFactory.class);
+    
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Create and return a new class loader, based on the configuration
+     * defaults and the specified directory paths:
+     *
+     * @param unpacked Array of pathnames to unpacked directories that should
+     *  be added to the repositories of the class loader, or <code>null</code> 
+     * for no unpacked directories to be considered
+     * @param packed Array of pathnames to directories containing JAR files
+     *  that should be added to the repositories of the class loader, 
+     * or <code>null</code> for no directories of JAR files to be considered
+     * @param parent Parent class loader for the new class loader, or
+     *  <code>null</code> for the system class loader.
+     *
+     * @exception Exception if an error occurs constructing the class loader
+     */
+    public static ClassLoader createClassLoader(File unpacked[],
+                                                File packed[],
+                                                final ClassLoader parent)
+        throws Exception {
+
+        if (log.isDebugEnabled())
+            log.debug("Creating new class loader");
+
+        // Construct the "class path" for this class loader
+        Set<URL> set = new LinkedHashSet<URL>();
+
+        // Add unpacked directories
+        if (unpacked != null) {
+            for (int i = 0; i < unpacked.length; i++)  {
+                File file = unpacked[i];
+                if (!file.exists() || !file.canRead())
+                    continue;
+                file = new File(file.getCanonicalPath() + File.separator);
+                URL url = file.toURI().toURL();
+                if (log.isDebugEnabled())
+                    log.debug("  Including directory " + url);
+                set.add(url);
+            }
+        }
+
+        // Add packed directory JAR files
+        if (packed != null) {
+            for (int i = 0; i < packed.length; i++) {
+                File directory = packed[i];
+                if (!directory.isDirectory() || !directory.exists() ||
+                    !directory.canRead())
+                    continue;
+                String filenames[] = directory.list();
+                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 (log.isDebugEnabled())
+                        log.debug("  Including jar file " + file.getAbsolutePath());
+                    URL url = file.toURI().toURL();
+                    set.add(url);
+                }
+            }
+        }
+
+        // Construct the class loader itself
+        final URL[] array = set.toArray(new URL[set.size()]);
+        return AccessController.doPrivileged(
+                new PrivilegedAction<StandardClassLoader>() {
+                    @Override
+                    public StandardClassLoader run() {
+                        if (parent == null)
+                            return new StandardClassLoader(array);
+                        else
+                            return new StandardClassLoader(array, parent);
+                    }
+                });
+    }
+
+
+    /**
+     * Create and return a new class loader, based on the configuration
+     * defaults and the specified directory paths:
+     *
+     * @param repositories List of class directories, jar files, jar directories
+     *                     or URLS that should be added to the repositories of
+     *                     the class loader.
+     * @param parent Parent class loader for the new class loader, or
+     *  <code>null</code> for the system class loader.
+     *
+     * @exception Exception if an error occurs constructing the class loader
+     */
+    public static ClassLoader createClassLoader(List<Repository> repositories,
+                                                final ClassLoader parent)
+        throws Exception {
+
+        if (log.isDebugEnabled())
+            log.debug("Creating new class loader");
+
+        // Construct the "class path" for this class loader
+        Set<URL> set = new LinkedHashSet<URL>();
+
+        if (repositories != null) {
+            for (Repository repository : repositories)  {
+                if (repository.getType() == RepositoryType.URL) {
+                    URL url = new URL(repository.getLocation());
+                    if (log.isDebugEnabled())
+                        log.debug("  Including URL " + url);
+                    set.add(url);
+                } else if (repository.getType() == RepositoryType.DIR) {
+                    File directory = new File(repository.getLocation());
+                    directory = directory.getCanonicalFile();
+                    if (!validateFile(directory, RepositoryType.DIR)) {
+                        continue;
+                    }
+                    URL url = directory.toURI().toURL();
+                    if (log.isDebugEnabled())
+                        log.debug("  Including directory " + url);
+                    set.add(url);
+                } else if (repository.getType() == RepositoryType.JAR) {
+                    File file=new File(repository.getLocation());
+                    file = file.getCanonicalFile();
+                    if (!validateFile(file, RepositoryType.JAR)) {
+                        continue;
+                    }
+                    URL url = file.toURI().toURL();
+                    if (log.isDebugEnabled())
+                        log.debug("  Including jar file " + url);
+                    set.add(url);
+                } else if (repository.getType() == RepositoryType.GLOB) {
+                    File directory=new File(repository.getLocation());
+                    directory = directory.getCanonicalFile();
+                    if (!validateFile(directory, RepositoryType.GLOB)) {
+                        continue;
+                    }
+                    if (log.isDebugEnabled())
+                        log.debug("  Including directory glob "
+                            + directory.getAbsolutePath());
+                    String filenames[] = directory.list();
+                    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]);
+                        file = file.getCanonicalFile();
+                        if (!validateFile(file, RepositoryType.JAR)) {
+                            continue;
+                        }
+                        if (log.isDebugEnabled())
+                            log.debug("    Including glob jar file "
+                                + file.getAbsolutePath());
+                        URL url = file.toURI().toURL();
+                        set.add(url);
+                    }
+                }
+            }
+        }
+
+        // Construct the class loader itself
+        final URL[] array = set.toArray(new URL[set.size()]);
+        if (log.isDebugEnabled())
+            for (int i = 0; i < array.length; i++) {
+                log.debug("  location " + i + " is " + array[i]);
+            }
+
+        return AccessController.doPrivileged(
+                new PrivilegedAction<StandardClassLoader>() {
+                    @Override
+                    public StandardClassLoader run() {
+                        if (parent == null)
+                            return new StandardClassLoader(array);
+                        else
+                            return new StandardClassLoader(array, parent);
+                    }
+                });
+    }
+
+    private static boolean validateFile(File file,
+            RepositoryType type) throws IOException {
+        if (RepositoryType.DIR == type || RepositoryType.GLOB == type) {
+            if (!file.exists() || !file.isDirectory() || !file.canRead()) {
+                String msg = "Problem with directory [" + file +
+                        "], exists: [" + file.exists() +
+                        "], isDirectory: [" + file.isDirectory() +
+                        "], canRead: [" + file.canRead() + "]";
+
+                File home = new File (Bootstrap.getCatalinaHome());
+                home = home.getCanonicalFile();
+                File base = new File (Bootstrap.getCatalinaBase());
+                base = base.getCanonicalFile();
+                File defaultValue = new File(base, "lib");
+
+                // Existence of ${catalina.base}/lib directory is optional.
+                // Hide the warning if Tomcat runs with separate catalina.home
+                // and catalina.base and that directory is absent.
+                if (!home.getPath().equals(base.getPath())
+                        && file.getPath().equals(defaultValue.getPath())
+                        && !file.exists()) {
+                    log.debug(msg);
+                } else {
+                    log.warn(msg);
+                }
+                return false;
+            }
+        } else if (RepositoryType.JAR == type) {
+            if (!file.exists() || !file.canRead()) {
+                log.warn("Problem with JAR file [" + file +
+                        "], exists: [" + file.exists() +
+                        "], canRead: [" + file.canRead() + "]");
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static enum RepositoryType {
+        DIR,
+        GLOB,
+        JAR,
+        URL
+    }
+    
+    public static class Repository {
+        private String location;
+        private RepositoryType type;
+        
+        public Repository(String location, RepositoryType type) {
+            this.location = location;
+            this.type = type;
+        }
+        
+        public String getLocation() {
+            return location;
+        }
+        
+        public RepositoryType getType() {
+            return type;
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/ClusterRuleSetFactory.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/ClusterRuleSetFactory.java
new file mode 100644
index 0000000..5b627f1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/ClusterRuleSetFactory.java
@@ -0,0 +1,205 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.startup;
+
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.digester.Digester;
+import org.apache.tomcat.util.digester.RuleSetBase;
+public class ClusterRuleSetFactory {
+    
+    public static final Log log = LogFactory.getLog(ClusterRuleSetFactory.class);
+    
+    public static RuleSetBase getClusterRuleSet(String prefix) {
+        
+        //OLD CLUSTER 1
+        //first try the same classloader as this class server/lib
+        try {
+            return loadRuleSet(prefix,"org.apache.catalina.cluster.ClusterRuleSet",ClusterRuleSetFactory.class.getClassLoader());
+        } catch ( Exception x ) {
+            //display warning
+            if ( log.isDebugEnabled() ) log.debug("Unable to load ClusterRuleSet (org.apache.catalina.cluster.ClusterRuleSet), falling back on context classloader");
+        }
+        //try to load it from the context class loader
+        try {
+            return loadRuleSet(prefix,"org.apache.catalina.cluster.ClusterRuleSet",Thread.currentThread().getContextClassLoader());
+        } catch ( Exception x ) {
+            //display warning
+            if ( log.isDebugEnabled() ) log.debug("Unable to load ClusterRuleSet (org.apache.catalina.cluster.ClusterRuleSet), will try to load the HA cluster");
+        }
+        
+        //NEW CLUSTER 2
+        //first try the same classloader as this class server/lib
+        try {
+            return loadRuleSet(prefix,"org.apache.catalina.ha.ClusterRuleSet",ClusterRuleSetFactory.class.getClassLoader());
+        } catch ( Exception x ) {
+            //display warning
+            if ( log.isDebugEnabled() ) log.debug("Unable to load HA ClusterRuleSet (org.apache.catalina.ha.ClusterRuleSet), falling back on context classloader");
+        }
+        //try to load it from the context class loader
+        try {
+            return loadRuleSet(prefix,"org.apache.catalina.ha.ClusterRuleSet",Thread.currentThread().getContextClassLoader());
+        } catch ( Exception x ) {
+            //display warning
+            if ( log.isDebugEnabled() ) log.debug("Unable to load HA ClusterRuleSet (org.apache.catalina.ha.ClusterRuleSet), falling back on DefaultClusterRuleSet");
+        }
+
+        log.info("Unable to find a cluster rule set in the classpath. Will load the default rule set.");
+        return new DefaultClusterRuleSet(prefix);
+    }
+    
+    
+    protected static RuleSetBase loadRuleSet(String prefix, String className, ClassLoader cl) 
+        throws ClassNotFoundException, InstantiationException, 
+               NoSuchMethodException,IllegalAccessException,
+               InvocationTargetException {
+        Class<?> clazz = Class.forName(className,true,cl);
+        Constructor<?> cons = clazz.getConstructor(new Class[] {String.class});
+        return (RuleSetBase)cons.newInstance(prefix);
+    }
+    
+    /**
+     * <p><strong>RuleSet</strong> for processing the contents of a
+     * Cluster definition element.  </p>
+     *
+     * @author Filip Hanik
+     * @author Peter Rossbach
+     * @version $Id: ClusterRuleSetFactory.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+     */
+
+    public static class DefaultClusterRuleSet 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 DefaultClusterRuleSet() {
+
+            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 DefaultClusterRuleSet(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 + "Membership",
+                                     null, // MUST be specified in the element
+                                     "className");
+            digester.addSetProperties(prefix + "Membership");
+            digester.addSetNext(prefix + "Membership",
+                                "setMembershipService",
+                                "org.apache.catalina.cluster.MembershipService");
+
+            digester.addObjectCreate(prefix + "Sender",
+                                     null, // MUST be specified in the element
+                                     "className");
+            digester.addSetProperties(prefix + "Sender");
+            digester.addSetNext(prefix + "Sender",
+                                "setClusterSender",
+                                "org.apache.catalina.cluster.ClusterSender");
+
+            digester.addObjectCreate(prefix + "Receiver",
+                                     null, // MUST be specified in the element
+                                     "className");
+            digester.addSetProperties(prefix + "Receiver");
+            digester.addSetNext(prefix + "Receiver",
+                                "setClusterReceiver",
+                                "org.apache.catalina.cluster.ClusterReceiver");
+
+            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.cluster.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.cluster.MessageListener");
+            //Cluster configuration end
+        }
+
+
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/ConnectorCreateRule.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/ConnectorCreateRule.java
new file mode 100644
index 0000000..2c87397
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/ConnectorCreateRule.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.catalina.startup;
+
+
+import java.lang.reflect.Method;
+
+import org.apache.catalina.Executor;
+import org.apache.catalina.Service;
+import org.apache.catalina.connector.Connector;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.IntrospectionUtils;
+import org.apache.tomcat.util.digester.Rule;
+import org.xml.sax.Attributes;
+
+
+/**
+ * Rule implementation that creates a connector.
+ */
+
+public class ConnectorCreateRule extends Rule {
+
+    private static final Log log = LogFactory.getLog(ConnectorCreateRule.class);
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Process the beginning of this element.
+     *
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the parser is not namespace aware or the element has
+     *   no namespace
+     * @param name the local name if the parser is namespace aware, or just 
+     *   the element name otherwise
+     * @param attributes The attribute list for this element
+     */
+    @Override
+    public void begin(String namespace, String name, Attributes attributes)
+            throws Exception {
+        Service svc = (Service)digester.peek();
+        Executor ex = null;
+        if ( attributes.getValue("executor")!=null ) {
+            ex = svc.getExecutor(attributes.getValue("executor"));
+        }
+        Connector con = new Connector(attributes.getValue("protocol"));
+        if ( ex != null )  _setExecutor(con,ex);
+        
+        digester.push(con);
+    }
+    
+    public void _setExecutor(Connector con, Executor ex) throws Exception {
+        Method m = IntrospectionUtils.findMethod(con.getProtocolHandler().getClass(),"setExecutor",new Class[] {java.util.concurrent.Executor.class});
+        if (m!=null) {
+            m.invoke(con.getProtocolHandler(), new Object[] {ex});
+        }else {
+            log.warn("Connector ["+con+"] does not support external executors. Method setExecutor(java.util.concurrent.Executor) not found.");
+        }
+    }
+
+
+    /**
+     * Process the end of this element.
+     * 
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the parser is not namespace aware or the element has
+     *   no namespace
+     * @param name the local name if the parser is namespace aware, or just 
+     *   the element name otherwise
+     */
+    @Override
+    public void end(String namespace, String name) throws Exception {
+        digester.pop();
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/Constants.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/Constants.java
new file mode 100644
index 0000000..d356666
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/Constants.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.startup;
+
+
+/**
+ * String constants for the startup package.
+ *
+ * @author Craig R. McClanahan
+ * @author Jean-Francois Arcand
+ * @version $Id: Constants.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public final class Constants {
+
+    public static final String Package = "org.apache.catalina.startup";
+
+    public static final String ApplicationContextXml = "META-INF/context.xml";
+    public static final String ApplicationWebXml = "/WEB-INF/web.xml";
+    public static final String DefaultContextXml = "conf/context.xml";
+    public static final String DefaultWebXml = "conf/web.xml";
+    public static final String HostContextXml = "context.xml.default";
+    public static final String HostWebXml = "web.xml.default";
+
+    // J2EE
+    public static final String J2eeSchemaPublicId_14 =
+        "j2ee_1_4.xsd";
+    public static final String J2eeSchemaResourcePath_14 =
+        "/javax/servlet/resources/j2ee_1_4.xsd";
+
+    public static final String JavaeeSchemaPublicId_5 =
+        "javaee_5.xsd";
+    public static final String JavaeeSchemaResourcePath_5 =
+        "/javax/servlet/resources/javaee_5.xsd";
+
+    public static final String JavaeeSchemaPublicId_6 =
+        "javaee_6.xsd";
+    public static final String JavaeeSchemaResourcePath_6 =
+        "/javax/servlet/resources/javaee_6.xsd";
+
+    
+    // W3C
+    public static final String W3cSchemaPublicId_10 =
+        "xml.xsd";
+    public static final String W3cSchemaResourcePath_10 =
+        "/javax/servlet/resources/xml.xsd";
+
+    public static final String W3cSchemaDTDPublicId_10 =
+        "XMLSchema.dtd";
+    public static final String W3cSchemaDTDResourcePath_10 =
+        "/javax/servlet/resources/XMLSchema.dtd";
+
+    public static final String W3cDatatypesDTDPublicId_10 =
+        "datatypes.dtd";
+    public static final String W3cDatatypesDTDResourcePath_10 =
+        "/javax/servlet/resources/datatypes.dtd";
+
+    
+    // JSP
+    public static final String JspSchemaPublicId_20 =
+        "jsp_2_0.xsd";
+    public static final String JspSchemaResourcePath_20 =
+        "/javax/servlet/jsp/resources/jsp_2_0.xsd";
+    
+    public static final String JspSchemaPublicId_21 =
+        "jsp_2_1.xsd";
+    public static final String JspSchemaResourcePath_21 =
+        "/javax/servlet/jsp/resources/jsp_2_1.xsd";
+
+    public static final String JspSchemaPublicId_22 =
+        "jsp_2_2.xsd";
+    public static final String JspSchemaResourcePath_22 =
+        "/javax/servlet/jsp/resources/jsp_2_2.xsd";
+
+
+    // TLD
+    public static final String TldDtdPublicId_11 =
+        "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN";
+    public static final String TldDtdResourcePath_11 =
+        "/javax/servlet/jsp/resources/web-jsptaglibrary_1_1.dtd";
+
+    public static final String TldDtdPublicId_12 =
+        "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN";
+    public static final String TldDtdResourcePath_12 =
+        "/javax/servlet/jsp/resources/web-jsptaglibrary_1_2.dtd";
+
+    public static final String TldSchemaPublicId_20 =
+        "web-jsptaglibrary_2_0.xsd";
+    public static final String TldSchemaResourcePath_20 =
+        "/javax/servlet/jsp/resources/web-jsptaglibrary_2_0.xsd";
+
+    public static final String TldSchemaPublicId_21 =
+        "web-jsptaglibrary_2_1.xsd";
+    public static final String TldSchemaResourcePath_21 =
+        "/javax/servlet/jsp/resources/web-jsptaglibrary_2_1.xsd";
+
+    
+    // web.xml
+    public static final String WebDtdPublicId_22 =
+        "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN";
+    public static final String WebDtdResourcePath_22 =
+        "/javax/servlet/resources/web-app_2_2.dtd";
+
+    public static final String WebDtdPublicId_23 =
+        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN";
+    public static final String WebDtdResourcePath_23 =
+        "/javax/servlet/resources/web-app_2_3.dtd";
+
+    public static final String WebSchemaPublicId_24 =
+        "web-app_2_4.xsd";
+    public static final String WebSchemaResourcePath_24 =
+        "/javax/servlet/resources/web-app_2_4.xsd";
+
+    public static final String WebSchemaPublicId_25 =
+        "web-app_2_5.xsd";
+    public static final String WebSchemaResourcePath_25 =
+        "/javax/servlet/resources/web-app_2_5.xsd";
+
+    public static final String WebSchemaPublicId_30 =
+        "web-app_3_0.xsd";
+    public static final String WebSchemaResourcePath_30 =
+        "/javax/servlet/resources/web-app_3_0.xsd";
+
+    public static final String WebCommonSchemaPublicId_30 =
+        "web-common_3_0.xsd";
+    public static final String WebCommonSchemaResourcePath_30 =
+        "/javax/servlet/resources/web-common_3_0.xsd";
+
+    public static final String WebFragmentSchemaPublicId_30 =
+        "web-fragment_3_0.xsd";
+    public static final String WebFragmentSchemaResourcePath_30 =
+        "/javax/servlet/resources/web-fragment_3_0.xsd";
+    
+    // Web service
+    public static final String J2eeWebServiceSchemaPublicId_11 =
+            "j2ee_web_services_1_1.xsd";
+    public static final String J2eeWebServiceSchemaResourcePath_11 =
+            "/javax/servlet/resources/j2ee_web_services_1_1.xsd";
+    
+    public static final String J2eeWebServiceClientSchemaPublicId_11 =
+            "j2ee_web_services_client_1_1.xsd";
+    public static final String J2eeWebServiceClientSchemaResourcePath_11 =
+            "/javax/servlet/resources/j2ee_web_services_client_1_1.xsd";
+
+    public static final String JavaeeWebServiceSchemaPublicId_12 =
+        "javaee_web_services_1_2.xsd";
+    public static final String JavaeeWebServiceSchemaResourcePath_12 =
+        "/javax/servlet/resources/javaee_web_services_1_2.xsd";
+
+    public static final String JavaeeWebServiceClientSchemaPublicId_12 =
+        "javaee_web_services_client_1_2.xsd";
+    public static final String JavaeeWebServiceClientSchemaResourcePath_12 =
+        "/javax/servlet/resources/javaee_web_services_client_1_2.xsd";
+
+    public static final String JavaeeWebServiceSchemaPublicId_13 =
+        "javaee_web_services_1_3.xsd";
+    public static final String JavaeeWebServiceSchemaResourcePath_13 =
+        "/javax/servlet/resources/javaee_web_services_1_3.xsd";
+
+    public static final String JavaeeWebServiceClientSchemaPublicId_13 =
+        "javaee_web_services_client_1_3.xsd";
+    public static final String JavaeeWebServiceClientSchemaResourcePath_13 =
+        "/javax/servlet/resources/javaee_web_services_client_1_3.xsd";
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/ContextConfig.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/ContextConfig.java
new file mode 100644
index 0000000..d261ba1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/ContextConfig.java
@@ -0,0 +1,2386 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.startup;
+
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.JarURLConnection;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.annotation.HandlesTypes;
+
+import org.apache.catalina.Authenticator;
+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.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.Pipeline;
+import org.apache.catalina.Valve;
+import org.apache.catalina.Wrapper;
+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.deploy.ErrorPage;
+import org.apache.catalina.deploy.FilterDef;
+import org.apache.catalina.deploy.FilterMap;
+import org.apache.catalina.deploy.LoginConfig;
+import org.apache.catalina.deploy.SecurityConstraint;
+import org.apache.catalina.deploy.ServletDef;
+import org.apache.catalina.deploy.WebXml;
+import org.apache.catalina.util.ContextName;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.naming.resources.DirContextURLConnection;
+import org.apache.naming.resources.ResourceAttributes;
+import org.apache.tomcat.JarScanner;
+import org.apache.tomcat.JarScannerCallback;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.bcel.classfile.AnnotationElementValue;
+import org.apache.tomcat.util.bcel.classfile.AnnotationEntry;
+import org.apache.tomcat.util.bcel.classfile.ArrayElementValue;
+import org.apache.tomcat.util.bcel.classfile.ClassFormatException;
+import org.apache.tomcat.util.bcel.classfile.ClassParser;
+import org.apache.tomcat.util.bcel.classfile.ElementValue;
+import org.apache.tomcat.util.bcel.classfile.ElementValuePair;
+import org.apache.tomcat.util.bcel.classfile.JavaClass;
+import org.apache.tomcat.util.digester.Digester;
+import org.apache.tomcat.util.digester.RuleSet;
+import org.apache.tomcat.util.res.StringManager;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXParseException;
+
+/**
+ * Startup event listener for a <b>Context</b> that configures the properties
+ * of that Context, and the associated defined servlets.
+ *
+ * @author Craig R. McClanahan
+ * @author Jean-Francois Arcand
+ * @version $Id: ContextConfig.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public class ContextConfig
+    implements LifecycleListener {
+
+    private static final Log log = LogFactory.getLog( ContextConfig.class );
+    
+    private static final String SCI_LOCATION =
+        "META-INF/services/javax.servlet.ServletContainerInitializer";
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Custom mappings of login methods to authenticators
+     */
+    protected Map<String,Authenticator> customAuthenticators;
+
+
+    /**
+     * The set of Authenticators that we know how to configure.  The key is
+     * the name of the implemented authentication method, and the value is
+     * the fully qualified Java class name of the corresponding Valve.
+     */
+    protected static Properties authenticators = null;
+
+
+    /**
+     * The Context we are associated with.
+     */
+    protected Context context = null;
+
+
+    /**
+     * The default web application's context file location.
+     */
+    protected String defaultContextXml = null;
+    
+    
+    /**
+     * The default web application's deployment descriptor location.
+     */
+    protected String defaultWebXml = null;
+    
+    
+    /**
+     * Track any fatal errors during startup configuration processing.
+     */
+    protected boolean ok = false;
+
+
+    /**
+     * Original docBase.
+     */
+    protected String originalDocBase = null;
+    
+
+    /**
+     * Map of ServletContainerInitializer to classes they expressed interest in.
+     */
+    protected Map<ServletContainerInitializer, Set<Class<?>>> initializerClassMap =
+        new LinkedHashMap<ServletContainerInitializer, Set<Class<?>>>();
+    
+    /**
+     * Map of Types to ServletContainerInitializer that are interested in those
+     * types.
+     */
+    protected Map<Class<?>, Set<ServletContainerInitializer>> typeInitializerMap =
+        new HashMap<Class<?>, Set<ServletContainerInitializer>>();
+
+    /**
+     * The string resources for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
+     * The <code>Digester</code> we will use to process web application
+     * context files.
+     */
+    protected static Digester contextDigester = null;
+    
+    
+    /**
+     * The <code>Digester</code> we will use to process web application
+     * deployment descriptor files.
+     */
+    protected Digester webDigester = null;
+
+    /**
+     * The <code>Digester</code> we will use to process web fragment
+     * deployment descriptor files.
+     */
+    protected Digester webFragmentDigester = null;
+
+    
+    protected static Digester[] webDigesters = new Digester[4];
+
+    /**
+     * The <code>Digester</code>s available to process web fragment
+     * deployment descriptor files.
+     */
+    protected static Digester[] webFragmentDigesters = new Digester[4];
+    
+    /**
+     * The <code>Rule</code>s used to parse the web.xml
+     */
+    protected static WebRuleSet webRuleSet = new WebRuleSet(false);
+
+    /**
+     * The <code>Rule</code>s used to parse the web-fragment.xml
+     */
+    protected static WebRuleSet webFragmentRuleSet = new WebRuleSet(true);
+
+    /**
+     * Deployment count.
+     */
+    protected static long deploymentCount = 0L;
+    
+    
+    protected static final LoginConfig DUMMY_LOGIN_CONFIG =
+                                new LoginConfig("NONE", null, null, null);
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the location of the default deployment descriptor
+     */
+    public String getDefaultWebXml() {
+        if( defaultWebXml == null ) {
+            defaultWebXml=Constants.DefaultWebXml;
+        }
+
+        return (this.defaultWebXml);
+
+    }
+
+
+    /**
+     * Set the location of the default deployment descriptor
+     *
+     * @param path Absolute/relative path to the default web.xml
+     */
+    public void setDefaultWebXml(String path) {
+
+        this.defaultWebXml = path;
+
+    }
+
+
+    /**
+     * Return the location of the default context file
+     */
+    public String getDefaultContextXml() {
+        if( defaultContextXml == null ) {
+            defaultContextXml=Constants.DefaultContextXml;
+        }
+
+        return (this.defaultContextXml);
+
+    }
+
+
+    /**
+     * Set the location of the default context file
+     *
+     * @param path Absolute/relative path to the default context.xml
+     */
+    public void setDefaultContextXml(String path) {
+
+        this.defaultContextXml = path;
+
+    }
+
+
+    /**
+     * Sets custom mappings of login methods to authenticators.
+     *
+     * @param customAuthenticators Custom mappings of login methods to
+     * authenticators
+     */
+    public void setCustomAuthenticators(
+            Map<String,Authenticator> customAuthenticators) {
+        this.customAuthenticators = customAuthenticators;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Process events for an associated Context.
+     *
+     * @param event The lifecycle event that has occurred
+     */
+    @Override
+    public void lifecycleEvent(LifecycleEvent event) {
+
+        // Identify the context we are associated with
+        try {
+            context = (Context) event.getLifecycle();
+        } catch (ClassCastException e) {
+            log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
+            return;
+        }
+
+        // Process the event that has occurred
+        if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
+            configureStart();
+        } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
+            beforeStart();
+        } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
+            // Restore docBase for management tools
+            if (originalDocBase != null) {
+                String docBase = context.getDocBase();
+                context.setDocBase(originalDocBase);
+                originalDocBase = docBase;
+            }
+        } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
+            if (originalDocBase != null) {
+                String docBase = context.getDocBase();
+                context.setDocBase(originalDocBase);
+                originalDocBase = docBase;
+            }
+            configureStop();
+        } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
+            init();
+        } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
+            destroy();
+        }
+
+    }
+
+
+    // -------------------------------------------------------- protected Methods
+
+
+    /**
+     * Process the application classes annotations, if it exists.
+     */
+    protected void applicationAnnotationsConfig() {
+        
+        long t1=System.currentTimeMillis();
+        
+        WebAnnotationSet.loadApplicationAnnotations(context);
+        
+        long t2=System.currentTimeMillis();
+        if (context instanceof StandardContext) {
+            ((StandardContext) context).setStartupTime(t2-t1+
+                    ((StandardContext) context).getStartupTime());
+        }
+    }
+
+
+    /**
+     * Set up an Authenticator automatically if required, and one has not
+     * already been configured.
+     */
+    protected synchronized void authenticatorConfig() {
+
+        LoginConfig loginConfig = context.getLoginConfig();
+
+        SecurityConstraint constraints[] = context.findConstraints();
+        if (context.getIgnoreAnnotations() &&
+                (constraints == null || constraints.length ==0) &&
+                !context.getPreemptiveAuthentication())  {
+            return;
+        } else {
+            if (loginConfig == null) {
+                // Not metadata-complete or security constraints present, need
+                // an authenticator to support @ServletSecurity annotations
+                // and/or constraints
+                loginConfig = DUMMY_LOGIN_CONFIG;
+                context.setLoginConfig(loginConfig);
+            }
+        }
+
+        // Has an authenticator been configured already?
+        if (context.getAuthenticator() != null)
+            return;
+        
+        if (!(context instanceof ContainerBase)) {
+            return;     // Cannot install a Valve even if it would be needed
+        }
+
+        // Has a Realm been configured for us to authenticate against?
+        if (context.getRealm() == null) {
+            log.error(sm.getString("contextConfig.missingRealm"));
+            ok = false;
+            return;
+        }
+
+        /*
+         * First check to see if there is a custom mapping for the login
+         * method. If so, use it. Otherwise, check if there is a mapping in
+         * org/apache/catalina/startup/Authenticators.properties.
+         */
+        Valve authenticator = null;
+        if (customAuthenticators != null) {
+            authenticator = (Valve)
+                customAuthenticators.get(loginConfig.getAuthMethod());
+        }
+        if (authenticator == null) {
+            // Load our mapping properties if necessary
+            if (authenticators == null) {
+                try {
+                    InputStream is=this.getClass().getClassLoader().getResourceAsStream("org/apache/catalina/startup/Authenticators.properties");
+                    if( is!=null ) {
+                        authenticators = new Properties();
+                        authenticators.load(is);
+                    } else {
+                        log.error(sm.getString(
+                                "contextConfig.authenticatorResources"));
+                        ok=false;
+                        return;
+                    }
+                } catch (IOException e) {
+                    log.error(sm.getString(
+                                "contextConfig.authenticatorResources"), e);
+                    ok = false;
+                    return;
+                }
+            }
+
+            // Identify the class name of the Valve we should configure
+            String authenticatorName = null;
+            authenticatorName =
+                    authenticators.getProperty(loginConfig.getAuthMethod());
+            if (authenticatorName == null) {
+                log.error(sm.getString("contextConfig.authenticatorMissing",
+                                 loginConfig.getAuthMethod()));
+                ok = false;
+                return;
+            }
+
+            // Instantiate and install an Authenticator of the requested class
+            try {
+                Class<?> authenticatorClass = Class.forName(authenticatorName);
+                authenticator = (Valve) authenticatorClass.newInstance();
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                log.error(sm.getString(
+                                    "contextConfig.authenticatorInstantiate",
+                                    authenticatorName),
+                          t);
+                ok = false;
+            }
+        }
+
+        if (authenticator != null && context instanceof ContainerBase) {
+            Pipeline pipeline = ((ContainerBase) context).getPipeline();
+            if (pipeline != null) {
+                ((ContainerBase) context).getPipeline().addValve(authenticator);
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString(
+                                    "contextConfig.authenticatorConfigured",
+                                    loginConfig.getAuthMethod()));
+                }
+            }
+        }
+
+    }
+
+
+    /**
+     * Create (if necessary) and return a Digester configured to process the
+     * web application deployment descriptor (web.xml).
+     */
+    public void createWebXmlDigester(boolean namespaceAware,
+            boolean validation) {
+        
+        if (!namespaceAware && !validation) {
+            if (webDigesters[0] == null) {
+                webDigesters[0] = DigesterFactory.newDigester(validation,
+                        namespaceAware, webRuleSet);
+                webFragmentDigesters[0] = DigesterFactory.newDigester(validation,
+                        namespaceAware, webFragmentRuleSet);
+            }
+            webDigester = webDigesters[0];
+            webFragmentDigester = webFragmentDigesters[0];
+            
+        } else if (!namespaceAware && validation) {
+            if (webDigesters[1] == null) {
+                webDigesters[1] = DigesterFactory.newDigester(validation,
+                        namespaceAware, webRuleSet);
+                webFragmentDigesters[1] = DigesterFactory.newDigester(validation,
+                        namespaceAware, webFragmentRuleSet);
+            }
+            webDigester = webDigesters[1];
+            webFragmentDigester = webFragmentDigesters[1];
+            
+        } else if (namespaceAware && !validation) {
+            if (webDigesters[2] == null) {
+                webDigesters[2] = DigesterFactory.newDigester(validation,
+                        namespaceAware, webRuleSet);
+                webFragmentDigesters[2] = DigesterFactory.newDigester(validation,
+                        namespaceAware, webFragmentRuleSet);
+            }
+            webDigester = webDigesters[2];
+            webFragmentDigester = webFragmentDigesters[2];
+            
+        } else {
+            if (webDigesters[3] == null) {
+                webDigesters[3] = DigesterFactory.newDigester(validation,
+                        namespaceAware, webRuleSet);
+                webFragmentDigesters[3] = DigesterFactory.newDigester(validation,
+                        namespaceAware, webFragmentRuleSet);
+            }
+            webDigester = webDigesters[3];
+            webFragmentDigester = webFragmentDigesters[3];
+        }
+    }
+
+    
+    /**
+     * Create (if necessary) and return a Digester configured to process the
+     * context configuration descriptor for an application.
+     */
+    protected Digester createContextDigester() {
+        Digester digester = new Digester();
+        digester.setValidating(false);
+        digester.setRulesValidation(true);
+        HashMap<Class<?>, List<String>> fakeAttributes =
+            new HashMap<Class<?>, List<String>>();
+        ArrayList<String> attrs = new ArrayList<String>();
+        attrs.add("className");
+        fakeAttributes.put(Object.class, attrs);
+        digester.setFakeAttributes(fakeAttributes);
+        RuleSet contextRuleSet = new ContextRuleSet("", false);
+        digester.addRuleSet(contextRuleSet);
+        RuleSet namingRuleSet = new NamingRuleSet("Context/");
+        digester.addRuleSet(namingRuleSet);
+        return digester;
+    }
+
+
+    protected String getBaseDir() {
+        Container engineC=context.getParent().getParent();
+        if( engineC instanceof StandardEngine ) {
+            return ((StandardEngine)engineC).getBaseDir();
+        }
+        return System.getProperty(Globals.CATALINA_BASE_PROP);
+    }
+
+    
+    /**
+     * Process the default configuration file, if it exists.
+     */
+    protected void contextConfig() {
+        
+        // Open the default context.xml file, if it exists
+        if( defaultContextXml==null && context instanceof StandardContext ) {
+            defaultContextXml = ((StandardContext)context).getDefaultContextXml();
+        }
+        // set the default if we don't have any overrides
+        if( defaultContextXml==null ) getDefaultContextXml();
+
+        if (!context.getOverride()) {
+            File defaultContextFile = new File(defaultContextXml);
+            if (!defaultContextFile.isAbsolute()) {
+                defaultContextFile =new File(getBaseDir(), defaultContextXml);
+            }
+            if (defaultContextFile.exists()) {
+                try {
+                    URL defaultContextUrl = defaultContextFile.toURI().toURL();
+                    processContextConfig(defaultContextUrl);
+                } catch (MalformedURLException e) {
+                    log.error(sm.getString(
+                            "contextConfig.badUrl", defaultContextFile), e);
+                }
+            }
+            
+            File hostContextFile = new File(getConfigBase(),
+                    getHostConfigPath(Constants.HostContextXml));
+            if (hostContextFile.exists()) {
+                try {
+                    URL hostContextUrl = hostContextFile.toURI().toURL();
+                    processContextConfig(hostContextUrl);
+                } catch (MalformedURLException e) {
+                    log.error(sm.getString(
+                            "contextConfig.badUrl", hostContextFile), e);
+                }
+            }
+        }
+        if (context.getConfigFile() != null)
+            processContextConfig(context.getConfigFile());
+        
+    }
+
+    
+    /**
+     * Process a context.xml.
+     */
+    protected void processContextConfig(URL contextXml) {
+        
+        if (log.isDebugEnabled())
+            log.debug("Processing context [" + context.getName() 
+                    + "] configuration file [" + contextXml + "]");
+
+        InputSource source = null;
+        InputStream stream = null;
+
+        try {
+            source = new InputSource(contextXml.toString());
+            stream = contextXml.openStream();
+            
+            // Add as watched resource so that cascade reload occurs if a default
+            // config file is modified/added/removed
+            if ("file".equals(contextXml.getProtocol())) {
+                context.addWatchedResource(
+                        (new File(contextXml.toURI())).getAbsolutePath());
+            }
+        } catch (Exception e) {
+            log.error(sm.getString("contextConfig.contextMissing",  
+                      contextXml) , e);
+        }
+        
+        if (source == null)
+            return;
+        synchronized (contextDigester) {
+            try {
+                source.setByteStream(stream);
+                contextDigester.setClassLoader(this.getClass().getClassLoader());
+                contextDigester.setUseContextClassLoader(false);
+                contextDigester.push(context.getParent());
+                contextDigester.push(context);
+                XmlErrorHandler errorHandler = new XmlErrorHandler();
+                contextDigester.setErrorHandler(errorHandler);
+                contextDigester.parse(source);
+                if (errorHandler.getWarnings().size() > 0 ||
+                        errorHandler.getErrors().size() > 0) {
+                    errorHandler.logFindings(log, contextXml.toString());
+                    ok = false;
+                }
+                if (log.isDebugEnabled())
+                    log.debug("Successfully processed context [" + context.getName() 
+                            + "] configuration file [" + contextXml + "]");
+            } catch (SAXParseException e) {
+                log.error(sm.getString("contextConfig.contextParse",
+                        context.getName()), e);
+                log.error(sm.getString("contextConfig.defaultPosition",
+                                 "" + e.getLineNumber(),
+                                 "" + e.getColumnNumber()));
+                ok = false;
+            } catch (Exception e) {
+                log.error(sm.getString("contextConfig.contextParse",
+                        context.getName()), e);
+                ok = false;
+            } finally {
+                contextDigester.reset();
+                try {
+                    if (stream != null) {
+                        stream.close();
+                    }
+                } catch (IOException e) {
+                    log.error(sm.getString("contextConfig.contextClose"), e);
+                }
+            }
+        }
+    }
+
+    
+    /**
+     * Adjust docBase.
+     */
+    protected void fixDocBase()
+        throws IOException {
+        
+        Host host = (Host) context.getParent();
+        String appBase = host.getAppBase();
+
+        File canonicalAppBase = new File(appBase);
+        if (canonicalAppBase.isAbsolute()) {
+            canonicalAppBase = canonicalAppBase.getCanonicalFile();
+        } else {
+            canonicalAppBase = 
+                new File(System.getProperty(Globals.CATALINA_BASE_PROP), appBase)
+                .getCanonicalFile();
+        }
+
+        String docBase = context.getDocBase();
+        if (docBase == null) {
+            // Trying to guess the docBase according to the path
+            String path = context.getPath();
+            if (path == null) {
+                return;
+            }
+            ContextName cn = new ContextName(path, context.getWebappVersion());
+            docBase = cn.getBaseName();
+        }
+
+        File file = new File(docBase);
+        if (!file.isAbsolute()) {
+            docBase = (new File(canonicalAppBase, docBase)).getPath();
+        } else {
+            docBase = file.getCanonicalPath();
+        }
+        file = new File(docBase);
+        String origDocBase = docBase;
+        
+        ContextName cn = new ContextName(context.getPath(),
+                context.getWebappVersion());
+        String pathName = cn.getBaseName();
+
+        boolean unpackWARs = true;
+        if (host instanceof StandardHost) {
+            unpackWARs = ((StandardHost) host).isUnpackWARs() &&
+                    ((StandardContext) context).getUnpackWAR() &&
+                    (docBase.startsWith(canonicalAppBase.getPath()));
+        }
+
+        if (docBase.toLowerCase(Locale.ENGLISH).endsWith(".war") && !file.isDirectory() && unpackWARs) {
+            URL war = new URL("jar:" + (new File(docBase)).toURI().toURL() + "!/");
+            docBase = ExpandWar.expand(host, war, pathName);
+            file = new File(docBase);
+            docBase = file.getCanonicalPath();
+            if (context instanceof StandardContext) {
+                ((StandardContext) context).setOriginalDocBase(origDocBase);
+            }
+        } else if (docBase.toLowerCase(Locale.ENGLISH).endsWith(".war") &&
+                !file.isDirectory() && !unpackWARs) {
+            URL war =
+                new URL("jar:" + (new File (docBase)).toURI().toURL() + "!/");
+            ExpandWar.validate(host, war, pathName);
+        } else {
+            File docDir = new File(docBase);
+            if (!docDir.exists()) {
+                File warFile = new File(docBase + ".war");
+                if (warFile.exists()) {
+                    URL war =
+                        new URL("jar:" + warFile.toURI().toURL() + "!/");
+                    if (unpackWARs) {
+                        docBase = ExpandWar.expand(host, war, pathName);
+                        file = new File(docBase);
+                        docBase = file.getCanonicalPath();
+                    } else {
+                        docBase = warFile.getCanonicalPath();
+                        ExpandWar.validate(host, war, pathName);
+                    }
+                }
+                if (context instanceof StandardContext) {
+                    ((StandardContext) context).setOriginalDocBase(origDocBase);
+                }
+            }
+        }
+
+        if (docBase.startsWith(canonicalAppBase.getPath() + File.separatorChar)) {
+            docBase = docBase.substring(canonicalAppBase.getPath().length());
+            docBase = docBase.replace(File.separatorChar, '/');
+            if (docBase.startsWith("/")) {
+                docBase = docBase.substring(1);
+            }
+        } else {
+            docBase = docBase.replace(File.separatorChar, '/');
+        }
+
+        context.setDocBase(docBase);
+
+    }
+    
+    
+    protected void antiLocking() {
+
+        if ((context instanceof StandardContext) 
+            && ((StandardContext) context).getAntiResourceLocking()) {
+            
+            Host host = (Host) context.getParent();
+            String appBase = host.getAppBase();
+            String docBase = context.getDocBase();
+            if (docBase == null)
+                return;
+            if (originalDocBase == null) {
+                originalDocBase = docBase;
+            } else {
+                docBase = originalDocBase;
+            }
+            File docBaseFile = new File(docBase);
+            if (!docBaseFile.isAbsolute()) {
+                File file = new File(appBase);
+                if (!file.isAbsolute()) {
+                    file = new File(System.getProperty(Globals.CATALINA_BASE_PROP), appBase);
+                }
+                docBaseFile = new File(file, docBase);
+            }
+            
+            String path = context.getPath();
+            if (path == null) {
+                return;
+            }
+            ContextName cn = new ContextName(path, context.getWebappVersion());
+            docBase = cn.getBaseName();
+
+            File file = null;
+            if (docBase.toLowerCase(Locale.ENGLISH).endsWith(".war")) {
+                // TODO - This is never executed. Bug or code to delete?
+                file = new File(System.getProperty("java.io.tmpdir"),
+                        deploymentCount++ + "-" + docBase + ".war");
+            } else {
+                file = new File(System.getProperty("java.io.tmpdir"), 
+                        deploymentCount++ + "-" + docBase);
+            }
+            
+            if (log.isDebugEnabled())
+                log.debug("Anti locking context[" + context.getName() 
+                        + "] setting docBase to " + file);
+            
+            // Cleanup just in case an old deployment is lying around
+            ExpandWar.delete(file);
+            if (ExpandWar.copy(docBaseFile, file)) {
+                context.setDocBase(file.getAbsolutePath());
+            }
+            
+        }
+        
+    }
+    
+
+    /**
+     * Process a "init" event for this Context.
+     */
+    protected void init() {
+        // Called from StandardContext.init()
+
+        if (contextDigester == null){
+            contextDigester = createContextDigester();
+            contextDigester.getParser();
+        }
+
+        if (log.isDebugEnabled())
+            log.debug(sm.getString("contextConfig.init"));
+        context.setConfigured(false);
+        ok = true;
+        
+        contextConfig();
+        
+        try {
+            fixDocBase();
+        } catch (IOException e) {
+            log.error(sm.getString(
+                    "contextConfig.fixDocBase", context.getName()), e);
+        }
+        
+    }
+    
+    
+    /**
+     * Process a "before start" event for this Context.
+     */
+    protected synchronized void beforeStart() {
+        
+        antiLocking();
+
+    }
+    
+    
+    /**
+     * Process a "contextConfig" event for this Context.
+     */
+    protected synchronized void configureStart() {
+        // Called from StandardContext.start()
+
+        if (log.isDebugEnabled())
+            log.debug(sm.getString("contextConfig.start"));
+
+        if (log.isDebugEnabled()) {
+            log.debug(sm.getString("contextConfig.xmlSettings",
+                    context.getName(),
+                    Boolean.valueOf(context.getXmlValidation()),
+                    Boolean.valueOf(context.getXmlNamespaceAware())));
+        }
+        
+        createWebXmlDigester(context.getXmlNamespaceAware(), context.getXmlValidation());
+        
+        webConfig();
+
+        if (!context.getIgnoreAnnotations()) {
+            applicationAnnotationsConfig();
+        }
+        if (ok) {
+            validateSecurityRoles();
+        }
+
+        // Configure an authenticator if we need one
+        if (ok)
+            authenticatorConfig();
+
+        // Dump the contents of this pipeline if requested
+        if ((log.isDebugEnabled()) && (context instanceof ContainerBase)) {
+            log.debug("Pipeline Configuration:");
+            Pipeline pipeline = ((ContainerBase) context).getPipeline();
+            Valve valves[] = null;
+            if (pipeline != null)
+                valves = pipeline.getValves();
+            if (valves != null) {
+                for (int i = 0; i < valves.length; i++) {
+                    log.debug("  " + valves[i].getInfo());
+                }
+            }
+            log.debug("======================");
+        }
+
+        // Make our application available if no problems were encountered
+        if (ok)
+            context.setConfigured(true);
+        else {
+            log.error(sm.getString("contextConfig.unavailable"));
+            context.setConfigured(false);
+        }
+
+    }
+
+
+    /**
+     * Process a "stop" event for this Context.
+     */
+    protected synchronized void configureStop() {
+
+        if (log.isDebugEnabled())
+            log.debug(sm.getString("contextConfig.stop"));
+
+        int i;
+
+        // Removing children
+        Container[] children = context.findChildren();
+        for (i = 0; i < children.length; i++) {
+            context.removeChild(children[i]);
+        }
+
+        // Removing application parameters
+        /*
+        ApplicationParameter[] applicationParameters =
+            context.findApplicationParameters();
+        for (i = 0; i < applicationParameters.length; i++) {
+            context.removeApplicationParameter
+                (applicationParameters[i].getName());
+        }
+        */
+
+        // Removing security constraints
+        SecurityConstraint[] securityConstraints = context.findConstraints();
+        for (i = 0; i < securityConstraints.length; i++) {
+            context.removeConstraint(securityConstraints[i]);
+        }
+
+        // Removing Ejbs
+        /*
+        ContextEjb[] contextEjbs = context.findEjbs();
+        for (i = 0; i < contextEjbs.length; i++) {
+            context.removeEjb(contextEjbs[i].getName());
+        }
+        */
+
+        // Removing environments
+        /*
+        ContextEnvironment[] contextEnvironments = context.findEnvironments();
+        for (i = 0; i < contextEnvironments.length; i++) {
+            context.removeEnvironment(contextEnvironments[i].getName());
+        }
+        */
+
+        // Removing errors pages
+        ErrorPage[] errorPages = context.findErrorPages();
+        for (i = 0; i < errorPages.length; i++) {
+            context.removeErrorPage(errorPages[i]);
+        }
+
+        // Removing filter defs
+        FilterDef[] filterDefs = context.findFilterDefs();
+        for (i = 0; i < filterDefs.length; i++) {
+            context.removeFilterDef(filterDefs[i]);
+        }
+
+        // Removing filter maps
+        FilterMap[] filterMaps = context.findFilterMaps();
+        for (i = 0; i < filterMaps.length; i++) {
+            context.removeFilterMap(filterMaps[i]);
+        }
+
+        // Removing local ejbs
+        /*
+        ContextLocalEjb[] contextLocalEjbs = context.findLocalEjbs();
+        for (i = 0; i < contextLocalEjbs.length; i++) {
+            context.removeLocalEjb(contextLocalEjbs[i].getName());
+        }
+        */
+
+        // Removing Mime mappings
+        String[] mimeMappings = context.findMimeMappings();
+        for (i = 0; i < mimeMappings.length; i++) {
+            context.removeMimeMapping(mimeMappings[i]);
+        }
+
+        // Removing parameters
+        String[] parameters = context.findParameters();
+        for (i = 0; i < parameters.length; i++) {
+            context.removeParameter(parameters[i]);
+        }
+
+        // Removing resource env refs
+        /*
+        String[] resourceEnvRefs = context.findResourceEnvRefs();
+        for (i = 0; i < resourceEnvRefs.length; i++) {
+            context.removeResourceEnvRef(resourceEnvRefs[i]);
+        }
+        */
+
+        // Removing resource links
+        /*
+        ContextResourceLink[] contextResourceLinks =
+            context.findResourceLinks();
+        for (i = 0; i < contextResourceLinks.length; i++) {
+            context.removeResourceLink(contextResourceLinks[i].getName());
+        }
+        */
+
+        // Removing resources
+        /*
+        ContextResource[] contextResources = context.findResources();
+        for (i = 0; i < contextResources.length; i++) {
+            context.removeResource(contextResources[i].getName());
+        }
+        */
+
+        // Removing security role
+        String[] securityRoles = context.findSecurityRoles();
+        for (i = 0; i < securityRoles.length; i++) {
+            context.removeSecurityRole(securityRoles[i]);
+        }
+
+        // Removing servlet mappings
+        String[] servletMappings = context.findServletMappings();
+        for (i = 0; i < servletMappings.length; i++) {
+            context.removeServletMapping(servletMappings[i]);
+        }
+
+        // FIXME : Removing status pages
+
+        // Removing welcome files
+        String[] welcomeFiles = context.findWelcomeFiles();
+        for (i = 0; i < welcomeFiles.length; i++) {
+            context.removeWelcomeFile(welcomeFiles[i]);
+        }
+
+        // Removing wrapper lifecycles
+        String[] wrapperLifecycles = context.findWrapperLifecycles();
+        for (i = 0; i < wrapperLifecycles.length; i++) {
+            context.removeWrapperLifecycle(wrapperLifecycles[i]);
+        }
+
+        // Removing wrapper listeners
+        String[] wrapperListeners = context.findWrapperListeners();
+        for (i = 0; i < wrapperListeners.length; i++) {
+            context.removeWrapperListener(wrapperListeners[i]);
+        }
+
+        // Remove (partially) folders and files created by antiLocking
+        Host host = (Host) context.getParent();
+        String appBase = host.getAppBase();
+        String docBase = context.getDocBase();
+        if ((docBase != null) && (originalDocBase != null)) {
+            File docBaseFile = new File(docBase);
+            if (!docBaseFile.isAbsolute()) {
+                docBaseFile = new File(appBase, docBase);
+            }
+            // No need to log failure - it is expected in this case
+            ExpandWar.delete(docBaseFile, false);
+        }
+        
+        // Reset ServletContextInitializer scanning
+        initializerClassMap.clear();
+        typeInitializerMap.clear();
+        
+        ok = true;
+
+    }
+    
+    
+    /**
+     * Process a "destroy" event for this Context.
+     */
+    protected synchronized void destroy() {
+        // Called from StandardContext.destroy()
+        if (log.isDebugEnabled())
+            log.debug(sm.getString("contextConfig.destroy"));
+
+        // Changed to getWorkPath per Bugzilla 35819.
+        String workDir = ((StandardContext) context).getWorkPath();
+        if (workDir != null)
+            ExpandWar.delete(new File(workDir));
+    }
+    
+    
+    /**
+     * Validate the usage of security role names in the web application
+     * deployment descriptor.  If any problems are found, issue warning
+     * messages (for backwards compatibility) and add the missing roles.
+     * (To make these problems fatal instead, simply set the <code>ok</code>
+     * instance variable to <code>false</code> as well).
+     */
+    protected void validateSecurityRoles() {
+
+        // Check role names used in <security-constraint> elements
+        SecurityConstraint constraints[] = context.findConstraints();
+        for (int i = 0; i < constraints.length; i++) {
+            String roles[] = constraints[i].findAuthRoles();
+            for (int j = 0; j < roles.length; j++) {
+                if (!"*".equals(roles[j]) &&
+                    !context.findSecurityRole(roles[j])) {
+                    log.info(sm.getString("contextConfig.role.auth", roles[j]));
+                    context.addSecurityRole(roles[j]);
+                }
+            }
+        }
+
+        // Check role names used in <servlet> elements
+        Container wrappers[] = context.findChildren();
+        for (int i = 0; i < wrappers.length; i++) {
+            Wrapper wrapper = (Wrapper) wrappers[i];
+            String runAs = wrapper.getRunAs();
+            if ((runAs != null) && !context.findSecurityRole(runAs)) {
+                log.info(sm.getString("contextConfig.role.runas", runAs));
+                context.addSecurityRole(runAs);
+            }
+            String names[] = wrapper.findSecurityReferences();
+            for (int j = 0; j < names.length; j++) {
+                String link = wrapper.findSecurityReference(names[j]);
+                if ((link != null) && !context.findSecurityRole(link)) {
+                    log.info(sm.getString("contextConfig.role.link", link));
+                    context.addSecurityRole(link);
+                }
+            }
+        }
+
+    }
+
+
+    /**
+     * Get config base.
+     */
+    protected File getConfigBase() {
+        File configBase = 
+            new File(System.getProperty(Globals.CATALINA_BASE_PROP), "conf");
+        if (!configBase.exists()) {
+            return null;
+        }
+        return configBase;
+    }  
+
+    
+    protected String getHostConfigPath(String resourceName) {
+        StringBuilder result = new StringBuilder();
+        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) {
+            result.append(engine.getName()).append('/');
+        }
+        if (host != null) {
+            result.append(host.getName()).append('/');
+        }
+        result.append(resourceName);
+        return result.toString();
+    }
+
+
+    /**
+     * Scan the web.xml files that apply to the web application and merge them
+     * using the rules defined in the spec. For the global web.xml files,
+     * where there is duplicate configuration, the most specific level wins. ie
+     * an application's web.xml takes precedence over the host level or global
+     * web.xml file.
+     */
+    protected void webConfig() {
+        WebXml webXml = createWebXml();
+
+        // Parse global web.xml if present
+        InputSource globalWebXml = getGlobalWebXmlSource();
+        if (globalWebXml == null) {
+            // This is unusual enough to log
+            log.info(sm.getString("contextConfig.defaultMissing"));
+        } else {
+            parseWebXml(globalWebXml, webXml, false);
+        }
+
+        // Parse host level web.xml if present
+        // Additive apart from welcome pages
+        webXml.setReplaceWelcomeFiles(true);
+        InputSource hostWebXml = getHostWebXmlSource();
+        parseWebXml(hostWebXml, webXml, false);
+        
+        // Parse context level web.xml
+        webXml.setReplaceWelcomeFiles(true);
+        InputSource contextWebXml = getContextWebXmlSource();
+        parseWebXml(contextWebXml, webXml, false);
+        
+        // Assuming 0 is safe for what is required in this case
+        double webXmlVersion = 0;
+        if (webXml.getVersion() != null) {
+            webXmlVersion = Double.parseDouble(webXml.getVersion());
+        }
+        
+        if (webXmlVersion >= 3) {
+            // Ordering is important here
+
+            // Step 1. Identify all the JARs packaged with the application
+            // If the JARs have a web-fragment.xml it will be parsed at this
+            // point.
+            Map<String,WebXml> fragments = processJarsForWebFragments();
+
+            // Only need to process fragments and annotations if metadata is
+            // not complete
+            Set<WebXml> orderedFragments = null;
+            if  (!webXml.isMetadataComplete()) {
+                // Step 2. Order the fragments.
+                orderedFragments = WebXml.orderWebFragments(webXml, fragments);
+    
+                // Step 3. Look for ServletContainerInitializer implementations
+                if (ok) {
+                    processServletContainerInitializers(orderedFragments);
+                }
+    
+                // Step 4. Process /WEB-INF/classes for annotations
+                // This will add any matching classes to the typeInitializerMap
+                if (ok) {
+                    URL webinfClasses;
+                    try {
+                        webinfClasses = context.getServletContext().getResource(
+                                "/WEB-INF/classes");
+                        processAnnotationsUrl(webinfClasses, webXml);
+                    } catch (MalformedURLException e) {
+                        log.error(sm.getString(
+                                "contextConfig.webinfClassesUrl"), e);
+                    }
+                }
+    
+                // Step 5. Process JARs for annotations - only need to process
+                // those fragments we are going to use
+                // This will add any matching classes to the typeInitializerMap
+                if (ok) {
+                    processAnnotations(orderedFragments);
+                }
+    
+                // Step 6. Merge web-fragment.xml files into the main web.xml
+                // file.
+                if (ok) {
+                    ok = webXml.merge(orderedFragments);
+                }
+    
+                // Step 6.5 Convert explicitly mentioned jsps to servlets
+                if (!false) {
+                    convertJsps(webXml);
+                }
+    
+                // Step 7. Apply merged web.xml to Context
+                if (ok) {
+                    webXml.configureContext(context);
+    
+                    // Step 7a. Make the merged web.xml available to other
+                    // components, specifically Jasper, to save those components
+                    // from having to re-generate it.
+                    // TODO Use a ServletContainerInitializer for Jasper
+                    String mergedWebXml = webXml.toXml();
+                    context.getServletContext().setAttribute(
+                           org.apache.tomcat.util.scan.Constants.MERGED_WEB_XML,
+                            mergedWebXml);
+                    if (context.getLogEffectiveWebXml()) {
+                        log.info("web.xml:\n" + mergedWebXml);
+                    }
+                }
+            } else {
+                webXml.configureContext(context);
+            }
+            
+            // Always need to look for static resources
+            // Step 8. Look for static resources packaged in JARs
+            if (ok) {
+                // Spec does not define an order.
+                // Use ordered JARs followed by remaining JARs
+                Set<WebXml> resourceJars = new LinkedHashSet<WebXml>();
+                if (orderedFragments != null) {
+                    for (WebXml fragment : orderedFragments) {
+                        resourceJars.add(fragment);
+                    }
+                }
+                for (WebXml fragment : fragments.values()) {
+                    if (!resourceJars.contains(fragment)) {
+                        resourceJars.add(fragment);
+                    }
+                }
+                processResourceJARs(resourceJars);
+                // See also StandardContext.resourcesStart() for
+                // WEB-INF/classes/META-INF/resources configuration
+            }
+            
+            // Only look for ServletContainerInitializer if metadata is not
+            // complete
+            if (!webXml.isMetadataComplete()) {
+                // Step 9. Apply the ServletContainerInitializer config to the
+                // context
+                if (ok) {
+                    for (Map.Entry<ServletContainerInitializer,
+                            Set<Class<?>>> entry : 
+                                initializerClassMap.entrySet()) {
+                        if (entry.getValue().isEmpty()) {
+                            context.addServletContainerInitializer(
+                                    entry.getKey(), null);
+                        } else {
+                            context.addServletContainerInitializer(
+                                    entry.getKey(), entry.getValue());
+                        }
+                    }
+                }
+            }
+        } else {
+            // Apply unmerged web.xml to Context
+            convertJsps(webXml);
+            webXml.configureContext(context);
+        }
+    }
+
+    private void convertJsps(WebXml webXml) {
+        ServletDef jspServlet = webXml.getServlets().get("jsp");
+        for (ServletDef servletDef: webXml.getServlets().values()) {
+            if (servletDef.getJspFile() != null) {
+                convertJsp(servletDef, jspServlet);
+            }
+        }
+    }
+
+    private void convertJsp(ServletDef servletDef, ServletDef jspServletDef) {
+        servletDef.setServletClass(org.apache.catalina.core.Constants.JSP_SERVLET_CLASS);
+        String jspFile = servletDef.getJspFile();
+        if ((jspFile != null) && !jspFile.startsWith("/")) {
+            if (context.isServlet22()) {
+                if(log.isDebugEnabled())
+                    log.debug(sm.getString("contextConfig.jspFile.warning",
+                                       jspFile));
+                jspFile = "/" + jspFile;
+            } else {
+                throw new IllegalArgumentException
+                    (sm.getString("contextConfig.jspFile.error", jspFile));
+            }
+        }
+        servletDef.getParameterMap().put("jspFile", jspFile);
+        servletDef.setJspFile(null);
+        for (Map.Entry<String, String> initParam: jspServletDef.getParameterMap().entrySet()) {
+            servletDef.addInitParameter(initParam.getKey(), initParam.getValue());
+        }
+    }
+
+    protected WebXml createWebXml() {
+        return new WebXml();
+    }
+
+    /**
+     * Scan JARs for ServletContainerInitializer implementations.
+     * Implementations will be added in web-fragment.xml priority order.
+     */
+    protected void processServletContainerInitializers(
+            Set<WebXml> fragments) {
+        
+        for (WebXml fragment : fragments) {
+            URL url = fragment.getURL();
+            JarFile jarFile = null;
+            InputStream is = null;
+            ServletContainerInitializer sci = null;
+            try {
+                if ("jar".equals(url.getProtocol())) {
+                    JarURLConnection conn =
+                        (JarURLConnection) url.openConnection();
+                    jarFile = conn.getJarFile();
+                    ZipEntry entry = jarFile.getEntry(SCI_LOCATION);
+                    if (entry != null) {
+                        is = jarFile.getInputStream(entry);
+                    }
+                } else if ("file".equals(url.getProtocol())) {
+                    String path = url.getPath();
+                    File file = new File(path, SCI_LOCATION);
+                    if (file.exists()) {
+                        is = new FileInputStream(file);
+                    }
+                }
+                if (is != null) {
+                    sci = getServletContainerInitializer(is);
+                }
+            } catch (IOException ioe) {
+                log.error(sm.getString(
+                        "contextConfig.servletContainerInitializerFail", url,
+                        context.getName()));
+                ok = false;
+                return;
+            } finally {
+                if (is != null) {
+                    try {
+                        is.close();
+                    } catch (IOException e) {
+                        // Ignore
+                    }
+                }
+                if (jarFile != null) {
+                    try {
+                        jarFile.close();
+                    } catch (IOException e) {
+                        // Ignore
+                    }
+                }
+            }
+            
+            if (sci == null) {
+                continue;
+            }
+
+            initializerClassMap.put(sci, new HashSet<Class<?>>());
+            
+            HandlesTypes ht =
+                sci.getClass().getAnnotation(HandlesTypes.class);
+            if (ht != null) {
+                Class<?>[] types = ht.value();
+                if (types != null) {
+                    for (Class<?> type : types) {
+                        Set<ServletContainerInitializer> scis =
+                            typeInitializerMap.get(type);
+                        if (scis == null) {
+                            scis = new HashSet<ServletContainerInitializer>();
+                            typeInitializerMap.put(type, scis);
+                        }
+                        scis.add(sci);
+                    }
+                }
+            }
+
+        }
+    }
+    
+    
+    /**
+     * Extract the name of the ServletContainerInitializer.
+     * 
+     * @param is    The resource where the name is defined 
+     * @return      The class name
+     * @throws IOException
+     */
+    protected ServletContainerInitializer getServletContainerInitializer(
+            InputStream is) throws IOException {
+
+        String className = null;
+        
+        if (is != null) {
+            String line = null;
+            try {
+                BufferedReader br =
+                    new BufferedReader(new InputStreamReader(is, "UTF-8"));
+                line = br.readLine();
+                if (line != null && line.trim().length() > 0) {
+                    className = line.trim();
+                }
+            } catch (UnsupportedEncodingException e) {
+                // Should never happen with UTF-8
+                // If it does - ignore & return null
+            }
+        }
+        
+        ServletContainerInitializer sci = null;
+        try {
+            Class<?> clazz = Class.forName(className,true,
+                    context.getLoader().getClassLoader());
+             sci = (ServletContainerInitializer) clazz.newInstance();
+        } catch (ClassNotFoundException e) {
+            log.error(sm.getString("contextConfig.invalidSci", className), e);
+            throw new IOException(e);
+        } catch (InstantiationException e) {
+            log.error(sm.getString("contextConfig.invalidSci", className), e);
+            throw new IOException(e);
+        } catch (IllegalAccessException e) {
+            log.error(sm.getString("contextConfig.invalidSci", className), e);
+            throw new IOException(e);
+        }
+        
+        return sci;
+    }
+
+    
+    /**
+     * Scan JARs that contain web-fragment.xml files that will be used to
+     * configure this application to see if they also contain static resources.
+     * If static resources are found, add them to the context. Resources are
+     * added in web-fragment.xml priority order.
+     */
+    protected void processResourceJARs(Set<WebXml> fragments) {
+        for (WebXml fragment : fragments) {
+            URL url = fragment.getURL();
+            JarFile jarFile = null;
+            try {
+                // Note: Ignore file URLs for now since only jar URLs will be accepted
+                if ("jar".equals(url.getProtocol())) {
+                    JarURLConnection conn =
+                        (JarURLConnection) url.openConnection();
+                    jarFile = conn.getJarFile();   
+                    ZipEntry entry = jarFile.getEntry("META-INF/resources/");
+                    if (entry != null) {
+                        context.addResourceJarUrl(url);
+                    }
+                }
+            } catch (IOException ioe) {
+                log.error(sm.getString("contextConfig.resourceJarFail", url,
+                        context.getName()));
+            } finally {
+                if (jarFile != null) {
+                    try {
+                        jarFile.close();
+                    } catch (IOException e) {
+                        // Ignore
+                    }
+                }
+            }
+        }
+    }
+    
+    
+    /**
+     * Identify the default web.xml to be used and obtain an input source for
+     * it.
+     */
+    protected InputSource getGlobalWebXmlSource() {
+        // Is a default web.xml specified for the Context?
+        if (defaultWebXml == null && context instanceof StandardContext) {
+            defaultWebXml = ((StandardContext) context).getDefaultWebXml();
+        }
+        // Set the default if we don't have any overrides
+        if (defaultWebXml == null) getDefaultWebXml();
+
+        return getWebXmlSource(defaultWebXml, getBaseDir());
+    }
+    
+    /**
+     * Identify the host web.xml to be used and obtain an input source for
+     * it.
+     */
+    protected InputSource getHostWebXmlSource() {
+        String resourceName = getHostConfigPath(Constants.HostWebXml);
+        
+        // In an embedded environment, configBase might not be set
+        File configBase = getConfigBase();
+        if (configBase == null)
+            return null;
+        
+        String basePath = null;
+        try {
+            basePath = configBase.getCanonicalPath();
+        } catch (IOException e) {
+            log.error(sm.getString("contectConfig.baseError"), e);
+            return null;
+        }
+
+        return getWebXmlSource(resourceName, basePath);
+    }
+    
+    /**
+     * Identify the application web.xml to be used and obtain an input source
+     * for it.
+     */
+    protected InputSource getContextWebXmlSource() {
+        InputStream stream = null;
+        InputSource source = null;
+        URL url = null;
+        
+        String altDDName = null;
+
+        // Open the application web.xml file, if it exists
+        ServletContext servletContext = context.getServletContext();
+        if (servletContext != null) {
+            altDDName = (String)servletContext.getAttribute(
+                                                        Globals.ALT_DD_ATTR);
+            if (altDDName != null) {
+                try {
+                    stream = new FileInputStream(altDDName);
+                    url = new File(altDDName).toURI().toURL();
+                } catch (FileNotFoundException e) {
+                    log.error(sm.getString("contextConfig.altDDNotFound",
+                                           altDDName));
+                } catch (MalformedURLException e) {
+                    log.error(sm.getString("contextConfig.applicationUrl"));
+                }
+            }
+            else {
+                stream = servletContext.getResourceAsStream
+                    (Constants.ApplicationWebXml);
+                try {
+                    url = servletContext.getResource(
+                            Constants.ApplicationWebXml);
+                } catch (MalformedURLException e) {
+                    log.error(sm.getString("contextConfig.applicationUrl"));
+                }
+            }
+        }
+        if (stream == null || url == null) {
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString("contextConfig.applicationMissing") + " " + context);
+            }
+        } else {
+            source = new InputSource(url.toExternalForm());
+            source.setByteStream(stream);
+        }
+        
+        return source;
+    }
+    
+    /**
+     * 
+     * @param filename  Name of the file (possibly with one or more leading path
+     *                  segments) to read
+     * @param path      Location that filename is relative to 
+     */
+    protected InputSource getWebXmlSource(String filename, String path) {
+        File file = new File(filename);
+        if (!file.isAbsolute()) {
+            file = new File(path, filename);
+        }
+
+        InputStream stream = null;
+        InputSource source = null;
+
+        try {
+            if (!file.exists()) {
+                // Use getResource and getResourceAsStream
+                stream =
+                    getClass().getClassLoader().getResourceAsStream(filename);
+                if(stream != null) {
+                    source =
+                        new InputSource(getClass().getClassLoader().getResource(
+                                filename).toString());
+                } 
+            } else {
+                source = new InputSource("file://" + file.getAbsolutePath());
+                stream = new FileInputStream(file);
+                context.addWatchedResource(file.getAbsolutePath());
+            }
+
+            if (stream != null && source != null) {
+                source.setByteStream(stream);
+            }
+        } catch (Exception e) {
+            log.error(sm.getString(
+                    "contextConfig.defaultError", filename, file), e);
+        }
+
+        return source;
+    }
+
+
+    protected void parseWebXml(InputSource source, WebXml dest,
+            boolean fragment) {
+        
+        if (source == null) return;
+
+        XmlErrorHandler handler = new XmlErrorHandler();
+
+        // Web digesters and rulesets are shared between contexts but are not
+        // thread safe. Whilst there should only be one thread at a time
+        // processing a config, play safe and sync.
+        Digester digester;
+        if (fragment) {
+            digester = webFragmentDigester;
+        } else {
+            digester = webDigester;
+        }
+        
+        synchronized(digester) {
+            
+            digester.push(dest);
+            digester.setErrorHandler(handler);
+            
+            if(log.isDebugEnabled()) {
+                log.debug(sm.getString("contextConfig.applicationStart",
+                        source.getSystemId()));
+            }
+
+            try {
+                digester.parse(source);
+
+                if (handler.getWarnings().size() > 0 ||
+                        handler.getErrors().size() > 0) {
+                    ok = false;
+                    handler.logFindings(log, source.getSystemId());
+                }
+            } catch (SAXParseException e) {
+                log.error(sm.getString("contextConfig.applicationParse",
+                        source.getSystemId()), e);
+                log.error(sm.getString("contextConfig.applicationPosition",
+                                 "" + e.getLineNumber(),
+                                 "" + e.getColumnNumber()));
+                ok = false;
+            } catch (Exception e) {
+                log.error(sm.getString("contextConfig.applicationParse",
+                        source.getSystemId()), e);
+                ok = false;
+            } finally {
+                digester.reset();
+                if (fragment) {
+                    webFragmentRuleSet.recycle();
+                } else {
+                    webRuleSet.recycle();
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Scan /META-INF/lib for JARs and for each one found add it and any
+     * /META-INF/web-fragment.xml to the resulting Map. web-fragment.xml files
+     * will be parsed before being added to the map. Every JAR will be added and
+     * <code>null</code> will be used if no web-fragment.xml was found. Any JARs
+     * known not contain fragments will be skipped.
+     * 
+     * @return A map of JAR name to processed web fragment (if any)
+     */
+    protected Map<String,WebXml> processJarsForWebFragments() {
+        
+        JarScanner jarScanner = context.getJarScanner();
+        FragmentJarScannerCallback callback = new FragmentJarScannerCallback();
+        
+        jarScanner.scan(context.getServletContext(),
+                context.getLoader().getClassLoader(), callback, null);
+        
+        return callback.getFragments();
+    }
+
+    protected void processAnnotations(Set<WebXml> fragments) {
+        for(WebXml fragment : fragments) {
+            if (!fragment.isMetadataComplete()) {
+                WebXml annotations = new WebXml();
+                // no impact on distributable
+                annotations.setDistributable(true);
+                URL url = fragment.getURL();
+                processAnnotationsUrl(url, annotations);
+                Set<WebXml> set = new HashSet<WebXml>();
+                set.add(annotations);
+                // Merge annotations into fragment - fragment takes priority
+                fragment.merge(set);
+            }
+        }
+    }
+
+    protected void processAnnotationsUrl(URL url, WebXml fragment) {
+        if (url == null) {
+            // Nothing to do.
+            return;
+        } else if ("jar".equals(url.getProtocol())) {
+            processAnnotationsJar(url, fragment);
+        } else if ("jndi".equals(url.getProtocol())) {
+            processAnnotationsJndi(url, fragment);
+        } else if ("file".equals(url.getProtocol())) {
+            try {
+                processAnnotationsFile(new File(url.toURI()), fragment);
+            } catch (URISyntaxException e) {
+                log.error(sm.getString("contextConfig.fileUrl", url), e);
+            }
+        } else {
+            log.error(sm.getString("contextConfig.unknownUrlProtocol",
+                    url.getProtocol(), url));
+        }
+        
+    }
+
+
+    protected void processAnnotationsJar(URL url, WebXml fragment) {
+        JarFile jarFile = null;
+        
+        try {
+            URLConnection urlConn = url.openConnection();
+            JarURLConnection jarUrlConn;
+            if (!(urlConn instanceof JarURLConnection)) {
+                // This should never happen
+                sm.getString("contextConfig.jarUrl", url);
+                return;
+            }
+            
+            jarUrlConn = (JarURLConnection) urlConn;
+            jarUrlConn.setUseCaches(false);
+            jarFile = jarUrlConn.getJarFile();
+            
+            Enumeration<JarEntry> jarEntries = jarFile.entries();
+            while (jarEntries.hasMoreElements()) {
+                JarEntry jarEntry = jarEntries.nextElement();
+                String entryName = jarEntry.getName();
+                if (entryName.endsWith(".class")) {
+                    InputStream is = null;
+                    try {
+                        is = jarFile.getInputStream(jarEntry);
+                        processAnnotationsStream(is, fragment);
+                    } catch (IOException e) {
+                        log.error(sm.getString("contextConfig.inputStreamJar",
+                                entryName, url),e);
+                    } finally {
+                        if (is != null) {
+                            try {
+                                is.close();
+                            } catch (Throwable t) {
+                                ExceptionUtils.handleThrowable(t);
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (IOException e) {
+            log.error(sm.getString("contextConfig.jarFile", url), e);
+        } finally {
+            if (jarFile != null) {
+                try {
+                    jarFile.close();
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                }
+            }
+        }
+    }
+
+    
+    protected void processAnnotationsJndi(URL url, WebXml fragment) {
+        try {
+            URLConnection urlConn = url.openConnection();
+            DirContextURLConnection dcUrlConn;
+            if (!(urlConn instanceof DirContextURLConnection)) {
+                // This should never happen
+                sm.getString("contextConfig.jndiUrlNotDirContextConn", url);
+                return;
+            }
+            
+            dcUrlConn = (DirContextURLConnection) urlConn;
+            dcUrlConn.setUseCaches(false);
+            
+            String type = dcUrlConn.getHeaderField(ResourceAttributes.TYPE);
+            if (ResourceAttributes.COLLECTION_TYPE.equals(type)) {
+                // Collection
+                Enumeration<String> dirs = dcUrlConn.list();
+                while (dirs.hasMoreElements()) {
+                    String dir = dirs.nextElement();
+                    URL dirUrl = new URL(url.toString() + '/' + dir);
+                    processAnnotationsJndi(dirUrl, fragment);
+                }
+                
+            } else {
+                // Single file
+                if (url.getPath().endsWith(".class")) {
+                    InputStream is = null;
+                    try {
+                        is = dcUrlConn.getInputStream();
+                        processAnnotationsStream(is, fragment);
+                    } catch (IOException e) {
+                        log.error(sm.getString("contextConfig.inputStreamJndi",
+                                url),e);
+                    } finally {
+                        if (is != null) {
+                            try {
+                                is.close();
+                            } catch (Throwable t) {
+                                ExceptionUtils.handleThrowable(t);
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (IOException e) {
+            log.error(sm.getString("contextConfig.jndiUrl", url), e);
+        }
+    }
+    
+    
+    protected void processAnnotationsFile(File file, WebXml fragment) {
+        
+        if (file.isDirectory()) {
+            String[] dirs = file.list();
+            for (String dir : dirs) {
+                processAnnotationsFile(new File(file,dir), fragment);
+            }
+        } else if (file.canRead() && file.getName().endsWith(".class")) {
+            FileInputStream fis = null;
+            try {
+                fis = new FileInputStream(file);
+                processAnnotationsStream(fis, fragment);
+            } catch (IOException e) {
+                log.error(sm.getString("contextConfig.inputStreamFile",
+                        file.getAbsolutePath()),e);
+            } finally {
+                if (fis != null) {
+                    try {
+                        fis.close();
+                    } catch (Throwable t) {
+                        ExceptionUtils.handleThrowable(t);
+                    }
+                }
+            }
+        }
+    }
+
+
+    protected void processAnnotationsStream(InputStream is, WebXml fragment)
+            throws ClassFormatException, IOException {
+        
+        ClassParser parser = new ClassParser(is, null);
+        JavaClass clazz = parser.parse();
+        
+        checkHandlesTypes(clazz);
+        
+        String className = clazz.getClassName();
+        
+        AnnotationEntry[] annotationsEntries = clazz.getAnnotationEntries();
+
+        for (AnnotationEntry ae : annotationsEntries) {
+            String type = ae.getAnnotationType();
+            if ("Ljavax/servlet/annotation/WebServlet;".equals(type)) {
+                processAnnotationWebServlet(className, ae, fragment);
+            }else if ("Ljavax/servlet/annotation/WebFilter;".equals(type)) {
+                processAnnotationWebFilter(className, ae, fragment);
+            }else if ("Ljavax/servlet/annotation/WebListener;".equals(type)) {
+                fragment.addListener(className);
+            } else {
+                // Unknown annotation - ignore
+            }
+        }
+    }
+
+    /**
+     * For classes packaged with the web application, the class and each
+     * super class needs to be checked for a match with {@link HandlesTypes} or
+     * for an annotation that matches {@link HandlesTypes}.
+     * @param javaClass
+     */
+    protected void checkHandlesTypes(JavaClass javaClass) {
+        
+        // Skip this if we can
+        if (typeInitializerMap.size() == 0)
+            return;
+        
+        // No choice but to load the class
+        String className = javaClass.getClassName();
+        
+        Class<?> clazz = null;
+        try {
+            clazz = context.getLoader().getClassLoader().loadClass(className);
+        } catch (NoClassDefFoundError e) {
+            log.debug(sm.getString("contextConfig.invalidSciHandlesTypes",
+                    className), e);
+            return;
+        } catch (ClassNotFoundException e) {
+            log.warn(sm.getString("contextConfig.invalidSciHandlesTypes",
+                    className), e);
+            return;
+        } catch (ClassFormatError e) {
+            log.warn(sm.getString("contextConfig.invalidSciHandlesTypes",
+                    className), e);
+            return;
+        }
+
+        if (clazz.isAnnotation()) {
+            // Skip
+            return;
+        }
+        
+        boolean match = false;
+        
+        for (Map.Entry<Class<?>, Set<ServletContainerInitializer>> entry :
+                typeInitializerMap.entrySet()) {
+            if (entry.getKey().isAnnotation()) {
+                AnnotationEntry[] annotationEntries = javaClass.getAnnotationEntries();
+                for (AnnotationEntry annotationEntry : annotationEntries) {
+                    if (entry.getKey().getName().equals(
+                        getClassName(annotationEntry.getAnnotationType()))) {
+                        match = true;
+                        break;
+                    }
+                }
+            } else if (entry.getKey().isAssignableFrom(clazz)) {
+                match = true;
+            }
+            if (match) {
+                for (ServletContainerInitializer sci : entry.getValue()) {
+                    initializerClassMap.get(sci).add(clazz);
+                }
+            }
+        }
+    }
+
+    private static final String getClassName(String internalForm) {
+        if (!internalForm.startsWith("L")) {
+            return internalForm;
+        }
+        
+        // Assume starts with L, ends with ; and uses / rather than .
+        return internalForm.substring(1,
+                internalForm.length() - 1).replace('/', '.');
+    }
+
+    protected void processAnnotationWebServlet(String className,
+            AnnotationEntry ae, WebXml fragment) {
+        String servletName = null;
+        // must search for name s. Spec Servlet API 3.0 - 8.2.3.3.n.ii page 81
+        ElementValuePair[] evps = ae.getElementValuePairs();
+        for (ElementValuePair evp : evps) {
+            String name = evp.getNameString();
+            if ("name".equals(name)) {
+                servletName = evp.getValue().stringifyValue();
+                break;
+            }
+        }
+        if (servletName == null) {
+            // classname is default servletName as annotation has no name!
+            servletName = className;
+        }
+        ServletDef servletDef = fragment.getServlets().get(servletName);
+        
+        boolean isWebXMLservletDef;
+        if (servletDef == null) {
+            servletDef = new ServletDef();
+            servletDef.setServletName(servletName);
+            servletDef.setServletClass(className);
+            isWebXMLservletDef = false;
+        } else {
+            isWebXMLservletDef = true;
+        }
+
+        boolean urlPatternsSet = false;
+        String[] urlPatterns = null;
+
+        // ElementValuePair[] evps = ae.getElementValuePairs();
+        for (ElementValuePair evp : evps) {
+            String name = evp.getNameString();
+            if ("value".equals(name) || "urlPatterns".equals(name)) {
+                if (urlPatternsSet) {
+                    throw new IllegalArgumentException(sm.getString(
+                            "contextConfig.urlPatternValue", className));
+                }
+                urlPatternsSet = true;
+                urlPatterns = processAnnotationsStringArray(evp.getValue());
+            } else if ("description".equals(name)) {
+                if (servletDef.getDescription() == null) {
+                    servletDef.setDescription(evp.getValue().stringifyValue());
+                }
+            } else if ("displayName".equals(name)) {
+                if (servletDef.getDisplayName() == null) {
+                    servletDef.setDisplayName(evp.getValue().stringifyValue());
+                }
+            } else if ("largeIcon".equals(name)) {
+                if (servletDef.getLargeIcon() == null) {
+                    servletDef.setLargeIcon(evp.getValue().stringifyValue());
+                }
+            } else if ("smallIcon".equals(name)) {
+                if (servletDef.getSmallIcon() == null) {
+                    servletDef.setSmallIcon(evp.getValue().stringifyValue());
+                }
+            } else if ("asyncSupported".equals(name)) {
+                if (servletDef.getAsyncSupported() == null) {
+                    servletDef.setAsyncSupported(evp.getValue()
+                            .stringifyValue());
+                }
+            } else if ("loadOnStartup".equals(name)) {
+                if (servletDef.getLoadOnStartup() == null) {
+                    servletDef
+                            .setLoadOnStartup(evp.getValue().stringifyValue());
+                }
+            } else if ("initParams".equals(name)) {
+                Map<String, String> initParams = processAnnotationWebInitParams(evp
+                        .getValue());
+                if (isWebXMLservletDef) {
+                    Map<String, String> webXMLInitParams = servletDef
+                            .getParameterMap();
+                    for (Map.Entry<String, String> entry : initParams
+                            .entrySet()) {
+                        if (webXMLInitParams.get(entry.getKey()) == null) {
+                            servletDef.addInitParameter(entry.getKey(), entry
+                                    .getValue());
+                        }
+                    }
+                } else {
+                    for (Map.Entry<String, String> entry : initParams
+                            .entrySet()) {
+                        servletDef.addInitParameter(entry.getKey(), entry
+                                .getValue());
+                    }
+                }
+            }
+        }
+        if (!isWebXMLservletDef && urlPatterns != null) {
+            fragment.addServlet(servletDef);
+        }
+        if (urlPatterns != null) {
+            if (!fragment.getServletMappings().containsValue(servletName)) {
+                for (String urlPattern : urlPatterns) {
+                    fragment.addServletMapping(urlPattern, servletName);
+                }
+            }
+        }
+
+    }
+
+    /**
+     * process filter annotation and merge with existing one!
+     * FIXME: refactoring method to long and has redundant subroutines with processAnnotationWebServlet!
+     * @param className
+     * @param ae
+     * @param fragment
+     */
+    protected void processAnnotationWebFilter(String className,
+            AnnotationEntry ae, WebXml fragment) {
+        String filterName = null;
+        // must search for name s. Spec Servlet API 3.0 - 8.2.3.3.n.ii page 81
+        ElementValuePair[] evps = ae.getElementValuePairs();
+        for (ElementValuePair evp : evps) {
+            String name = evp.getNameString();
+            if ("filterName".equals(name)) {
+                filterName = evp.getValue().stringifyValue();
+                break;
+            }
+        }
+        if (filterName == null) {
+            // classname is default filterName as annotation has no name!
+            filterName = className;
+        }
+        FilterDef filterDef = fragment.getFilters().get(filterName);
+        FilterMap filterMap = new FilterMap();
+
+        boolean isWebXMLfilterDef;
+        if (filterDef == null) {
+            filterDef = new FilterDef();
+            filterDef.setFilterName(filterName);
+            filterDef.setFilterClass(className);
+            isWebXMLfilterDef = false;
+        } else {
+            isWebXMLfilterDef = true;
+        }
+
+        boolean urlPatternsSet = false;
+        boolean dispatchTypesSet = false;
+        String[] urlPatterns = null;
+
+        for (ElementValuePair evp : evps) {
+            String name = evp.getNameString();
+            if ("value".equals(name) || "urlPatterns".equals(name)) {
+                if (urlPatternsSet) {
+                    throw new IllegalArgumentException(sm.getString(
+                            "contextConfig.urlPatternValue", className));
+                }
+                urlPatterns = processAnnotationsStringArray(evp.getValue());
+                urlPatternsSet = urlPatterns.length > 0;
+                for (String urlPattern : urlPatterns) {
+                    filterMap.addURLPattern(urlPattern);
+                }
+            } else if ("servletNames".equals(name)) {
+                String[] servletNames = processAnnotationsStringArray(evp
+                        .getValue());
+                for (String servletName : servletNames) {
+                    filterMap.addServletName(servletName);
+                }
+            } else if ("dispatcherTypes".equals(name)) {
+                String[] dispatcherTypes = processAnnotationsStringArray(evp
+                        .getValue());
+                dispatchTypesSet = dispatcherTypes.length > 0;
+                for (String dispatcherType : dispatcherTypes) {
+                    filterMap.setDispatcher(dispatcherType);
+                }
+            } else if ("description".equals(name)) {
+                if (filterDef.getDescription() == null) {
+                    filterDef.setDescription(evp.getValue().stringifyValue());
+                }
+            } else if ("displayName".equals(name)) {
+                if (filterDef.getDisplayName() == null) {
+                    filterDef.setDisplayName(evp.getValue().stringifyValue());
+                }
+            } else if ("largeIcon".equals(name)) {
+                if (filterDef.getLargeIcon() == null) {
+                    filterDef.setLargeIcon(evp.getValue().stringifyValue());
+                }
+            } else if ("smallIcon".equals(name)) {
+                if (filterDef.getSmallIcon() == null) {
+                    filterDef.setSmallIcon(evp.getValue().stringifyValue());
+                }
+            } else if ("asyncSupported".equals(name)) {
+                if (filterDef.getAsyncSupported() == null) {
+                    filterDef
+                            .setAsyncSupported(evp.getValue().stringifyValue());
+                }
+            } else if ("initParams".equals(name)) {
+                Map<String, String> initParams = processAnnotationWebInitParams(evp
+                        .getValue());
+                if (isWebXMLfilterDef) {
+                    Map<String, String> webXMLInitParams = filterDef
+                            .getParameterMap();
+                    for (Map.Entry<String, String> entry : initParams
+                            .entrySet()) {
+                        if (webXMLInitParams.get(entry.getKey()) == null) {
+                            filterDef.addInitParameter(entry.getKey(), entry
+                                    .getValue());
+                        }
+                    }
+                } else {
+                    for (Map.Entry<String, String> entry : initParams
+                            .entrySet()) {
+                        filterDef.addInitParameter(entry.getKey(), entry
+                                .getValue());
+                    }
+                }
+
+            }
+        }
+        if (!isWebXMLfilterDef) {
+            fragment.addFilter(filterDef);
+            filterMap.setFilterName(filterName);
+            fragment.addFilterMapping(filterMap);
+        }
+        if (urlPatternsSet || dispatchTypesSet) {
+            Set<FilterMap> fmap = fragment.getFilterMappings();
+            FilterMap descMap = null;
+            for (FilterMap map : fmap) {
+                if (filterName.equals(map.getFilterName())) {
+                    descMap = map;
+                    break;
+                }
+            }
+            if (descMap != null) {
+                String[] urlsPatterns = descMap.getURLPatterns();
+                if (urlPatternsSet
+                        && (urlsPatterns == null || urlsPatterns.length == 0)) {
+                    for (String urlPattern : filterMap.getURLPatterns()) {
+                        descMap.addURLPattern(urlPattern);
+                    }
+                }
+                String[] dispatcherNames = descMap.getDispatcherNames();
+                if (dispatchTypesSet
+                        && (dispatcherNames == null || dispatcherNames.length == 0)) {
+                    for (String dis : filterMap.getDispatcherNames()) {
+                        descMap.setDispatcher(dis);
+                    }
+                }
+            }
+        }
+
+    }
+
+    protected String[] processAnnotationsStringArray(ElementValue ev) {
+        ArrayList<String> values = new ArrayList<String>();
+        if (ev instanceof ArrayElementValue) {
+            ElementValue[] arrayValues =
+                ((ArrayElementValue) ev).getElementValuesArray();
+            for (ElementValue value : arrayValues) {
+                values.add(value.stringifyValue());
+            }
+        } else {
+            values.add(ev.stringifyValue());
+        }
+        String[] result = new String[values.size()];
+        return values.toArray(result);
+    }
+    
+    protected Map<String,String> processAnnotationWebInitParams(
+            ElementValue ev) {
+        Map<String, String> result = new HashMap<String,String>();
+        if (ev instanceof ArrayElementValue) {
+            ElementValue[] arrayValues =
+                ((ArrayElementValue) ev).getElementValuesArray();
+            for (ElementValue value : arrayValues) {
+                if (value instanceof AnnotationElementValue) {
+                    ElementValuePair[] evps = ((AnnotationElementValue)
+                            value).getAnnotationEntry().getElementValuePairs();
+                    String initParamName = null;
+                    String initParamValue = null;
+                    for (ElementValuePair evp : evps) {
+                        if ("name".equals(evp.getNameString())) {
+                            initParamName = evp.getValue().stringifyValue();
+                        } else if ("value".equals(evp.getNameString())) {
+                            initParamValue = evp.getValue().stringifyValue();
+                        } else {
+                            // Ignore
+                        }
+                    }
+                    result.put(initParamName, initParamValue);
+                }
+            }
+        }
+        return result;
+    }
+    
+    private class FragmentJarScannerCallback implements JarScannerCallback {
+
+        private static final String FRAGMENT_LOCATION =
+            "META-INF/web-fragment.xml";
+        private Map<String,WebXml> fragments = new HashMap<String,WebXml>();
+        
+        @Override
+        public void scan(JarURLConnection urlConn) throws IOException {
+            
+            JarFile jarFile = null;
+            InputStream stream = null;
+            WebXml fragment = new WebXml();
+
+            try {
+                urlConn.setUseCaches(false);
+                jarFile = urlConn.getJarFile();
+                JarEntry fragmentEntry =
+                    jarFile.getJarEntry(FRAGMENT_LOCATION);
+                if (fragmentEntry == null) {
+                    // If there is no web.xml, normal JAR no impact on
+                    // distributable
+                    fragment.setDistributable(true);
+                } else {
+                    stream = jarFile.getInputStream(fragmentEntry);
+                    InputSource source = new InputSource(
+                            urlConn.getJarFileURL().toString() +
+                            "!/" + FRAGMENT_LOCATION);
+                    source.setByteStream(stream);
+                    parseWebXml(source, fragment, true);
+                }
+            } finally {
+                if (jarFile != null) {
+                    try {
+                        jarFile.close();
+                    } catch (Throwable t) {
+                        ExceptionUtils.handleThrowable(t);
+                    }
+                }
+                if (stream != null) {
+                    try {
+                        stream.close();
+                    } catch (Throwable t) {
+                        ExceptionUtils.handleThrowable(t);
+                    }
+                }
+                fragment.setURL(urlConn.getURL());
+                if (fragment.getName() == null) {
+                    fragment.setName(fragment.getURL().toString());
+                }
+                fragments.put(fragment.getName(), fragment);
+            }
+        }
+
+        @Override
+        public void scan(File file) throws IOException {
+
+            InputStream stream = null;
+            WebXml fragment = new WebXml();
+            
+            try {
+                File fragmentFile = new File(file, FRAGMENT_LOCATION);
+                if (fragmentFile.isFile()) {
+                    stream = new FileInputStream(fragmentFile);
+                    InputSource source =
+                        new InputSource(fragmentFile.toURI().toURL().toString());
+                    source.setByteStream(stream);
+                    parseWebXml(source, fragment, true);
+                }
+            } finally {
+                if (stream != null) {
+                    try {
+                        stream.close();
+                    } catch (Throwable t) {
+                        ExceptionUtils.handleThrowable(t);
+                    }
+                }
+                fragment.setURL(file.toURI().toURL());
+                if (fragment.getName() == null) {
+                    fragment.setName(fragment.getURL().toString());
+                }
+                fragments.put(fragment.getName(), fragment);
+            }
+        }
+        
+        public Map<String,WebXml> getFragments() {
+            return fragments;
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/ContextRuleSet.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/ContextRuleSet.java
new file mode 100644
index 0000000..1bef8cb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/ContextRuleSet.java
@@ -0,0 +1,217 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.startup;
+
+
+import org.apache.tomcat.util.digester.Digester;
+import org.apache.tomcat.util.digester.RuleSetBase;
+
+
+/**
+ * <p><strong>RuleSet</strong> for processing the contents of a
+ * Context definition element.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: ContextRuleSet.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public class ContextRuleSet extends RuleSetBase {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The matching pattern prefix to use for recognizing our elements.
+     */
+    protected String prefix = null;
+
+
+    /**
+     * Should the context be created.
+     */
+    protected boolean create = true;
+
+
+    // ------------------------------------------------------------ Constructor
+
+
+    /**
+     * Construct an instance of this <code>RuleSet</code> with the default
+     * matching pattern prefix.
+     */
+    public ContextRuleSet() {
+
+        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 ContextRuleSet(String prefix) {
+
+        super();
+        this.namespaceURI = null;
+        this.prefix = prefix;
+
+    }
+
+
+    /**
+     * 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 ContextRuleSet(String prefix, boolean create) {
+
+        super();
+        this.namespaceURI = null;
+        this.prefix = prefix;
+        this.create = create;
+
+    }
+
+
+    // --------------------------------------------------------- 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) {
+
+        if (create) {
+            digester.addObjectCreate(prefix + "Context",
+                    "org.apache.catalina.core.StandardContext", "className");
+            digester.addSetProperties(prefix + "Context");
+        } else {
+            digester.addRule(prefix + "Context", new SetContextPropertiesRule());
+        }
+
+        if (create) {
+            digester.addRule(prefix + "Context",
+                             new LifecycleListenerRule
+                                 ("org.apache.catalina.startup.ContextConfig",
+                                  "configClass"));
+            digester.addSetNext(prefix + "Context",
+                                "addChild",
+                                "org.apache.catalina.Container");
+        }
+        digester.addCallMethod(prefix + "Context/InstanceListener",
+                               "addInstanceListener", 0);
+
+        digester.addObjectCreate(prefix + "Context/Listener",
+                                 null, // MUST be specified in the element
+                                 "className");
+        digester.addSetProperties(prefix + "Context/Listener");
+        digester.addSetNext(prefix + "Context/Listener",
+                            "addLifecycleListener",
+                            "org.apache.catalina.LifecycleListener");
+
+        digester.addObjectCreate(prefix + "Context/Loader",
+                            "org.apache.catalina.loader.WebappLoader",
+                            "className");
+        digester.addSetProperties(prefix + "Context/Loader");
+        digester.addSetNext(prefix + "Context/Loader",
+                            "setLoader",
+                            "org.apache.catalina.Loader");
+
+        digester.addObjectCreate(prefix + "Context/Manager",
+                                 "org.apache.catalina.session.StandardManager",
+                                 "className");
+        digester.addSetProperties(prefix + "Context/Manager");
+        digester.addSetNext(prefix + "Context/Manager",
+                            "setManager",
+                            "org.apache.catalina.Manager");
+
+        digester.addObjectCreate(prefix + "Context/Manager/Store",
+                                 null, // MUST be specified in the element
+                                 "className");
+        digester.addSetProperties(prefix + "Context/Manager/Store");
+        digester.addSetNext(prefix + "Context/Manager/Store",
+                            "setStore",
+                            "org.apache.catalina.Store");
+
+        digester.addObjectCreate(prefix + "Context/Parameter",
+                                 "org.apache.catalina.deploy.ApplicationParameter");
+        digester.addSetProperties(prefix + "Context/Parameter");
+        digester.addSetNext(prefix + "Context/Parameter",
+                            "addApplicationParameter",
+                            "org.apache.catalina.deploy.ApplicationParameter");
+
+        digester.addRuleSet(new RealmRuleSet(prefix + "Context/"));
+
+        digester.addObjectCreate(prefix + "Context/Resources",
+                                 "org.apache.naming.resources.FileDirContext",
+                                 "className");
+        digester.addSetProperties(prefix + "Context/Resources");
+        digester.addSetNext(prefix + "Context/Resources",
+                            "setResources",
+                            "javax.naming.directory.DirContext");
+
+        digester.addObjectCreate(prefix + "Context/ResourceLink",
+                "org.apache.catalina.deploy.ContextResourceLink");
+        digester.addSetProperties(prefix + "Context/ResourceLink");
+        digester.addRule(prefix + "Context/ResourceLink",
+                new SetNextNamingRule("addResourceLink",
+                        "org.apache.catalina.deploy.ContextResourceLink"));
+
+        digester.addObjectCreate(prefix + "Context/Valve",
+                                 null, // MUST be specified in the element
+                                 "className");
+        digester.addSetProperties(prefix + "Context/Valve");
+        digester.addSetNext(prefix + "Context/Valve",
+                            "addValve",
+                            "org.apache.catalina.Valve");
+
+        digester.addCallMethod(prefix + "Context/WatchedResource",
+                               "addWatchedResource", 0);
+
+        digester.addCallMethod(prefix + "Context/WrapperLifecycle",
+                               "addWrapperLifecycle", 0);
+
+        digester.addCallMethod(prefix + "Context/WrapperListener",
+                               "addWrapperListener", 0);
+
+        digester.addObjectCreate(prefix + "Context/JarScanner",
+                                 "org.apache.tomcat.util.scan.StandardJarScanner",
+                                 "className");
+        digester.addSetProperties(prefix + "Context/JarScanner");
+        digester.addSetNext(prefix + "Context/JarScanner",
+                            "setJarScanner",
+                            "org.apache.tomcat.JarScanner");
+
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/CopyParentClassLoaderRule.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/CopyParentClassLoaderRule.java
new file mode 100644
index 0000000..cff7ce8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/CopyParentClassLoaderRule.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.catalina.startup;
+
+
+import java.lang.reflect.Method;
+
+import org.apache.catalina.Container;
+import org.apache.tomcat.util.digester.Rule;
+import org.xml.sax.Attributes;
+
+
+/**
+ * <p>Rule that copies the <code>parentClassLoader</code> property from the
+ * next-to-top item on the stack (which must be a <code>Container</code>)
+ * to the top item on the stack (which must also be a
+ * <code>Container</code>).</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: CopyParentClassLoaderRule.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+public class CopyParentClassLoaderRule extends Rule {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new instance of this Rule.
+     */
+    public CopyParentClassLoaderRule() {
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Handle the beginning of an XML element.
+     *
+     * @param attributes The attributes of this element
+     *
+     * @exception Exception if a processing error occurs
+     */
+    @Override
+    public void begin(String namespace, String name, Attributes attributes)
+        throws Exception {
+
+        if (digester.getLogger().isDebugEnabled())
+            digester.getLogger().debug("Copying parent class loader");
+        Container child = (Container) digester.peek(0);
+        Object parent = digester.peek(1);
+        Method method =
+            parent.getClass().getMethod("getParentClassLoader", new Class[0]);
+        ClassLoader classLoader =
+            (ClassLoader) method.invoke(parent, new Object[0]);
+        child.setParentClassLoader(classLoader);
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/DigesterFactory.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/DigesterFactory.java
new file mode 100644
index 0000000..c8ae586
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/DigesterFactory.java
@@ -0,0 +1,215 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.startup;
+
+import java.net.URL;
+
+import org.apache.catalina.util.SchemaResolver;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.digester.Digester;
+import org.apache.tomcat.util.digester.RuleSet;
+
+/**
+ * Wrapper class around the Digester that hide Digester's initialization details
+ *
+ * @author Jean-Francois Arcand
+ */
+public class DigesterFactory {
+    /**
+     * The log.
+     */
+    private static final Log log = LogFactory.getLog(DigesterFactory.class);
+
+    /**
+     * Create a <code>Digester</code> parser with no <code>Rule</code>
+     * associated and XML validation turned off.
+     */
+    public static Digester newDigester(){
+        return newDigester(false, false, null);
+    }
+
+    
+    /**
+     * Create a <code>Digester</code> parser with XML validation turned off.
+     * @param rule an instance of <code>RuleSet</code> used for parsing the xml.
+     */
+    public static Digester newDigester(RuleSet rule){
+        return newDigester(false,false,rule);
+    }
+
+    
+    /**
+     * Create a <code>Digester</code> parser.
+     * @param xmlValidation turn on/off xml validation
+     * @param xmlNamespaceAware turn on/off namespace validation
+     * @param rule an instance of <code>RuleSet</code> used for parsing the xml.
+     */
+    public static Digester newDigester(boolean xmlValidation,
+                                       boolean xmlNamespaceAware,
+                                       RuleSet rule) {
+        Digester digester = new Digester();
+        digester.setNamespaceAware(xmlNamespaceAware);
+        digester.setValidating(xmlValidation);
+        digester.setUseContextClassLoader(true);
+
+        SchemaResolver schemaResolver = new SchemaResolver(digester);
+        registerLocalSchema(schemaResolver);
+        
+        digester.setEntityResolver(schemaResolver);
+        if ( rule != null ) {
+            digester.addRuleSet(rule);
+        }
+
+        return (digester);
+    }
+
+
+    /**
+     * Utilities used to force the parser to use local schema, when available,
+     * instead of the <code>schemaLocation</code> XML element.
+     */
+    protected static void registerLocalSchema(SchemaResolver schemaResolver){
+        // J2EE
+        register(Constants.J2eeSchemaResourcePath_14,
+                 Constants.J2eeSchemaPublicId_14,
+                 schemaResolver);
+
+        register(Constants.JavaeeSchemaResourcePath_5,
+                Constants.JavaeeSchemaPublicId_5,
+                schemaResolver);
+
+        register(Constants.JavaeeSchemaResourcePath_6,
+                Constants.JavaeeSchemaPublicId_6,
+                schemaResolver);
+
+        // W3C
+        register(Constants.W3cSchemaResourcePath_10,
+                 Constants.W3cSchemaPublicId_10,
+                 schemaResolver);
+
+        register(Constants.W3cSchemaDTDResourcePath_10,
+                Constants.W3cSchemaDTDPublicId_10,
+                schemaResolver);
+
+        register(Constants.W3cDatatypesDTDResourcePath_10,
+                Constants.W3cDatatypesDTDPublicId_10,
+                schemaResolver);
+
+        // JSP
+        register(Constants.JspSchemaResourcePath_20,
+                 Constants.JspSchemaPublicId_20,
+                 schemaResolver);
+
+        register(Constants.JspSchemaResourcePath_21,
+                Constants.JspSchemaPublicId_21,
+                schemaResolver);
+
+        register(Constants.JspSchemaResourcePath_22,
+                Constants.JspSchemaPublicId_22,
+                schemaResolver);
+
+        // TLD
+        register(Constants.TldDtdResourcePath_11,  
+                 Constants.TldDtdPublicId_11,
+                 schemaResolver);
+        
+        register(Constants.TldDtdResourcePath_12,
+                 Constants.TldDtdPublicId_12,
+                 schemaResolver);
+
+        register(Constants.TldSchemaResourcePath_20,
+                 Constants.TldSchemaPublicId_20,
+                 schemaResolver);
+
+        register(Constants.TldSchemaResourcePath_21,
+                Constants.TldSchemaPublicId_21,
+                schemaResolver);
+
+        // web.xml    
+        register(Constants.WebDtdResourcePath_22,
+                 Constants.WebDtdPublicId_22,
+                 schemaResolver);
+
+        register(Constants.WebDtdResourcePath_23,
+                 Constants.WebDtdPublicId_23,
+                 schemaResolver);
+
+        register(Constants.WebSchemaResourcePath_24,
+                 Constants.WebSchemaPublicId_24,
+                 schemaResolver);
+
+        register(Constants.WebSchemaResourcePath_25,
+                Constants.WebSchemaPublicId_25,
+                schemaResolver);
+
+        register(Constants.WebSchemaResourcePath_30,
+                Constants.WebSchemaPublicId_30,
+                schemaResolver);
+
+        register(Constants.WebCommonSchemaResourcePath_30,
+                Constants.WebCommonSchemaPublicId_30,
+                schemaResolver);
+        
+        register(Constants.WebFragmentSchemaResourcePath_30,
+                Constants.WebFragmentSchemaPublicId_30,
+                schemaResolver);
+
+        // Web Service
+        register(Constants.J2eeWebServiceSchemaResourcePath_11,
+                 Constants.J2eeWebServiceSchemaPublicId_11,
+                 schemaResolver);
+
+        register(Constants.J2eeWebServiceClientSchemaResourcePath_11,
+                 Constants.J2eeWebServiceClientSchemaPublicId_11,
+                 schemaResolver);
+
+        register(Constants.JavaeeWebServiceSchemaResourcePath_12,
+                Constants.JavaeeWebServiceSchemaPublicId_12,
+                schemaResolver);
+
+        register(Constants.JavaeeWebServiceClientSchemaResourcePath_12,
+                Constants.JavaeeWebServiceClientSchemaPublicId_12,
+                schemaResolver);
+
+        register(Constants.JavaeeWebServiceSchemaResourcePath_13,
+                Constants.JavaeeWebServiceSchemaPublicId_13,
+                schemaResolver);
+
+        register(Constants.JavaeeWebServiceClientSchemaResourcePath_13,
+                Constants.JavaeeWebServiceClientSchemaPublicId_13,
+                schemaResolver);
+    }
+
+
+    /**
+     * Load the resource and add it to the resolver.
+     */
+    protected static void register(String resourceURL, String resourcePublicId,
+            SchemaResolver schemaResolver){
+        URL url = DigesterFactory.class.getResource(resourceURL);
+   
+        if(url == null) {
+            log.warn("Could not get url for " + resourceURL);
+        } else {
+            schemaResolver.register(resourcePublicId , url.toString() );
+        }
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/Embedded.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/Embedded.java
new file mode 100644
index 0000000..2d1b077
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/Embedded.java
@@ -0,0 +1,986 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.startup;
+
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.HashMap;
+
+import org.apache.catalina.Authenticator;
+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.Lifecycle;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.Loader;
+import org.apache.catalina.Realm;
+import org.apache.catalina.Valve;
+import org.apache.catalina.connector.Connector;
+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.security.SecurityConfig;
+import org.apache.catalina.util.LifecycleSupport;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.IntrospectionUtils;
+import org.apache.tomcat.util.log.SystemLogHandler;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Convenience class to embed a Catalina servlet container environment
+ * inside another application.  You must call the methods of this class in the
+ * following order to ensure correct operation.
+ *
+ * <ul>
+ * <li>Instantiate a new instance of this class.</li>
+ * <li>Set the relevant properties of this object itself.  In particular,
+ *     you will want to establish the default Logger to be used, as well
+ *     as the default Realm if you are using container-managed security.</li>
+ * <li>Call <code>createEngine()</code> to create an Engine object, and then
+ *     call its property setters as desired.</li>
+ * <li>Call <code>createHost()</code> to create at least one virtual Host
+ *     associated with the newly created Engine, and then call its property
+ *     setters as desired.  After you customize this Host, add it to the
+ *     corresponding Engine with <code>engine.addChild(host)</code>.</li>
+ * <li>Call <code>createContext()</code> to create at least one Context
+ *     associated with each newly created Host, and then call its property
+ *     setters as desired.  You <strong>SHOULD</strong> create a Context with
+ *     a pathname equal to a zero-length string, which will be used to process
+ *     all requests not mapped to some other Context.  After you customize
+ *     this Context, add it to the corresponding Host with
+ *     <code>host.addChild(context)</code>.</li>
+ * <li>Call <code>addEngine()</code> to attach this Engine to the set of
+ *     defined Engines for this object.</li>
+ * <li>Call <code>createConnector()</code> to create at least one TCP/IP
+ *     connector, and then call its property setters as desired.</li>
+ * <li>Call <code>addConnector()</code> to attach this Connector to the set
+ *     of defined Connectors for this object.  The added Connector will use
+ *     the most recently added Engine to process its received requests.</li>
+ * <li>Repeat the above series of steps as often as required (although there
+ *     will typically be only one Engine instance created).</li>
+ * <li>Call <code>start()</code> to initiate normal operations of all the
+ *     attached components.</li>
+ * </ul>
+ *
+ * After normal operations have begun, you can add and remove Connectors,
+ * Engines, Hosts, and Contexts on the fly.  However, once you have removed
+ * a particular component, it must be thrown away -- you can create a new one
+ * with the same characteristics if you merely want to do a restart.
+ * <p>
+ * To initiate a normal shutdown, call the <code>stop()</code> method of
+ * this object.
+ * <p>
+ * @see org.apache.catalina.startup.Bootstrap#main For a complete example
+ * of how Tomcat is set up and launched as an Embedded application.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: Embedded.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ * 
+ * @deprecated Use {@link Tomcat} instead.
+ */
+
+@Deprecated
+public class Embedded  extends StandardService {
+    private static final Log log = LogFactory.getLog(Embedded.class);
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new instance of this class with default properties.
+     */
+    public Embedded() {
+
+        this(null);
+
+    }
+
+
+    /**
+     * Construct a new instance of this class with specified properties.
+     *
+     * @param realm Realm implementation to be inherited by all components
+     *  (unless overridden further down the container hierarchy)
+     */
+    public Embedded(Realm realm) {
+
+        super();
+        setRealm(realm);
+        setSecurityProtection();
+        
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Is naming enabled ?
+     */
+    protected boolean useNaming = true;
+
+
+    /**
+     * Is standard streams redirection enabled ?
+     */
+    protected boolean redirectStreams = true;
+
+
+    /**
+     * The set of Engines that have been deployed in this server.  Normally
+     * there will only be one.
+     */
+    protected Engine engines[] = new Engine[0];
+
+
+    /**
+     * Custom mappings of login methods to authenticators
+     */
+    protected HashMap<String,Authenticator> authenticators;
+
+
+    /**
+     * Descriptive information about this server implementation.
+     */
+    protected static final String info =
+        "org.apache.catalina.startup.Embedded/1.0";
+
+
+    /**
+     * The lifecycle event support for this component.
+     */
+    protected LifecycleSupport lifecycle = new LifecycleSupport(this);
+
+
+    /**
+     * The default realm to be used by all containers associated with
+     * this component.
+     */
+    protected Realm realm = null;
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
+     * Use await.
+     */
+    protected boolean await = false;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return true if naming is enabled.
+     */
+    public boolean isUseNaming() {
+
+        return (this.useNaming);
+
+    }
+
+
+    /**
+     * Enables or disables naming support.
+     *
+     * @param useNaming The new use naming value
+     */
+    public void setUseNaming(boolean useNaming) {
+
+        boolean oldUseNaming = this.useNaming;
+        this.useNaming = useNaming;
+        support.firePropertyChange("useNaming", Boolean.valueOf(oldUseNaming),
+                                   Boolean.valueOf(this.useNaming));
+
+    }
+
+
+    /**
+     * Return true if redirection of standard streams is enabled.
+     */
+    public boolean isRedirectStreams() {
+
+        return (this.redirectStreams);
+
+    }
+
+
+    /**
+     * Enables or disables redirection.
+     *
+     * @param redirectStreams The new redirection value
+     */
+    public void setRedirectStreams(boolean redirectStreams) {
+
+        boolean oldRedirectStreams = this.redirectStreams;
+        this.redirectStreams = redirectStreams;
+        support.firePropertyChange("redirectStreams",
+                                   Boolean.valueOf(oldRedirectStreams),
+                                   Boolean.valueOf(this.redirectStreams));
+
+    }
+
+
+    /**
+     * Return the default Realm for our Containers.
+     */
+    public Realm getRealm() {
+
+        return (this.realm);
+
+    }
+
+
+    /**
+     * Set the default Realm for our Containers.
+     *
+     * @param realm The new default realm
+     */
+    public void setRealm(Realm realm) {
+
+        Realm oldRealm = this.realm;
+        this.realm = realm;
+        support.firePropertyChange("realm", oldRealm, this.realm);
+
+    }
+
+    public void setAwait(boolean b) {
+        await = b;
+    }
+
+    public boolean isAwait() {
+        return await;
+    }
+
+    public void setCatalinaHome(String s) {
+        System.setProperty(Globals.CATALINA_HOME_PROP, s);
+    }
+
+    public void setCatalinaBase(String s) {
+        System.setProperty(Globals.CATALINA_BASE_PROP, s);
+    }
+
+    public String getCatalinaHome() {
+        return System.getProperty(Globals.CATALINA_HOME_PROP);
+    }
+
+    public String getCatalinaBase() {
+        return System.getProperty(Globals.CATALINA_BASE_PROP);
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Add a new Connector to the set of defined Connectors.  The newly
+     * added Connector will be associated with the most recently added Engine.
+     *
+     * @param connector The connector to be added
+     *
+     * @exception IllegalStateException if no engines have been added yet
+     */
+    @Override
+    public synchronized void addConnector(Connector connector) {
+
+        if( log.isDebugEnabled() ) {
+            log.debug("Adding connector (" + connector.getInfo() + ")");
+        }
+
+        // Make sure we have a Container to send requests to
+        if (engines.length < 1)
+            throw new IllegalStateException
+                (sm.getString("embedded.noEngines"));
+
+        /*
+         * Add the connector. This will set the connector's container to the
+         * most recently added Engine
+         */
+        super.addConnector(connector);
+    }
+
+
+    /**
+     * Add a new Engine to the set of defined Engines.
+     *
+     * @param engine The engine to be added
+     */
+    public synchronized void addEngine(Engine engine) {
+
+        if( log.isDebugEnabled() )
+            log.debug("Adding engine (" + engine.getInfo() + ")");
+
+        // Add this Engine to our set of defined Engines
+        Engine results[] = new Engine[engines.length + 1];
+        for (int i = 0; i < engines.length; i++)
+            results[i] = engines[i];
+        results[engines.length] = engine;
+        engines = results;
+
+        // Start this Engine if necessary
+        if (getState().isAvailable()) {
+            try {
+                engine.start();
+            } catch (LifecycleException e) {
+                log.error("Engine.start", e);
+            }
+        }
+
+        this.container = engine;
+    }
+
+
+    /**
+     * Create, configure, and return a new TCP/IP socket connector
+     * based on the specified properties.
+     *
+     * @param address InetAddress to bind to, or <code>null</code> if the
+     * connector is supposed to bind to all addresses on this server
+     * @param port Port number to listen to
+     * @param secure true if the generated connector is supposed to be
+     * SSL-enabled, and false otherwise
+     */
+    public Connector createConnector(InetAddress address, int port,
+                                     boolean secure) {
+        return createConnector(address != null? address.toString() : null,
+                port, secure);
+    }
+
+    public Connector createConnector(String address, int port,
+                                     boolean secure) {
+        String protocol = "http";
+        if (secure) {
+            protocol = "https";
+        }
+
+        return createConnector(address, port, protocol);
+    }
+
+
+    public Connector createConnector(InetAddress address, int port,
+                                     String protocol) {
+        return createConnector(address != null? address.toString() : null,
+                port, protocol);
+    }
+
+    public Connector createConnector(String address, int port,
+            String protocol) {
+
+        Connector connector = null;
+
+        if (address != null) {
+            /*
+             * InetAddress.toString() returns a string of the form
+             * "<hostname>/<literal_IP>". Get the latter part, so that the
+             * address can be parsed (back) into an InetAddress using
+             * InetAddress.getByName().
+             */
+            int index = address.indexOf('/');
+            if (index != -1) {
+                address = address.substring(index + 1);
+            }
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("Creating connector for address='" +
+                      ((address == null) ? "ALL" : address) +
+                      "' port='" + port + "' protocol='" + protocol + "'");
+        }
+
+        try {
+
+            if (protocol.equals("ajp")) {
+                connector = new Connector("org.apache.coyote.ajp.AjpProtocol");
+            } else if (protocol.equals("memory")) {
+                connector = new Connector("org.apache.coyote.memory.MemoryProtocolHandler");
+            } else if (protocol.equals("http")) {
+                connector = new Connector();
+            } else if (protocol.equals("https")) {
+                connector = new Connector();
+                connector.setScheme("https");
+                connector.setSecure(true);
+                connector.setProperty("SSLEnabled","true");
+                // FIXME !!!! SET SSL PROPERTIES
+            } else {
+                connector = new Connector(protocol);
+            }
+
+            if (address != null) {
+                IntrospectionUtils.setProperty(connector, "address", 
+                                               "" + address);
+            }
+            IntrospectionUtils.setProperty(connector, "port", "" + port);
+
+        } catch (Exception e) {
+            log.error("Couldn't create connector.");
+        } 
+
+        return (connector);
+
+    }
+
+    /**
+     * Create, configure, and return a Context that will process all
+     * HTTP requests received from one of the associated Connectors,
+     * and directed to the specified context path on the virtual host
+     * to which this Context is connected.
+     * <p>
+     * After you have customized the properties, listeners, and Valves
+     * for this Context, you must attach it to the corresponding Host
+     * by calling:
+     * <pre>
+     *   host.addChild(context);
+     * </pre>
+     * which will also cause the Context to be started if the Host has
+     * already been started.
+     *
+     * @param path Context path of this application ("" for the default
+     *  application for this host, must start with a slash otherwise)
+     * @param docBase Absolute pathname to the document base directory
+     *  for this web application
+     *
+     * @exception IllegalArgumentException if an invalid parameter
+     *  is specified
+     */
+    public Context createContext(String path, String docBase) {
+
+        if( log.isDebugEnabled() )
+            log.debug("Creating context '" + path + "' with docBase '" +
+                       docBase + "'");
+
+        StandardContext context = new StandardContext();
+
+        context.setDocBase(docBase);
+        context.setPath(path);
+
+        ContextConfig config = new ContextConfig();
+        config.setCustomAuthenticators(authenticators);
+        context.addLifecycleListener(config);
+
+        return (context);
+
+    }
+
+
+    /**
+     * Create, configure, and return an Engine that will process all
+     * HTTP requests received from one of the associated Connectors,
+     * based on the specified properties.
+     */
+    public Engine createEngine() {
+
+        if( log.isDebugEnabled() )
+            log.debug("Creating engine");
+
+        StandardEngine engine = new StandardEngine();
+
+        // Default host will be set to the first host added
+        engine.setRealm(realm);         // Inherited by all children
+
+        return (engine);
+
+    }
+
+
+    /**
+     * Create, configure, and return a Host that will process all
+     * HTTP requests received from one of the associated Connectors,
+     * and directed to the specified virtual host.
+     * <p>
+     * After you have customized the properties, listeners, and Valves
+     * for this Host, you must attach it to the corresponding Engine
+     * by calling:
+     * <pre>
+     *   engine.addChild(host);
+     * </pre>
+     * which will also cause the Host to be started if the Engine has
+     * already been started.  If this is the default (or only) Host you
+     * will be defining, you may also tell the Engine to pass all requests
+     * not assigned to another virtual host to this one:
+     * <pre>
+     *   engine.setDefaultHost(host.getName());
+     * </pre>
+     *
+     * @param name Canonical name of this virtual host
+     * @param appBase Absolute pathname to the application base directory
+     *  for this virtual host
+     *
+     * @exception IllegalArgumentException if an invalid parameter
+     *  is specified
+     */
+    public Host createHost(String name, String appBase) {
+
+        if( log.isDebugEnabled() )
+            log.debug("Creating host '" + name + "' with appBase '" +
+                       appBase + "'");
+
+        StandardHost host = new StandardHost();
+
+        host.setAppBase(appBase);
+        host.setName(name);
+
+        return (host);
+
+    }
+
+
+    /**
+     * Create and return a class loader manager that can be customized, and
+     * then attached to a Context, before it is started.
+     *
+     * @param parent ClassLoader that will be the parent of the one
+     *  created by this Loader
+     */
+    public Loader createLoader(ClassLoader parent) {
+
+        if( log.isDebugEnabled() )
+            log.debug("Creating Loader with parent class loader '" +
+                       parent + "'");
+
+        WebappLoader loader = new WebappLoader(parent);
+        return (loader);
+
+    }
+
+
+    /**
+     * Return descriptive information about this Server implementation and
+     * the corresponding version number, in the format
+     * <code>&lt;description&gt;/&lt;version&gt;</code>.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    /**
+     * Remove the specified Context from the set of defined Contexts for its
+     * associated Host.  If this is the last Context for this Host, the Host
+     * will also be removed.
+     *
+     * @param context The Context to be removed
+     */
+    public synchronized void removeContext(Context context) {
+
+        if( log.isDebugEnabled() )
+            log.debug("Removing context[" + context.getName() + "]");
+
+        // Is this Context actually among those that are defined?
+        boolean found = false;
+        for (int i = 0; i < engines.length; i++) {
+            Container hosts[] = engines[i].findChildren();
+            for (int j = 0; j < hosts.length; j++) {
+                Container contexts[] = hosts[j].findChildren();
+                for (int k = 0; k < contexts.length; k++) {
+                    if (context == (Context) contexts[k]) {
+                        found = true;
+                        break;
+                    }
+                }
+                if (found)
+                    break;
+            }
+            if (found)
+                break;
+        }
+        if (!found)
+            return;
+
+        // Remove this Context from the associated Host
+        if( log.isDebugEnabled() )
+            log.debug(" Removing this Context");
+        context.getParent().removeChild(context);
+
+    }
+
+
+    /**
+     * Remove the specified Engine from the set of defined Engines, along with
+     * all of its related Hosts and Contexts.  All associated Connectors are
+     * also removed.
+     *
+     * @param engine The Engine to be removed
+     */
+    public synchronized void removeEngine(Engine engine) {
+
+        if( log.isDebugEnabled() )
+            log.debug("Removing engine (" + engine.getInfo() + ")");
+
+        // Is the specified Engine actually defined?
+        int j = -1;
+        for (int i = 0; i < engines.length; i++) {
+            if (engine == engines[i]) {
+                j = i;
+                break;
+            }
+        }
+        if (j < 0)
+            return;
+
+        // Remove any Connector that is using this Engine
+        if( log.isDebugEnabled() )
+            log.debug(" Removing related Containers");
+        while (true) {
+            int n = -1;
+            for (int i = 0; i < connectors.length; i++) {
+                if (connectors[i].getService().getContainer() == engine) {
+                    n = i;
+                    break;
+                }
+            }
+            if (n < 0)
+                break;
+            removeConnector(connectors[n]);
+        }
+
+        // Stop this Engine if necessary
+        if( log.isDebugEnabled() )
+            log.debug(" Stopping this Engine");
+        try {
+            engine.stop();
+        } catch (LifecycleException e) {
+            log.error("Engine.stop", e);
+        }
+
+        // Remove this Engine from our set of defined Engines
+        if( log.isDebugEnabled() )
+            log.debug(" Removing this Engine");
+        int k = 0;
+        Engine results[] = new Engine[engines.length - 1];
+        for (int i = 0; i < engines.length; i++) {
+            if (i != j)
+                results[k++] = engines[i];
+        }
+        engines = results;
+
+    }
+
+
+    /**
+     * Remove the specified Host, along with all of its related Contexts,
+     * from the set of defined Hosts for its associated Engine.  If this is
+     * the last Host for this Engine, the Engine will also be removed.
+     *
+     * @param host The Host to be removed
+     */
+    public synchronized void removeHost(Host host) {
+
+        if( log.isDebugEnabled() )
+            log.debug("Removing host[" + host.getName() + "]");
+
+        // Is this Host actually among those that are defined?
+        boolean found = false;
+        for (int i = 0; i < engines.length; i++) {
+            Container hosts[] = engines[i].findChildren();
+            for (int j = 0; j < hosts.length; j++) {
+                if (host == (Host) hosts[j]) {
+                    found = true;
+                    break;
+
+                }
+            }
+            if (found)
+                break;
+        }
+        if (!found)
+            return;
+
+        // Remove this Host from the associated Engine
+        if( log.isDebugEnabled() )
+            log.debug(" Removing this Host");
+        host.getParent().removeChild(host);
+
+    }
+
+
+    /*
+     * Maps the specified login method to the specified authenticator, allowing
+     * the mappings in org/apache/catalina/startup/Authenticators.properties
+     * to be overridden.
+     *
+     * @param authenticator Authenticator to handle authentication for the
+     * specified login method
+     * @param loginMethod Login method that maps to the specified authenticator
+     *
+     * @throws IllegalArgumentException if the specified authenticator does not
+     * implement the org.apache.catalina.Valve interface
+     */
+    public void addAuthenticator(Authenticator authenticator,
+                                 String loginMethod) {
+        if (!(authenticator instanceof Valve)) {
+            throw new IllegalArgumentException(
+                sm.getString("embedded.authenticatorNotInstanceOfValve"));
+        }
+        if (authenticators == null) {
+            synchronized (this) {
+                if (authenticators == null) {
+                    authenticators = new HashMap<String,Authenticator>();
+                }
+            }
+        }
+        authenticators.put(loginMethod, authenticator);
+    }
+
+
+    // ------------------------------------------------------ Lifecycle Methods
+
+
+    /**
+     * Add a lifecycle event listener to this component.
+     *
+     * @param listener The listener to add
+     */
+    @Override
+    public void addLifecycleListener(LifecycleListener listener) {
+
+        lifecycle.addLifecycleListener(listener);
+
+    }
+
+
+    /**
+     * 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 lifecycle.findLifecycleListeners();
+
+    }
+
+
+    /**
+     * Remove a lifecycle event listener from this component.
+     *
+     * @param listener The listener to remove
+     */
+    @Override
+    public void removeLifecycleListener(LifecycleListener listener) {
+
+        lifecycle.removeLifecycleListener(listener);
+
+    }
+
+
+    /**
+     * Start nested components ({@link Connector}s and {@link Engine}s) 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.isInfoEnabled() )
+            log.info("Starting tomcat server");
+
+        // Validate the setup of our required system properties
+        initDirs();
+
+        // Initialize some naming specific properties
+        initNaming();
+
+        setState(LifecycleState.STARTING);
+
+        // Start our defined Engines first
+        for (int i = 0; i < engines.length; i++) {
+            engines[i].start();
+        }
+
+        // Start our defined Connectors second
+        for (int i = 0; i < connectors.length; i++) {
+            ((Lifecycle) connectors[i]).start();
+        }
+
+    }
+
+
+    /**
+     * Stop nested components ({@link Connector}s and {@link Engine}s) and
+     * implement the requirements of
+     * {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
+     *
+     * @exception LifecycleException if this component detects a fatal error
+     *  that needs to be reported
+     */
+    @Override
+    protected void stopInternal() throws LifecycleException {
+
+        if( log.isDebugEnabled() )
+            log.debug("Stopping embedded server");
+
+        setState(LifecycleState.STOPPING);
+
+        // Stop our defined Connectors first
+        for (int i = 0; i < connectors.length; i++) {
+            ((Lifecycle) connectors[i]).stop();
+        }
+
+        // Stop our defined Engines second
+        for (int i = 0; i < engines.length; i++) {
+            engines[i].stop();
+        }
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /** Initialize naming - this should only enable java:env and root naming.
+     * If tomcat is embedded in an application that already defines those -
+     * it shouldn't do it.
+     *
+     * XXX The 2 should be separated, you may want to enable java: but not
+     * the initial context and the reverse
+     * XXX Can we "guess" - i.e. lookup java: and if something is returned assume
+     * false ?
+     * XXX We have a major problem with the current setting for java: url
+     */
+    protected void initNaming() {
+        // Setting additional variables
+        if (!useNaming) {
+            log.info( "Catalina naming disabled");
+            System.setProperty("catalina.useNaming", "false");
+        } else {
+            System.setProperty("catalina.useNaming", "true");
+            String value = "org.apache.naming";
+            String oldValue =
+                System.getProperty(javax.naming.Context.URL_PKG_PREFIXES);
+            if (oldValue != null) {
+                value = value + ":" + oldValue;
+            }
+            System.setProperty(javax.naming.Context.URL_PKG_PREFIXES, value);
+            if( log.isDebugEnabled() )
+                log.debug("Setting naming prefix=" + value);
+            value = System.getProperty
+                (javax.naming.Context.INITIAL_CONTEXT_FACTORY);
+            if (value == null) {
+                System.setProperty
+                    (javax.naming.Context.INITIAL_CONTEXT_FACTORY,
+                     "org.apache.naming.java.javaURLContextFactory");
+            } else {
+                log.debug( "INITIAL_CONTEXT_FACTORY already set " + value );
+            }
+        }
+    }
+
+
+    protected void initDirs() {
+
+        String catalinaHome = System.getProperty(Globals.CATALINA_HOME_PROP);
+        if (catalinaHome == null) {
+            // Backwards compatibility patch for J2EE RI 1.3
+            String j2eeHome = System.getProperty("com.sun.enterprise.home");
+            if (j2eeHome != null) {
+                catalinaHome=System.getProperty("com.sun.enterprise.home");
+            } else if (System.getProperty(Globals.CATALINA_BASE_PROP) != null) {
+                catalinaHome = System.getProperty(Globals.CATALINA_BASE_PROP);
+            } else {
+                // Use IntrospectionUtils and guess the dir
+                catalinaHome = IntrospectionUtils.guessInstall
+                    (Globals.CATALINA_HOME_PROP, Globals.CATALINA_BASE_PROP, "catalina.jar");
+                if (catalinaHome == null) {
+                    catalinaHome = IntrospectionUtils.guessInstall
+                        ("tomcat.install", Globals.CATALINA_HOME_PROP, "tomcat.jar");
+                }
+            }
+        }
+        // last resort - for minimal/embedded cases. 
+        if(catalinaHome==null) {
+            catalinaHome=System.getProperty("user.dir");
+        }
+        if (catalinaHome != null) {
+            File home = new File(catalinaHome);
+            if (!home.isAbsolute()) {
+                try {
+                    catalinaHome = home.getCanonicalPath();
+                } catch (IOException e) {
+                    catalinaHome = home.getAbsolutePath();
+                }
+            }
+            System.setProperty(Globals.CATALINA_HOME_PROP, catalinaHome);
+        }
+
+        if (System.getProperty(Globals.CATALINA_BASE_PROP) == null) {
+            System.setProperty(Globals.CATALINA_BASE_PROP,
+                               catalinaHome);
+        } else {
+            String catalinaBase = System.getProperty(Globals.CATALINA_BASE_PROP);
+            File base = new File(catalinaBase);
+            if (!base.isAbsolute()) {
+                try {
+                    catalinaBase = base.getCanonicalPath();
+                } catch (IOException e) {
+                    catalinaBase = base.getAbsolutePath();
+                }
+            }
+            System.setProperty(Globals.CATALINA_BASE_PROP, catalinaBase);
+        }
+        
+        String temp = System.getProperty("java.io.tmpdir");
+        if (temp == null || (!(new File(temp)).exists())
+                || (!(new File(temp)).isDirectory())) {
+            log.error(sm.getString("embedded.notmp", temp));
+        }
+
+    }
+
+    
+    protected void initStreams() {
+        if (redirectStreams) {
+            // Replace System.out and System.err with a custom PrintStream
+            System.setOut(new SystemLogHandler(System.out));
+            System.setErr(new SystemLogHandler(System.err));
+        }
+    }
+    
+
+    // -------------------------------------------------------- Private Methods
+
+    /**
+     * Set the security package access/protection.
+     */
+    protected void setSecurityProtection(){
+        SecurityConfig securityConfig = SecurityConfig.newInstance();
+        securityConfig.setPackageDefinition();
+        securityConfig.setPackageAccess();
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/EngineConfig.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/EngineConfig.java
new file mode 100644
index 0000000..6db845e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/EngineConfig.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.startup;
+
+
+import org.apache.catalina.Engine;
+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;
+
+
+/**
+ * Startup event listener for a <b>Engine</b> that configures the properties
+ * of that Engine, and the associated defined contexts.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: EngineConfig.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public class EngineConfig
+    implements LifecycleListener {
+
+
+    private static final Log log = LogFactory.getLog( EngineConfig.class );
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The Engine we are associated with.
+     */
+    protected Engine engine = null;
+
+
+    /**
+     * The string resources for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Process the START event for an associated Engine.
+     *
+     * @param event The lifecycle event that has occurred
+     */
+    public void lifecycleEvent(LifecycleEvent event) {
+
+        // Identify the engine we are associated with
+        try {
+            engine = (Engine) event.getLifecycle();
+        } catch (ClassCastException e) {
+            log.error(sm.getString("engineConfig.cce", event.getLifecycle()), e);
+            return;
+        }
+
+        // Process the event that has occurred
+        if (event.getType().equals(Lifecycle.START_EVENT))
+            start();
+        else if (event.getType().equals(Lifecycle.STOP_EVENT))
+            stop();
+
+    }
+
+
+    // -------------------------------------------------------- Protected Methods
+
+
+    /**
+     * Process a "start" event for this Engine.
+     */
+    protected void start() {
+
+        if (engine.getLogger().isDebugEnabled())
+            engine.getLogger().debug(sm.getString("engineConfig.start"));
+
+    }
+
+
+    /**
+     * Process a "stop" event for this Engine.
+     */
+    protected void stop() {
+
+        if (engine.getLogger().isDebugEnabled())
+            engine.getLogger().debug(sm.getString("engineConfig.stop"));
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/EngineRuleSet.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/EngineRuleSet.java
new file mode 100644
index 0000000..059141c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/EngineRuleSet.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.catalina.startup;
+
+
+import org.apache.tomcat.util.digester.Digester;
+import org.apache.tomcat.util.digester.RuleSetBase;
+
+
+/**
+ * <p><strong>RuleSet</strong> for processing the contents of a
+ * Engine definition element.  This <code>RuleSet</code> does NOT include
+ * any rules for nested Host elements, which should be added via instances of
+ * <code>HostRuleSet</code>.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: EngineRuleSet.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+public class EngineRuleSet 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 EngineRuleSet() {
+
+        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 EngineRuleSet(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) {
+        
+        digester.addObjectCreate(prefix + "Engine",
+                                 "org.apache.catalina.core.StandardEngine",
+                                 "className");
+        digester.addSetProperties(prefix + "Engine");
+        digester.addRule(prefix + "Engine",
+                         new LifecycleListenerRule
+                         ("org.apache.catalina.startup.EngineConfig",
+                          "engineConfigClass"));
+        digester.addSetNext(prefix + "Engine",
+                            "setContainer",
+                            "org.apache.catalina.Container");
+
+        //Cluster configuration start
+        digester.addObjectCreate(prefix + "Engine/Cluster",
+                                 null, // MUST be specified in the element
+                                 "className");
+        digester.addSetProperties(prefix + "Engine/Cluster");
+        digester.addSetNext(prefix + "Engine/Cluster",
+                            "setCluster",
+                            "org.apache.catalina.Cluster");
+        //Cluster configuration end
+
+        digester.addObjectCreate(prefix + "Engine/Listener",
+                                 null, // MUST be specified in the element
+                                 "className");
+        digester.addSetProperties(prefix + "Engine/Listener");
+        digester.addSetNext(prefix + "Engine/Listener",
+                            "addLifecycleListener",
+                            "org.apache.catalina.LifecycleListener");
+
+
+        digester.addRuleSet(new RealmRuleSet(prefix + "Engine/"));
+
+        digester.addObjectCreate(prefix + "Engine/Valve",
+                                 null, // MUST be specified in the element
+                                 "className");
+        digester.addSetProperties(prefix + "Engine/Valve");
+        digester.addSetNext(prefix + "Engine/Valve",
+                            "addValve",
+                            "org.apache.catalina.Valve");
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/ExpandWar.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/ExpandWar.java
new file mode 100644
index 0000000..b93f5f0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/ExpandWar.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.catalina.startup;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.nio.channels.FileChannel;
+import java.util.Enumeration;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import org.apache.catalina.Globals;
+import org.apache.catalina.Host;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Expand out a WAR in a Host's appBase.
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @author Glenn L. Nielsen
+ * @version $Revision: 1.1 $
+ */
+
+public class ExpandWar {
+
+    private static final Log log = LogFactory.getLog(ExpandWar.class);
+
+    /**
+     * The string resources for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
+     * Expand the WAR file found at the specified URL into an unpacked
+     * directory structure, and return the absolute pathname to the expanded
+     * directory.
+     *
+     * @param host Host war is being installed for
+     * @param war URL of the web application archive to be expanded
+     *  (must start with "jar:")
+     * @param pathname Context path name for web application
+     *
+     * @exception IllegalArgumentException if this is not a "jar:" URL or if the
+     *            WAR file is invalid
+     * @exception IOException if an input/output error was encountered
+     *  during expansion
+     */
+    public static String expand(Host host, URL war, String pathname)
+        throws IOException {
+
+        // Make sure that there is no such directory already existing
+        File appBase = new File(host.getAppBase());
+        if (!appBase.isAbsolute()) {
+            appBase = new File(System.getProperty(Globals.CATALINA_BASE_PROP),
+                               host.getAppBase());
+        }
+        if (!appBase.exists() || !appBase.isDirectory()) {
+            throw new IOException
+                (sm.getString("hostConfig.appBase",
+                              appBase.getAbsolutePath()));
+        }
+        
+        File docBase = new File(appBase, pathname);
+        if (docBase.exists()) {
+            // War file is already installed
+            return (docBase.getAbsolutePath());
+        }
+
+        // Create the new document base directory
+        docBase.mkdir();
+
+        // Expand the WAR into the new document base directory
+        String canonicalDocBasePrefix = docBase.getCanonicalPath();
+        if (!canonicalDocBasePrefix.endsWith(File.separator)) {
+            canonicalDocBasePrefix += File.separator;
+        }
+        JarURLConnection juc = (JarURLConnection) war.openConnection();
+        juc.setUseCaches(false);
+        JarFile jarFile = null;
+        InputStream input = null;
+        boolean success = false;
+        try {
+            jarFile = juc.getJarFile();
+            Enumeration<JarEntry> jarEntries = jarFile.entries();
+            while (jarEntries.hasMoreElements()) {
+                JarEntry jarEntry = jarEntries.nextElement();
+                String name = jarEntry.getName();
+                File expandedFile = new File(docBase, name);
+                if (!expandedFile.getCanonicalPath().startsWith(
+                        canonicalDocBasePrefix)) {
+                    // Trying to expand outside the docBase
+                    // Throw an exception to stop the deployment
+                    throw new IllegalArgumentException(
+                            sm.getString("expandWar.illegalPath",war, name,
+                                    expandedFile.getCanonicalPath(),
+                                    canonicalDocBasePrefix));
+                }
+                int last = name.lastIndexOf('/');
+                if (last >= 0) {
+                    File parent = new File(docBase,
+                                           name.substring(0, last));
+                    parent.mkdirs();
+                }
+                if (name.endsWith("/")) {
+                    continue;
+                }
+                input = jarFile.getInputStream(jarEntry);
+
+                // Bugzilla 33636
+                expand(input, expandedFile);
+                long lastModified = jarEntry.getTime();
+                if ((lastModified != -1) && (lastModified != 0)) {
+                    expandedFile.setLastModified(lastModified);
+                }
+
+                input.close();
+                input = null;
+            }
+            success = true;
+        } catch (IOException e) {
+            throw e;
+        } finally {
+            if (!success) {
+                // If something went wrong, delete expanded dir to keep things 
+                // clean
+                deleteDir(docBase);
+            }
+            if (input != null) {
+                try {
+                    input.close();
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                }
+                input = null;
+            }
+            if (jarFile != null) {
+                try {
+                    jarFile.close();
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                }
+                jarFile = null;
+            }
+        }
+
+        // Return the absolute path to our new document base directory
+        return (docBase.getAbsolutePath());
+
+    }
+
+
+    /**
+     * Validate the WAR file found at the specified URL.
+     *
+     * @param host Host war is being installed for
+     * @param war URL of the web application archive to be validated
+     *  (must start with "jar:")
+     * @param pathname Context path name for web application
+     *
+     * @exception IllegalArgumentException if this is not a "jar:" URL or if the
+     *            WAR file is invalid
+     * @exception IOException if an input/output error was encountered
+     *            during validation
+     */
+    public static void validate(Host host, URL war, String pathname)
+        throws IOException {
+
+        // Make the appBase absolute
+        File appBase = new File(host.getAppBase());
+        if (!appBase.isAbsolute()) {
+            appBase = new File(System.getProperty(Globals.CATALINA_BASE_PROP),
+                               host.getAppBase());
+        }
+        
+        File docBase = new File(appBase, pathname);
+
+        // Calculate the document base directory
+        String canonicalDocBasePrefix = docBase.getCanonicalPath();
+        if (!canonicalDocBasePrefix.endsWith(File.separator)) {
+            canonicalDocBasePrefix += File.separator;
+        }
+        JarURLConnection juc = (JarURLConnection) war.openConnection();
+        juc.setUseCaches(false);
+        JarFile jarFile = null;
+        try {
+            jarFile = juc.getJarFile();
+            Enumeration<JarEntry> jarEntries = jarFile.entries();
+            while (jarEntries.hasMoreElements()) {
+                JarEntry jarEntry = jarEntries.nextElement();
+                String name = jarEntry.getName();
+                File expandedFile = new File(docBase, name);
+                if (!expandedFile.getCanonicalPath().startsWith(
+                        canonicalDocBasePrefix)) {
+                    // Entry located outside the docBase
+                    // Throw an exception to stop the deployment
+                    throw new IllegalArgumentException(
+                            sm.getString("expandWar.illegalPath",war, name,
+                                    expandedFile.getCanonicalPath(),
+                                    canonicalDocBasePrefix));
+                }
+            }
+        } catch (IOException e) {
+            throw e;
+        } finally {
+            if (jarFile != null) {
+                try {
+                    jarFile.close();
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                }
+                jarFile = null;
+            }
+        }
+    }
+
+
+    /**
+     * 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 = 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 = copy(fileSrc, fileDest);
+            } else {
+                FileChannel ic = null;
+                FileChannel oc = null;
+                try {
+                    ic = (new FileInputStream(fileSrc)).getChannel();
+                    oc = (new FileOutputStream(fileDest)).getChannel();
+                    ic.transferTo(0, ic.size(), oc);
+                } catch (IOException e) {
+                    log.error(sm.getString
+                            ("expandWar.copy", fileSrc, fileDest), e);
+                    result = false;
+                } finally {
+                    if (ic != null) {
+                        try {
+                            ic.close();
+                        } catch (IOException e) {
+                        }
+                    }
+                    if (oc != null) {
+                        try {
+                            oc.close();
+                        } catch (IOException e) {
+                        }
+                    }
+                }
+            }
+        }
+        return result;
+        
+    }
+    
+    
+    /**
+     * Delete the specified directory, including all of its contents and
+     * sub-directories recursively. Any failure will be logged.
+     *
+     * @param dir File object representing the directory to be deleted
+     */
+    public static boolean delete(File dir) {
+        // Log failure by default
+        return delete(dir, true);
+    }
+
+    /**
+     * Delete the specified directory, including all of its contents and
+     * sub-directories recursively.
+     *
+     * @param dir File object representing the directory to be deleted
+     * @param logFailure <code>true</code> if failure to delete the resource
+     *                   should be logged
+     */
+    public static boolean delete(File dir, boolean logFailure) {
+        boolean result;
+        if (dir.isDirectory()) {
+            result = deleteDir(dir, logFailure);
+        } else {
+            if (dir.exists()) {
+                result = dir.delete();
+            } else {
+                result = true;
+            }
+        }
+        if (logFailure && !result) {
+            log.error(sm.getString(
+                    "expandWar.deleteFailed", dir.getAbsolutePath()));
+        }
+        return result;
+    }
+    
+    
+    /**
+     * Delete the specified directory, including all of its contents and
+     * sub-directories recursively. Any failure will be logged.
+     *
+     * @param dir File object representing the directory to be deleted
+     */
+    public static boolean deleteDir(File dir) {
+        return deleteDir(dir, true);
+    }
+
+    /**
+     * Delete the specified directory, including all of its contents and
+     * sub-directories recursively.
+     *
+     * @param dir File object representing the directory to be deleted
+     * @param logFailure <code>true</code> if failure to delete the resource
+     *                   should be logged
+     */
+    public static boolean deleteDir(File dir, boolean logFailure) {
+
+        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, logFailure);
+            } else {
+                file.delete();
+            }
+        }
+
+        boolean result;
+        if (dir.exists()) {
+            result = dir.delete();
+        } else {
+            result = true;
+        }
+        
+        if (logFailure && !result) {
+            log.error(sm.getString(
+                    "expandWar.deleteFailed", dir.getAbsolutePath()));
+        }
+        
+        return result;
+
+    }
+
+
+    /**
+     * Expand the specified input stream into the specified file.
+     *
+     * @param input InputStream to be copied
+     * @param file The file to be created
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    private static void expand(InputStream input, File file)
+        throws IOException {
+        BufferedOutputStream output = null;
+        try {
+            output = 
+                new BufferedOutputStream(new FileOutputStream(file));
+            byte buffer[] = new byte[2048];
+            while (true) {
+                int n = input.read(buffer);
+                if (n <= 0)
+                    break;
+                output.write(buffer, 0, n);
+            }
+        } finally {
+            if (output != null) {
+                try {
+                    output.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/HomesUserDatabase.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/HomesUserDatabase.java
new file mode 100644
index 0000000..e4eae68
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/HomesUserDatabase.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.startup;
+
+
+import java.io.File;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+
+/**
+ * Concrete implementation of the <strong>UserDatabase</code> interface
+ * considers all directories in a directory whose pathname is specified
+ * to our constructor to be "home" directories for those users.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: HomesUserDatabase.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public final class HomesUserDatabase
+    implements UserDatabase {
+
+
+    // --------------------------------------------------------- Constructors
+
+
+    /**
+     * Initialize a new instance of this user database component.
+     */
+    public HomesUserDatabase() {
+
+        super();
+
+    }
+
+
+    // --------------------------------------------------- Instance Variables
+
+
+    /**
+     * The set of home directories for all defined users, keyed by username.
+     */
+    private Hashtable<String,String> homes = new Hashtable<String,String>();
+
+
+    /**
+     * The UserConfig listener with which we are associated.
+     */
+    private UserConfig userConfig = null;
+
+
+    // ----------------------------------------------------------- Properties
+
+
+    /**
+     * Return the UserConfig listener with which we are associated.
+     */
+    public UserConfig getUserConfig() {
+
+        return (this.userConfig);
+
+    }
+
+
+    /**
+     * Set the UserConfig listener with which we are associated.
+     *
+     * @param userConfig The new UserConfig listener
+     */
+    public void setUserConfig(UserConfig userConfig) {
+
+        this.userConfig = userConfig;
+        init();
+
+    }
+
+
+    // ------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return an absolute pathname to the home directory for the specified user.
+     *
+     * @param user User for which a home directory should be retrieved
+     */
+    public String getHome(String user) {
+
+        return homes.get(user);
+
+    }
+
+
+    /**
+     * Return an enumeration of the usernames defined on this server.
+     */
+    public Enumeration<String> getUsers() {
+
+        return (homes.keys());
+
+    }
+
+
+    // ------------------------------------------------------ Private Methods
+
+
+    /**
+     * Initialize our set of users and home directories.
+     */
+    private void init() {
+
+        String homeBase = userConfig.getHomeBase();
+        File homeBaseDir = new File(homeBase);
+        if (!homeBaseDir.exists() || !homeBaseDir.isDirectory())
+            return;
+        String homeBaseFiles[] = homeBaseDir.list();
+
+        for (int i = 0; i < homeBaseFiles.length; i++) {
+            File homeDir = new File(homeBaseDir, homeBaseFiles[i]);
+            if (!homeDir.isDirectory() || !homeDir.canRead())
+                continue;
+            homes.put(homeBaseFiles[i], homeDir.toString());
+        }
+
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/HostConfig.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/HostConfig.java
new file mode 100644
index 0000000..5b7f3ab
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/HostConfig.java
@@ -0,0 +1,1476 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.startup;
+
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+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.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.core.ContainerBase;
+import org.apache.catalina.core.StandardHost;
+import org.apache.catalina.util.ContextName;
+import org.apache.catalina.util.IOTools;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.digester.Digester;
+import org.apache.tomcat.util.modeler.Registry;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Startup event listener for a <b>Host</b> that configures the properties
+ * of that Host, and the associated defined contexts.
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @version $Id: HostConfig.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+public class HostConfig
+    implements LifecycleListener {
+    
+    private static final Log log = LogFactory.getLog( HostConfig.class );
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * App base.
+     */
+    protected File appBase = null;
+
+
+    /**
+     * Config base.
+     */
+    protected File configBase = null;
+
+
+    /**
+     * The Java class name of the Context configuration class we should use.
+     */
+    protected String configClass = "org.apache.catalina.startup.ContextConfig";
+
+
+    /**
+     * The Java class name of the Context implementation we should use.
+     */
+    protected String contextClass = "org.apache.catalina.core.StandardContext";
+
+
+    /**
+     * The Host we are associated with.
+     */
+    protected Host host = null;
+
+    
+    /**
+     * The JMX ObjectName of this component.
+     */
+    protected ObjectName oname = null;
+    
+
+    /**
+     * The string resources for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
+     * Should we deploy XML Context config files packaged with WAR files and
+     * directories?
+     */
+    protected boolean deployXML = false;
+
+
+    /**
+     * Should XML files be copied to $CATALINA_BASE/conf/<engine>/<host> by
+     * default when a web application is deployed?
+     */
+    protected boolean copyXML = false;
+    
+    
+    /**
+     * Should we unpack WAR files when auto-deploying applications in the
+     * <code>appBase</code> directory?
+     */
+    protected boolean unpackWARs = false;
+
+
+    /**
+     * Map of deployed applications.
+     */
+    protected HashMap<String, DeployedApplication> deployed =
+        new HashMap<String, DeployedApplication>();
+
+    
+    /**
+     * List of applications which are being serviced, and shouldn't be 
+     * deployed/undeployed/redeployed at the moment.
+     */
+    protected ArrayList<String> serviced = new ArrayList<String>();
+    
+
+    /**
+     * The <code>Digester</code> instance used to parse context descriptors.
+     */
+    protected static Digester digester = createDigester();
+
+    /**
+     * The list of Wars in the appBase to be ignored because they are invalid
+     * (e.g. contain /../ sequences).
+     */
+    protected Set<String> invalidWars = new HashSet<String>();
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the Context configuration class name.
+     */
+    public String getConfigClass() {
+
+        return (this.configClass);
+
+    }
+
+
+    /**
+     * Set the Context configuration class name.
+     *
+     * @param configClass The new Context configuration class name.
+     */
+    public void setConfigClass(String configClass) {
+
+        this.configClass = configClass;
+
+    }
+
+
+    /**
+     * Return the Context implementation class name.
+     */
+    public String getContextClass() {
+
+        return (this.contextClass);
+
+    }
+
+
+    /**
+     * Set the Context implementation class name.
+     *
+     * @param contextClass The new Context implementation class name.
+     */
+    public void setContextClass(String contextClass) {
+
+        this.contextClass = contextClass;
+
+    }
+
+
+    /**
+     * Return the deploy XML config file flag for this component.
+     */
+    public boolean isDeployXML() {
+
+        return (this.deployXML);
+
+    }
+
+
+    /**
+     * Set the deploy XML config file flag for this component.
+     *
+     * @param deployXML The new deploy XML flag
+     */
+    public void setDeployXML(boolean deployXML) {
+
+        this.deployXML= deployXML;
+
+    }
+
+
+    /**
+     * Return the copy XML config file flag for this component.
+     */
+    public boolean isCopyXML() {
+
+        return (this.copyXML);
+
+    }
+
+
+    /**
+     * Set the copy XML config file flag for this component.
+     *
+     * @param copyXML The new copy XML flag
+     */
+    public void setCopyXML(boolean copyXML) {
+
+        this.copyXML= copyXML;
+
+    }
+
+
+    /**
+     * Return the unpack WARs flag.
+     */
+    public boolean isUnpackWARs() {
+
+        return (this.unpackWARs);
+
+    }
+
+
+    /**
+     * Set the unpack WARs flag.
+     *
+     * @param unpackWARs The new unpack WARs flag
+     */
+    public void setUnpackWARs(boolean unpackWARs) {
+
+        this.unpackWARs = unpackWARs;
+
+    }
+    
+    
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Process the START event for an associated Host.
+     *
+     * @param event The lifecycle event that has occurred
+     */
+    @Override
+    public void lifecycleEvent(LifecycleEvent event) {
+
+        if (event.getType().equals(Lifecycle.PERIODIC_EVENT))
+            check();
+
+        // Identify the host we are associated with
+        try {
+            host = (Host) event.getLifecycle();
+            if (host instanceof StandardHost) {
+                setCopyXML(((StandardHost) host).isCopyXML());
+                setDeployXML(((StandardHost) host).isDeployXML());
+                setUnpackWARs(((StandardHost) host).isUnpackWARs());
+            }
+        } catch (ClassCastException e) {
+            log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
+            return;
+        }
+
+        // Process the event that has occurred
+        if (event.getType().equals(Lifecycle.START_EVENT))
+            start();
+        else if (event.getType().equals(Lifecycle.STOP_EVENT))
+            stop();
+
+    }
+
+    
+    /**
+     * Add a serviced application to the list.
+     */
+    public synchronized void addServiced(String name) {
+        serviced.add(name);
+    }
+    
+    
+    /**
+     * Is application serviced ?
+     * @return state of the application
+     */
+    public synchronized boolean isServiced(String name) {
+        return (serviced.contains(name));
+    }
+    
+
+    /**
+     * Removed a serviced application from the list.
+     */
+    public synchronized void removeServiced(String name) {
+        serviced.remove(name);
+    }
+
+    
+    /**
+     * Get the instant where an application was deployed.
+     * @return 0L if no application with that name is deployed, or the instant
+     * on which the application was deployed
+     */
+    public long getDeploymentTime(String name) {
+        DeployedApplication app = deployed.get(name);
+        if (app == null) {
+            return 0L;
+        }
+        
+        return app.timestamp;
+    }
+    
+    
+    /**
+     * Has the specified application been deployed? Note applications defined
+     * in server.xml will not have been deployed.
+     * @return <code>true</code> if the application has been deployed and
+     * <code>false</code> if the application has not been deployed or does not
+     * exist
+     */
+    public boolean isDeployed(String name) {
+        DeployedApplication app = deployed.get(name);
+        if (app == null) {
+            return false;
+        }
+        
+        return true;
+    }
+    
+    
+    // ------------------------------------------------------ Protected Methods
+
+    
+    /**
+     * Create the digester which will be used to parse context config files.
+     */
+    protected static Digester createDigester() {
+        Digester digester = new Digester();
+        digester.setValidating(false);
+        // Add object creation rule
+        digester.addObjectCreate("Context", "org.apache.catalina.core.StandardContext",
+            "className");
+        // Set the properties on that object (it doesn't matter if extra 
+        // properties are set)
+        digester.addSetProperties("Context");
+        return (digester);
+    }
+    
+    protected File returnCanonicalPath(String path) {
+        File file = new File(path);
+        File base = new File(System.getProperty(Globals.CATALINA_BASE_PROP));
+        if (!file.isAbsolute())
+            file = new File(base,path);
+        try {
+            return file.getCanonicalFile();
+        } catch (IOException e) {
+            return file;
+        }
+    }
+    
+
+    /**
+     * Return a File object representing the "application root" directory
+     * for our associated Host.
+     */
+    protected File appBase() {
+
+        if (appBase != null) {
+            return appBase;
+        }
+        
+        appBase = returnCanonicalPath(host.getAppBase());
+        return appBase;
+
+    }
+
+
+    /**
+     * Return a File object representing the "configuration root" directory
+     * for our associated Host.
+     */
+    protected File configBase() {
+
+        if (configBase != null) {
+            return configBase;
+        }
+        
+        if (host.getXmlBase()!=null) {
+            configBase = returnCanonicalPath(host.getXmlBase());
+        } else {
+            StringBuilder xmlDir = new StringBuilder("conf");
+            Container parent = host.getParent();
+            if (parent instanceof Engine) {
+                xmlDir.append('/');
+                xmlDir.append(parent.getName());
+            }
+            xmlDir.append('/');
+            xmlDir.append(host.getName());
+            configBase = returnCanonicalPath(xmlDir.toString());
+        }
+        return (configBase);
+
+    }
+
+    /**
+     * Get the name of the configBase.
+     * For use with JMX management.
+     */
+    public String getConfigBaseName() {
+        return configBase().getAbsolutePath();
+    }
+
+
+    /**
+     * Deploy applications for any directories or WAR files that are found
+     * in our "application root" directory.
+     */
+    protected void deployApps() {
+
+        File appBase = appBase();
+        File configBase = configBase();
+        String[] filteredAppPaths = filterAppPaths(appBase.list());
+        // Deploy XML descriptors from configBase
+        deployDescriptors(configBase, configBase.list());
+        // Deploy WARs, and loop if additional descriptors are found
+        deployWARs(appBase, filteredAppPaths);
+        // Deploy expanded folders
+        deployDirectories(appBase, filteredAppPaths);
+        
+    }
+
+
+    /**
+     * Filter the list of application file paths to remove those that match
+     * the regular expression defined by {@link Host#getDeployIgnore()}.
+     *  
+     * @param unfilteredAppPaths    The list of application paths to filtert
+     * 
+     * @return  The filtered list of application paths
+     */
+    protected String[] filterAppPaths(String[] unfilteredAppPaths) {
+        Pattern filter = host.getDeployIgnorePattern();
+        if (filter == null) {
+            return unfilteredAppPaths;
+        }
+
+        List<String> filteredList = new ArrayList<String>();
+        Matcher matcher = null;
+        for (String appPath : unfilteredAppPaths) {
+            if (matcher == null) {
+                matcher = filter.matcher(appPath);
+            } else {
+                matcher.reset(appPath);
+            }
+            if (matcher.matches()) {
+                if (log.isDebugEnabled()) {
+                    log.debug(sm.getString("hostConfig.ignorePath", appPath));
+                }
+            } else {
+                filteredList.add(appPath);
+            }
+        }
+        return filteredList.toArray(new String[filteredList.size()]);
+    }
+
+
+    /**
+     * Deploy applications for any directories or WAR files that are found
+     * in our "application root" directory.
+     */
+    protected void deployApps(String name) {
+
+        File appBase = appBase();
+        File configBase = configBase();
+        ContextName cn = new ContextName(name);
+        String baseName = cn.getBaseName();
+        
+        // Deploy XML descriptors from configBase
+        File xml = new File(configBase, baseName + ".xml");
+        if (xml.exists())
+            deployDescriptor(cn, xml, baseName + ".xml");
+        // Deploy WARs, and loop if additional descriptors are found
+        File war = new File(appBase, baseName + ".war");
+        if (war.exists())
+            deployWAR(cn, war, baseName + ".war");
+        // Deploy expanded folders
+        File dir = new File(appBase, baseName);
+        if (dir.exists())
+            deployDirectory(cn, dir, baseName);
+        
+    }
+
+
+    /**
+     * Deploy XML context descriptors.
+     */
+    protected void deployDescriptors(File configBase, String[] files) {
+
+        if (files == null)
+            return;
+        
+        for (int i = 0; i < files.length; i++) {
+            File contextXml = new File(configBase, files[i]);
+
+            if (files[i].toLowerCase(Locale.ENGLISH).endsWith(".xml")) {
+                ContextName cn = new ContextName(files[i]);
+                String name = cn.getName();
+
+                if (isServiced(name))
+                    continue;
+                
+                String file = files[i];
+
+                deployDescriptor(cn, contextXml, file);
+            }
+        }
+    }
+
+
+    /**
+     * @param cn
+     * @param contextXml
+     * @param file
+     */
+    protected void deployDescriptor(ContextName cn, File contextXml, String file) {
+        if (deploymentExists(cn.getName())) {
+            return;
+        }
+        
+        DeployedApplication deployedApp = new DeployedApplication(cn.getName());
+
+        // Assume this is a configuration descriptor and deploy it
+        if(log.isInfoEnabled()) {
+            log.info(sm.getString("hostConfig.deployDescriptor", file,
+                    configBase.getPath()));
+        }
+
+        Context context = null;
+        try {
+            synchronized (digester) {
+                try {
+                    context = (Context) digester.parse(contextXml);
+                    if (context == null) {
+                        log.error(sm.getString("hostConfig.deployDescriptor.error",
+                                file));
+                        return;
+                    }
+                } finally {
+                    digester.reset();
+                }
+            }
+
+            Class<?> clazz = Class.forName(host.getConfigClass());
+            LifecycleListener listener =
+                (LifecycleListener) clazz.newInstance();
+            context.addLifecycleListener(listener);
+
+            context.setConfigFile(contextXml.toURI().toURL());
+            context.setName(cn.getName());
+            context.setPath(cn.getPath());
+            context.setWebappVersion(cn.getVersion());
+            // Add the associated docBase to the redeployed list if it's a WAR
+            boolean isExternalWar = false;
+            boolean isExternal = false;
+            if (context.getDocBase() != null) {
+                File docBase = new File(context.getDocBase());
+                if (!docBase.isAbsolute()) {
+                    docBase = new File(appBase(), context.getDocBase());
+                }
+                // If external docBase, register .xml as redeploy first
+                if (!docBase.getCanonicalPath().startsWith(
+                        appBase().getAbsolutePath() + File.separator)) {
+                    isExternal = true;
+                    deployedApp.redeployResources.put(
+                            contextXml.getAbsolutePath(),
+                            Long.valueOf(contextXml.lastModified()));
+                    deployedApp.redeployResources.put(docBase.getAbsolutePath(),
+                            Long.valueOf(docBase.lastModified()));
+                    if (docBase.getAbsolutePath().toLowerCase(Locale.ENGLISH).endsWith(".war")) {
+                        isExternalWar = true;
+                    }
+                } else {
+                    log.warn(sm.getString("hostConfig.deployDescriptor.localDocBaseSpecified",
+                             docBase));
+                    // Ignore specified docBase
+                    context.setDocBase(null);
+                }
+            }
+            host.addChild(context);
+            // Get paths for WAR and expanded WAR in appBase
+
+            // default to appBase dir + name
+            File expandedDocBase = new File(appBase(), cn.getBaseName());
+            if (context.getDocBase() != null) {
+                // first assume docBase is absolute
+                expandedDocBase = new File(context.getDocBase());
+                if (!expandedDocBase.isAbsolute()) {
+                    // if docBase specified and relative, it must be relative to appBase
+                    expandedDocBase = new File(appBase(), context.getDocBase());
+                }
+            }
+            // Add the eventual unpacked WAR and all the resources which will be
+            // watched inside it
+            if (isExternalWar && unpackWARs) {
+                deployedApp.redeployResources.put(expandedDocBase.getAbsolutePath(),
+                        Long.valueOf(expandedDocBase.lastModified()));
+                deployedApp.redeployResources.put(contextXml.getAbsolutePath(),
+                        Long.valueOf(contextXml.lastModified()));
+                addWatchedResources(deployedApp, expandedDocBase.getAbsolutePath(), context);
+            } else {
+                // Find an existing matching war and expanded folder
+                if (!isExternal) {
+                    File warDocBase = new File(expandedDocBase.getAbsolutePath() + ".war");
+                    if (warDocBase.exists()) {
+                        deployedApp.redeployResources.put(warDocBase.getAbsolutePath(),
+                                Long.valueOf(warDocBase.lastModified()));
+                    }
+                }
+                if (expandedDocBase.exists()) {
+                    deployedApp.redeployResources.put(expandedDocBase.getAbsolutePath(),
+                            Long.valueOf(expandedDocBase.lastModified()));
+                    addWatchedResources(deployedApp, 
+                            expandedDocBase.getAbsolutePath(), context);
+                } else {
+                    addWatchedResources(deployedApp, null, context);
+                }
+                // Add the context XML to the list of files which should trigger a redeployment
+                if (!isExternal) {
+                    deployedApp.redeployResources.put(
+                            contextXml.getAbsolutePath(),
+                            Long.valueOf(contextXml.lastModified()));
+                }
+            }
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.error(sm.getString("hostConfig.deployDescriptor.error",
+                                   file), t);
+        }
+
+        if (context != null && host.findChild(context.getName()) != null) {
+            deployed.put(context.getName(), deployedApp);
+        }
+    }
+
+
+    /**
+     * Deploy WAR files.
+     */
+    protected void deployWARs(File appBase, String[] files) {
+        
+        if (files == null)
+            return;
+        
+        for (int i = 0; i < files.length; i++) {
+            
+            if (files[i].equalsIgnoreCase("META-INF"))
+                continue;
+            if (files[i].equalsIgnoreCase("WEB-INF"))
+                continue;
+            File dir = new File(appBase, files[i]);
+            if (files[i].toLowerCase(Locale.ENGLISH).endsWith(".war") && dir.isFile()
+                    && !invalidWars.contains(files[i]) ) {
+                
+                ContextName cn = new ContextName(files[i]);
+                
+                // Check for WARs with /../ /./ or similar sequences in the name
+                if (!validateContextPath(appBase, cn.getBaseName())) {
+                    log.error(sm.getString(
+                            "hostConfig.illegalWarName", files[i]));
+                    invalidWars.add(files[i]);
+                    continue;
+                }
+
+                if (isServiced(cn.getName()))
+                    continue;
+                
+                String file = files[i];
+                
+                deployWAR(cn, dir, file);
+            }
+        }
+    }
+
+
+    private boolean validateContextPath(File appBase, String contextPath) {
+        // More complicated than the ideal as the canonical path may or may
+        // not end with File.separator for a directory
+        
+        StringBuilder docBase;
+        String canonicalDocBase = null;
+        
+        try {
+            String canonicalAppBase = appBase.getCanonicalPath();
+            docBase = new StringBuilder(canonicalAppBase);
+            if (canonicalAppBase.endsWith(File.separator)) {
+                docBase.append(contextPath.substring(1).replace(
+                        '/', File.separatorChar));
+            } else {
+                docBase.append(contextPath.replace('/', File.separatorChar));
+            }
+            // At this point docBase should be canonical but will not end
+            // with File.separator
+            
+            canonicalDocBase =
+                (new File(docBase.toString())).getCanonicalPath();
+    
+            // If the canonicalDocBase ends with File.separator, add one to
+            // docBase before they are compared
+            if (canonicalDocBase.endsWith(File.separator)) {
+                docBase.append(File.separator);
+            }
+        } catch (IOException ioe) {
+            return false;
+        }
+        
+        // Compare the two. If they are not the same, the contextPath must
+        // have /../ like sequences in it 
+        return canonicalDocBase.equals(docBase.toString());
+    }
+
+    /**
+     * @param cn
+     * @param war
+     * @param file
+     */
+    protected void deployWAR(ContextName cn, File war, String file) {
+        
+        if (deploymentExists(cn.getName()))
+            return;
+        
+        // Checking for a nested /META-INF/context.xml
+        JarFile jar = null;
+        JarEntry entry = null;
+        InputStream istream = null;
+        BufferedOutputStream ostream = null;
+        File xml;
+        if (copyXML) {
+            xml = new File(configBase(),
+                    file.substring(0, file.lastIndexOf(".")) + ".xml");
+        } else {
+            xml = new File(appBase(),
+                    file.substring(0, file.lastIndexOf(".")) +
+                    "/META-INF/context.xml");
+        }
+        boolean xmlInWar = false;
+        
+        if (deployXML && !xml.exists()) {
+            try {
+                jar = new JarFile(war);
+                entry = jar.getJarEntry(Constants.ApplicationContextXml);
+                if (entry != null) {
+                    xmlInWar = true;
+                }
+                if ((copyXML || unpackWARs) && xmlInWar) {
+                    istream = jar.getInputStream(entry);
+                    
+                    ostream =
+                        new BufferedOutputStream
+                        (new FileOutputStream(xml), 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) {
+                /* Ignore */
+            } 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;
+                }
+                entry = null;
+                if (jar != null) {
+                    try {
+                        jar.close();
+                    } catch (Throwable t) {
+                        ExceptionUtils.handleThrowable(t);
+                    }
+                    jar = null;
+                }
+            }
+        }
+        
+        DeployedApplication deployedApp = new DeployedApplication(cn.getName());
+        
+        // Deploy the application in this WAR file
+        if(log.isInfoEnabled()) 
+            log.info(sm.getString("hostConfig.deployWar", file));
+
+        try {
+            Context context = null;
+            if (deployXML && xml.exists()) {
+                synchronized (digester) {
+                    try {
+                        context = (Context) digester.parse(xml);
+                        if (context == null) {
+                            log.error(sm.getString("hostConfig.deployDescriptor.error",
+                                    file));
+                            return;
+                        }
+                    } finally {
+                        digester.reset();
+                    }
+                }
+                context.setConfigFile(xml.toURI().toURL());
+            } else if (deployXML && xmlInWar) {
+                synchronized (digester) {
+                    try {
+                        jar = new JarFile(war);
+                        entry =
+                            jar.getJarEntry(Constants.ApplicationContextXml);
+                        istream = jar.getInputStream(entry);
+                        context = (Context) digester.parse(istream);
+
+                        if (context == null) {
+                            log.error(sm.getString(
+                                    "hostConfig.deployDescriptor.error",
+                                    file));
+                            return;
+                        }
+                        context.setConfigFile(new URL("jar:" +
+                                war.toURI().toString() + "!/" +
+                                Constants.ApplicationContextXml));
+                    } finally {
+                        if (istream != null) {
+                            try {
+                                istream.close();
+                            } catch (IOException e) {
+                                /* Ignore */
+                            }
+                            istream = null;
+                        }
+                        entry = null;
+                        if (jar != null) {
+                            try {
+                                jar.close();
+                            } catch (IOException e) {
+                                /* Ignore */
+                            }
+                            jar = null;
+                        }
+                        digester.reset();
+                    }
+                }
+            } else {
+                context = (Context) Class.forName(contextClass).newInstance();
+            }
+
+            // Populate redeploy resources with the WAR file
+            deployedApp.redeployResources.put
+                (war.getAbsolutePath(), Long.valueOf(war.lastModified()));
+
+            if (deployXML && xml.exists() && copyXML) {
+                deployedApp.redeployResources.put(xml.getAbsolutePath(),
+                        Long.valueOf(xml.lastModified()));
+            }
+
+            Class<?> clazz = Class.forName(host.getConfigClass());
+            LifecycleListener listener =
+                (LifecycleListener) clazz.newInstance();
+            context.addLifecycleListener(listener);
+
+            context.setName(cn.getName());
+            context.setPath(cn.getPath());
+            context.setWebappVersion(cn.getVersion());
+            context.setDocBase(file);
+            host.addChild(context);
+            // If we're unpacking WARs, the docBase will be mutated after
+            // starting the context
+            if (unpackWARs && (context.getDocBase() != null)) {
+                File docBase = new File(appBase(), cn.getBaseName());
+                deployedApp.redeployResources.put(docBase.getAbsolutePath(),
+                        Long.valueOf(docBase.lastModified()));
+                addWatchedResources(deployedApp, docBase.getAbsolutePath(),
+                        context);
+                if (deployXML && !copyXML && (xmlInWar || xml.exists())) {
+                    deployedApp.redeployResources.put(xml.getAbsolutePath(),
+                            Long.valueOf(xml.lastModified()));
+                }
+            } else {
+                addWatchedResources(deployedApp, null, context);
+            }
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.error(sm.getString("hostConfig.deployWar.error", file), t);
+        }
+        
+        deployed.put(cn.getName(), deployedApp);
+    }
+
+
+    /**
+     * Deploy directories.
+     */
+    protected void deployDirectories(File appBase, String[] files) {
+
+        if (files == null)
+            return;
+        
+        for (int i = 0; i < files.length; i++) {
+
+            if (files[i].equalsIgnoreCase("META-INF"))
+                continue;
+            if (files[i].equalsIgnoreCase("WEB-INF"))
+                continue;
+            File dir = new File(appBase, files[i]);
+            if (dir.isDirectory()) {
+                ContextName cn = new ContextName(files[i]);
+
+                if (isServiced(cn.getName()))
+                    continue;
+
+                deployDirectory(cn, dir, files[i]);
+            }
+        }
+    }
+
+    
+    /**
+     * @param cn
+     * @param dir
+     * @param file
+     */
+    protected void deployDirectory(ContextName cn, File dir, String file) {
+        
+        if (deploymentExists(cn.getName()))
+            return;
+
+        DeployedApplication deployedApp = new DeployedApplication(cn.getName());
+
+        // Deploy the application in this directory
+        if( log.isInfoEnabled() ) 
+            log.info(sm.getString("hostConfig.deployDir", file));
+        try {
+            Context context = null;
+            File xml = new File(dir, Constants.ApplicationContextXml);
+            File xmlCopy = null;
+            if (deployXML && xml.exists()) {
+                synchronized (digester) {
+                    try {
+                        context = (Context) digester.parse(xml);
+                        if (context == null) {
+                            log.error(sm.getString(
+                                    "hostConfig.deployDescriptor.error",
+                                    xml));
+                            return;
+                        }
+                    } finally {
+                        digester.reset();
+                    }
+                }
+                if (copyXML) {
+                    xmlCopy = new File(configBase(), file + ".xml");
+                    InputStream is = null;
+                    OutputStream os = null;
+                    try {
+                        is = new FileInputStream(xml);
+                        os = new FileOutputStream(xmlCopy);
+                        IOTools.flow(is, os);
+                        // Don't catch IOE - let the outer try/catch handle it
+                    } finally {
+                        try {
+                            if (is != null) is.close();
+                        } catch (IOException e){
+                            // Ignore
+                        }
+                        try {
+                            if (os != null) os.close();
+                        } catch (IOException e){
+                            // Ignore
+                        }
+                    }
+                    context.setConfigFile(xmlCopy.toURI().toURL());
+                } else {
+                    context.setConfigFile(xml.toURI().toURL());
+                }
+            } else {
+                context = (Context) Class.forName(contextClass).newInstance();
+            }
+
+            Class<?> clazz = Class.forName(host.getConfigClass());
+            LifecycleListener listener =
+                (LifecycleListener) clazz.newInstance();
+            context.addLifecycleListener(listener);
+
+            context.setName(cn.getName());
+            context.setPath(cn.getPath());
+            context.setWebappVersion(cn.getVersion());
+            context.setDocBase(file);
+            host.addChild(context);
+            deployedApp.redeployResources.put(dir.getAbsolutePath(),
+                    Long.valueOf(dir.lastModified()));
+            if (deployXML && xml.exists()) {
+                if (xmlCopy == null) {
+                    deployedApp.redeployResources.put(
+                            xml.getAbsolutePath(),
+                            Long.valueOf(xml.lastModified()));
+                } else {
+                    deployedApp.redeployResources.put(
+                            xmlCopy.getAbsolutePath(),
+                            Long.valueOf(xmlCopy.lastModified()));
+                }
+            }
+            addWatchedResources(deployedApp, dir.getAbsolutePath(), context);
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.error(sm.getString("hostConfig.deployDir.error", file), t);
+        }
+
+        deployed.put(cn.getName(), deployedApp);
+    }
+
+    
+    /**
+     * Check if a webapp is already deployed in this host.
+     * 
+     * @param contextName of the context which will be checked
+     */
+    protected boolean deploymentExists(String contextName) {
+        return (deployed.containsKey(contextName) ||
+                (host.findChild(contextName) != null));
+    }
+    
+
+    /**
+     * Add watched resources to the specified Context.
+     * @param app HostConfig deployed app
+     * @param docBase web app docBase
+     * @param context web application context
+     */
+    protected void addWatchedResources(DeployedApplication app, String docBase,
+            Context context) {
+        // FIXME: Feature idea. Add support for patterns (ex: WEB-INF/*,
+        //        WEB-INF/*.xml), where we would only check if at least one
+        //        resource is newer than app.timestamp
+        File docBaseFile = null;
+        if (docBase != null) {
+            docBaseFile = new File(docBase);
+            if (!docBaseFile.isAbsolute()) {
+                docBaseFile = new File(appBase(), docBase);
+            }
+        }
+        String[] watchedResources = context.findWatchedResources();
+        for (int i = 0; i < watchedResources.length; i++) {
+            File resource = new File(watchedResources[i]);
+            if (!resource.isAbsolute()) {
+                if (docBase != null) {
+                    resource = new File(docBaseFile, watchedResources[i]);
+                } else {
+                    if(log.isDebugEnabled())
+                        log.debug("Ignoring non-existent WatchedResource '" +
+                                resource.getAbsolutePath() + "'");
+                    continue;
+                }
+            }
+            if(log.isDebugEnabled())
+                log.debug("Watching WatchedResource '" +
+                        resource.getAbsolutePath() + "'");
+            app.reloadResources.put(resource.getAbsolutePath(), 
+                    Long.valueOf(resource.lastModified()));
+        }
+    }
+    
+
+    /**
+     * Check resources for redeployment and reloading.
+     */
+    protected synchronized void checkResources(DeployedApplication app) {
+        String[] resources =
+            app.redeployResources.keySet().toArray(new String[0]);
+        for (int i = 0; i < resources.length; i++) {
+            File resource = new File(resources[i]);
+            if (log.isDebugEnabled())
+                log.debug("Checking context[" + app.name +
+                        "] redeploy resource " + resource);
+            if (resource.exists()) {
+                long lastModified =
+                    app.redeployResources.get(resources[i]).longValue();
+                if ((!resource.isDirectory()) &&
+                        resource.lastModified() > lastModified) {
+                    // Undeploy application
+                    if (log.isInfoEnabled())
+                        log.info(sm.getString("hostConfig.undeploy", app.name));
+                    ContainerBase context =
+                        (ContainerBase) host.findChild(app.name);
+                    try {
+                        host.removeChild(context);
+                    } catch (Throwable t) {
+                        ExceptionUtils.handleThrowable(t);
+                        log.warn(sm.getString
+                                 ("hostConfig.context.remove", app.name), t);
+                    }
+                    // Delete other redeploy resources
+                    for (int j = i + 1; j < resources.length; j++) {
+                        try {
+                            File current = new File(resources[j]);
+                            current = current.getCanonicalFile();
+                            if ((current.getAbsolutePath().startsWith(
+                                    appBase().getAbsolutePath() +
+                                    File.separator))
+                                    || (current.getAbsolutePath().startsWith(
+                                            configBase().getAbsolutePath()))) {
+                                if (log.isDebugEnabled())
+                                    log.debug("Delete " + current);
+                                ExpandWar.delete(current);
+                            }
+                        } catch (IOException e) {
+                            log.warn(sm.getString
+                                    ("hostConfig.canonicalizing", app.name), e);
+                        }
+                    }
+                    deployed.remove(app.name);
+                    return;
+                }
+            } else {
+                // There is a chance the the resource was only missing
+                // temporarily eg renamed during a text editor save
+                try {
+                    Thread.sleep(500);
+                } catch (InterruptedException e1) {
+                    // Ignore
+                }
+                // Recheck the resource to see if it was really deleted
+                if (resource.exists()) {
+                    continue;
+                }
+                long lastModified =
+                    app.redeployResources.get(resources[i]).longValue();
+                if (lastModified == 0L) {
+                    continue;
+                }
+                // Undeploy application
+                if (log.isInfoEnabled())
+                    log.info(sm.getString("hostConfig.undeploy", app.name));
+                ContainerBase context = 
+                    (ContainerBase) host.findChild(app.name);
+                try {
+                    host.removeChild(context);
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    log.warn(sm.getString
+                             ("hostConfig.context.remove", app.name), t);
+                }
+                // Delete all redeploy resources
+                for (int j = i + 1; j < resources.length; j++) {
+                    try {
+                        File current = new File(resources[j]);
+                        current = current.getCanonicalFile();
+                        if ((current.getAbsolutePath().startsWith(
+                                appBase().getAbsolutePath() + File.separator))
+                            || (current.getAbsolutePath().startsWith(
+                                    configBase().getAbsolutePath()))) {
+                            if (log.isDebugEnabled())
+                                log.debug("Delete " + current);
+                            ExpandWar.delete(current);
+                        }
+                    } catch (IOException e) {
+                        log.warn(sm.getString
+                                ("hostConfig.canonicalizing", app.name), e);
+                    }
+                }
+                // Delete reload resources as well (to remove any remaining .xml
+                // descriptor)
+                String[] resources2 =
+                    app.reloadResources.keySet().toArray(new String[0]);
+                for (int j = 0; j < resources2.length; j++) {
+                    try {
+                        File current = new File(resources2[j]);
+                        current = current.getCanonicalFile();
+                        if ((current.getAbsolutePath().startsWith(
+                                appBase().getAbsolutePath() + File.separator))
+                            || ((current.getAbsolutePath().startsWith(
+                                    configBase().getAbsolutePath())
+                                 && (current.getAbsolutePath().endsWith(".xml"))))) {
+                            if (log.isDebugEnabled())
+                                log.debug("Delete " + current);
+                            ExpandWar.delete(current);
+                        }
+                    } catch (IOException e) {
+                        log.warn(sm.getString
+                                ("hostConfig.canonicalizing", app.name), e);
+                    }
+                }
+                deployed.remove(app.name);
+                return;
+            }
+        }
+        resources = app.reloadResources.keySet().toArray(new String[0]);
+        for (int i = 0; i < resources.length; i++) {
+            File resource = new File(resources[i]);
+            if (log.isDebugEnabled())
+                log.debug("Checking context[" + app.name +
+                        "] reload resource " + resource);
+            long lastModified =
+                app.reloadResources.get(resources[i]).longValue();
+            if ((!resource.exists() && lastModified != 0L) 
+                || (resource.lastModified() != lastModified)) {
+                // Reload application
+                if(log.isInfoEnabled())
+                    log.info(sm.getString("hostConfig.reload", app.name));
+                Container context = host.findChild(app.name);
+                try {
+                    // Might not have started if start failed last time
+                    if (context.getState().isAvailable()) {
+                        context.stop();
+                    }
+                } catch (Exception e) {
+                    log.warn(sm.getString
+                             ("hostConfig.context.restart", app.name), e);
+                }
+                // If the context was not started (for example an error 
+                // in web.xml) we'll still get to try to start
+                try {
+                    context.start();
+                } catch (Exception e) {
+                    log.warn(sm.getString
+                             ("hostConfig.context.restart", app.name), e);
+                }
+                // Update times
+                app.reloadResources.put(resources[i],
+                        Long.valueOf(resource.lastModified()));
+                app.timestamp = System.currentTimeMillis();
+                return;
+            }
+        }
+    }
+    
+    
+    /**
+     * Process a "start" event for this Host.
+     */
+    public void start() {
+
+        if (log.isDebugEnabled())
+            log.debug(sm.getString("hostConfig.start"));
+
+        try {
+            ObjectName hostON = host.getObjectName();
+            oname = new ObjectName
+                (hostON.getDomain() + ":type=Deployer,host=" + host.getName());
+            Registry.getRegistry(null, null).registerComponent
+                (this, oname, this.getClass().getName());
+        } catch (Exception e) {
+            log.error(sm.getString("hostConfig.jmx.register", oname), e);
+        }
+        
+        if (host.getCreateDirs()) {
+            File[] dirs = new File[] {appBase(),configBase()};
+            for (int i=0; i<dirs.length; i++) {
+                if ( (!dirs[i].exists()) && (!dirs[i].mkdirs())) {
+                    log.error(sm.getString("hostConfig.createDirs",dirs[i]));
+                }
+            }
+        }
+
+        if (host.getDeployOnStartup())
+            deployApps();
+        
+    }
+
+
+    /**
+     * Process a "stop" event for this Host.
+     */
+    public void stop() {
+
+        if (log.isDebugEnabled())
+            log.debug(sm.getString("hostConfig.stop"));
+
+        if (oname != null) {
+            try {
+                Registry.getRegistry(null, null).unregisterComponent(oname);
+            } catch (Exception e) {
+                log.error(sm.getString("hostConfig.jmx.unregister", oname), e);
+            }
+        }
+        oname = null;
+        appBase = null;
+        configBase = null;
+
+    }
+
+
+    /**
+     * Check status of all webapps.
+     */
+    protected void check() {
+
+        if (host.getAutoDeploy()) {
+            // Check for resources modification to trigger redeployment
+            DeployedApplication[] apps = 
+                deployed.values().toArray(new DeployedApplication[0]);
+            for (int i = 0; i < apps.length; i++) {
+                if (!isServiced(apps[i].name))
+                    checkResources(apps[i]);
+            }
+            // Hotdeploy applications
+            deployApps();
+        }
+
+    }
+
+    
+    /**
+     * Check status of a specific webapp, for use with stuff like management webapps.
+     */
+    public void check(String name) {
+        DeployedApplication app = deployed.get(name);
+        if (app != null) {
+            checkResources(app);
+        } else {
+            deployApps(name);
+        }
+    }
+
+    /**
+     * Add a new Context to be managed by us.
+     * Entry point for the admin webapp, and other JMX Context controllers.
+     */
+    public void manageApp(Context context)  {    
+
+        String contextName = context.getName();
+        
+        if (deployed.containsKey(contextName))
+            return;
+
+        DeployedApplication deployedApp = new DeployedApplication(contextName);
+        
+        // Add the associated docBase to the redeployed list if it's a WAR
+        boolean isWar = false;
+        if (context.getDocBase() != null) {
+            File docBase = new File(context.getDocBase());
+            if (!docBase.isAbsolute()) {
+                docBase = new File(appBase(), context.getDocBase());
+            }
+            deployedApp.redeployResources.put(docBase.getAbsolutePath(),
+                    Long.valueOf(docBase.lastModified()));
+            if (docBase.getAbsolutePath().toLowerCase(Locale.ENGLISH).endsWith(".war")) {
+                isWar = true;
+            }
+        }
+        host.addChild(context);
+        // Add the eventual unpacked WAR and all the resources which will be
+        // watched inside it
+        if (isWar && unpackWARs) {
+            File docBase = new File(appBase(), context.getBaseName());
+            deployedApp.redeployResources.put(docBase.getAbsolutePath(),
+                        Long.valueOf(docBase.lastModified()));
+            addWatchedResources(deployedApp, docBase.getAbsolutePath(), context);
+        } else {
+            addWatchedResources(deployedApp, null, context);
+        }
+        deployed.put(contextName, deployedApp);
+    }
+
+    /**
+     * Remove a webapp from our control.
+     * Entry point for the admin webapp, and other JMX Context controllers.
+     */
+    public void unmanageApp(String contextName) {
+        if(isServiced(contextName)) {
+            deployed.remove(contextName);
+            host.removeChild(host.findChild(contextName));
+        }
+    }
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * This class represents the state of a deployed application, as well as 
+     * the monitored resources.
+     */
+    protected static class DeployedApplication {
+        public DeployedApplication(String name) {
+            this.name = name;
+        }
+        
+        /**
+         * Application context path. The assertion is that 
+         * (host.getChild(name) != null).
+         */
+        public String name;
+        
+        /**
+         * Any modification of the specified (static) resources will cause a 
+         * redeployment of the application. If any of the specified resources is
+         * removed, the application will be undeployed. Typically, this will
+         * contain resources like the context.xml file, a compressed WAR path.
+         * The value is the last modification time.
+         */
+        public LinkedHashMap<String, Long> redeployResources =
+            new LinkedHashMap<String, Long>();
+
+        /**
+         * Any modification of the specified (static) resources will cause a 
+         * reload of the application. This will typically contain resources
+         * such as the web.xml of a webapp, but can be configured to contain
+         * additional descriptors.
+         * The value is the last modification time.
+         */
+        public HashMap<String, Long> reloadResources =
+            new HashMap<String, Long>();
+
+        /**
+         * Instant where the application was last put in service.
+         */
+     public long timestamp = System.currentTimeMillis();
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/HostRuleSet.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/HostRuleSet.java
new file mode 100644
index 0000000..dd83398
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/HostRuleSet.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.startup;
+
+
+import org.apache.tomcat.util.digester.Digester;
+import org.apache.tomcat.util.digester.RuleSetBase;
+
+
+/**
+ * <p><strong>RuleSet</strong> for processing the contents of a
+ * Host definition element.  This <code>RuleSet</code> does NOT include
+ * any rules for nested Context which should be added via instances of
+ * <code>ContextRuleSet</code>.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: HostRuleSet.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public class HostRuleSet 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 HostRuleSet() {
+
+        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 HostRuleSet(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) {
+
+        digester.addObjectCreate(prefix + "Host",
+                                 "org.apache.catalina.core.StandardHost",
+                                 "className");
+        digester.addSetProperties(prefix + "Host");
+        digester.addRule(prefix + "Host",
+                         new CopyParentClassLoaderRule());
+        digester.addRule(prefix + "Host",
+                         new LifecycleListenerRule
+                         ("org.apache.catalina.startup.HostConfig",
+                          "hostConfigClass"));
+        digester.addSetNext(prefix + "Host",
+                            "addChild",
+                            "org.apache.catalina.Container");
+
+        digester.addCallMethod(prefix + "Host/Alias",
+                               "addAlias", 0);
+
+        //Cluster configuration start
+        digester.addObjectCreate(prefix + "Host/Cluster",
+                                 null, // MUST be specified in the element
+                                 "className");
+        digester.addSetProperties(prefix + "Host/Cluster");
+        digester.addSetNext(prefix + "Host/Cluster",
+                            "setCluster",
+                            "org.apache.catalina.Cluster");
+        //Cluster configuration end
+
+        digester.addObjectCreate(prefix + "Host/Listener",
+                                 null, // MUST be specified in the element
+                                 "className");
+        digester.addSetProperties(prefix + "Host/Listener");
+        digester.addSetNext(prefix + "Host/Listener",
+                            "addLifecycleListener",
+                            "org.apache.catalina.LifecycleListener");
+
+        digester.addRuleSet(new RealmRuleSet(prefix + "Host/"));
+
+        digester.addObjectCreate(prefix + "Host/Valve",
+                                 null, // MUST be specified in the element
+                                 "className");
+        digester.addSetProperties(prefix + "Host/Valve");
+        digester.addSetNext(prefix + "Host/Valve",
+                            "addValve",
+                            "org.apache.catalina.Valve");
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/LifecycleListenerRule.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/LifecycleListenerRule.java
new file mode 100644
index 0000000..2118f15
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/LifecycleListenerRule.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.startup;
+
+
+import org.apache.catalina.Container;
+import org.apache.catalina.LifecycleListener;
+import org.apache.tomcat.util.IntrospectionUtils;
+import org.apache.tomcat.util.digester.Rule;
+import org.xml.sax.Attributes;
+
+
+/**
+ * Rule that creates a new {@link LifecycleListener} and associates it with the
+ * top object on the stack which must implement {@link Container}. The
+ * implementation class to be used is determined by:
+ * <ol>
+ * <li>Does the top element on the stack specify an implementation class using
+ *     the attribute specified when this rule was created?</li>
+ * <li>Does the parent {@link Container} of the {@link Container} on the top of
+ *     the stack specify an implementation class using the attribute specified
+ *     when this rule was created?</li>
+ * <li>Use the default implementation class specified when this rule was
+ *     created.</li>
+ * </ol>
+ */
+public class LifecycleListenerRule extends Rule {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new instance of this Rule.
+     *
+     * @param listenerClass Default name of the LifecycleListener
+     *  implementation class to be created
+     * @param attributeName Name of the attribute that optionally
+     *  includes an override name of the LifecycleListener class
+     */
+    public LifecycleListenerRule(String listenerClass, String attributeName) {
+
+        this.listenerClass = listenerClass;
+        this.attributeName = attributeName;
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The attribute name of an attribute that can override the
+     * implementation class name.
+     */
+    private String attributeName;
+
+
+    /**
+     * The name of the <code>LifecycleListener</code> implementation class.
+     */
+    private String listenerClass;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Handle the beginning of an XML element.
+     *
+     * @param attributes The attributes of this element
+     *
+     * @exception Exception if a processing error occurs
+     */
+    @Override
+    public void begin(String namespace, String name, Attributes attributes)
+        throws Exception {
+
+        Container c = (Container) digester.peek();
+        Container p = null;
+        Object obj = digester.peek(1);
+        if (obj instanceof Container) {
+            p = (Container) obj;
+        }
+
+        String className = null;
+        
+        // Check the container for the specified attribute
+        if (attributeName != null) {
+            String value = attributes.getValue(attributeName);
+            if (value != null)
+                className = value;
+        }
+
+        // Check the container's parent for the specified attribute
+        if (p != null && className == null) {
+            String configClass =
+                (String) IntrospectionUtils.getProperty(p, attributeName);
+            if (configClass != null && configClass.length() > 0) {
+                className = configClass;
+            }
+        }
+        
+        // Use the default
+        if (className == null) {
+            className = listenerClass;
+        }
+        
+        // Instantiate a new LifecyleListener implementation object
+        Class<?> clazz = Class.forName(className);
+        LifecycleListener listener =
+            (LifecycleListener) clazz.newInstance();
+
+        // Add this LifecycleListener to our associated component
+        c.addLifecycleListener(listener);
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/LocalStrings.properties
new file mode 100644
index 0000000..f003eff
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/LocalStrings.properties
@@ -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.
+
+catalina.configFail=Unable to load server configuration from [{0}]
+catalina.shutdownHookFail=The shutdown hook experienced an error while trying to stop the server
+catalina.stopServer=No shutdown port configured. Shut down server through OS signal. Server not shut down.
+contextConfig.altDDNotFound=alt-dd file {0} not found
+contextConfig.applicationUrl=Unable to determine URL for application web.xml
+contextConfig.applicationMissing=Missing application web.xml, using defaults only
+contextConfig.applicationParse=Parse error in application web.xml file at {0}
+contextConfig.applicationPosition=Occurred at line {0} column {1}
+contextConfig.applicationStart=Parsing application web.xml file at {0}
+contextConfig.authenticatorConfigured=Configured an authenticator for method {0}
+contextConfig.authenticatorInstantiate=Cannot instantiate an authenticator of class {0}
+contextConfig.authenticatorMissing=Cannot configure an authenticator for method {0}
+contextConfig.authenticatorResources=Cannot load authenticators mapping list
+contextConfig.badUrl=Unable to process context descriptor [{0}]
+contectConfig.baseError=Unable to determine $CATALINA_BASE
+contextConfig.cce=Lifecycle event data object {0} is not a Context
+contextConfig.contextClose=Error closing context.xml
+contextConfig.contextMissing=Missing context.xml: {0}
+contextConfig.contextParse=Parse error in context.xml for {0}
+contextConfig.defaultError=Error processed default web.xml named {0} at {1}
+contextConfig.defaultMissing=No global web.xml found
+contextConfig.defaultPosition=Occurred at line {0} column {1}
+contextConfig.destroy=ContextConfig: Destroying
+contextConfig.fileUrl=Unable to create a File object from the URL [{0}]
+contextConfig.fixDocBase=Exception fixing docBase for context [{0}] 
+contextConfig.init=ContextConfig: Initializing
+contextConfig.inputStreamFile=Unable to process file [{0}] for annotations
+contextConfig.inputStreamJar=Unable to process Jar entry [{0}] from Jar [{1}] for annotations
+contextConfig.inputStreamJndi=Unable to process resource element [{0}] for annotations
+contextConfig.invalidSci=The ServletContentInitializer [{0}] could not be created
+contextConfig.invalidSciHandlesTypes=Unable to load class [{0}] to check against the @HandlesTypes annotation of one or more ServletContentInitializers. 
+contextConfig.jarUrl=The connection created for URL [{0}] was not a JarUrlConnection
+contextConfig.jar=Unable to process resource [{0}] for annotations
+contextConfig.jndiUrl=Unable to process JNDI URL [{0}] for annotations
+contextConfig.jndiUrlNotDirContextConn=The connection created for URL [{0}] was not a DirContextURLConnection
+contextConfig.jspFile.error=JSP file {0} must start with a ''/'
+contextConfig.jspFile.warning=WARNING: JSP file {0} must start with a ''/'' in Servlet 2.4
+contextConfig.missingRealm=No Realm has been configured to authenticate against
+contextConfig.resourceJarFail=Failed to processes JAR found at URL [{0}] for static resources to be included in context with name [{0}]
+contextConfig.role.auth=WARNING: Security role name {0} used in an <auth-constraint> without being defined in a <security-role>
+contextConfig.role.link=WARNING: Security role name {0} used in a <role-link> without being defined in a <security-role>
+contextConfig.role.runas=WARNING: Security role name {0} used in a <run-as> without being defined in a <security-role>
+contextConfig.servletContainerInitializerFail=Failed to process JAR found at URL [{0}] for ServletContainerInitializers for context with name [{1}]
+contextConfig.start=ContextConfig: Processing START
+contextConfig.stop=ContextConfig: Processing STOP
+contextConfig.unavailable=Marking this application unavailable due to previous error(s)
+contextConfig.unknownUrlProtocol=The URL protocol [{0}] was not recognised during annotation processing. URL [{1}] was ignored.
+contextConfig.urlPatternValue=Both the UrlPattern and value attribute were set for the WebServlet annotation on class [{0}]
+contextConfig.webinfClassesUrl=Unable to determine URL for WEB-INF/classes
+contextConfig.xmlSettings=Context [{0}] will parse web.xml and web-fragment.xml files with validation:{1} and namespaceAware:{2}
+embedded.noEngines=No engines have been defined yet
+embedded.notmp=Cannot find specified temporary folder at {0}
+embedded.authenticatorNotInstanceOfValve=Specified Authenticator is not a Valve
+engineConfig.cce=Lifecycle event data object {0} is not an Engine
+engineConfig.start=EngineConfig: Processing START
+engineConfig.stop=EngineConfig: Processing STOP
+expandWar.copy=Error copying {0} to {1}
+expandWar.deleteFailed=[{0}] could not be completely deleted. The presence of the remaining files may cause problems
+expandWar.illegalPath=The archive [{0}] is malformed and will be ignored: an entry contains an illegal path [{1}] which was not expanded to [{2}] since that is outside of the defined docBase [{3}]
+hostConfig.appBase=Application base directory {0} does not exist
+hostConfig.canonicalizing=Error delete redeploy resources from context [{0}]
+hostConfig.cce=Lifecycle event data object {0} is not a Host
+hostConfig.context.remove=Error while removing context [{0}]
+hostConfig.context.restart=Error during context [{0}] restart
+hostConfig.createDirs=Unable to create directory for deployment: {0}
+hostConfig.deploy=Deploying web application directory {0}
+hostConfig.deployDescriptor=Deploying configuration descriptor {0} from {1}
+hostConfig.deployDescriptor.error=Error deploying configuration descriptor {0}
+hostConfig.deployDescriptor.localDocBaseSpecified=A docBase {0} inside the host appBase has been specified, and will be ignored
+hostConfig.deployDir=Deploying web application directory {0}
+hostConfig.deployDir.error=Error deploying web application directory {0}
+hostConfig.deployWar=Deploying web application archive {0}
+hostConfig.deployWar.error=Error deploying web application archive {0}
+hostConfig.deploy.error=Exception while deploying web application directory {0}
+hostConfig.deploying=Deploying discovered web applications
+hostConfig.expand=Expanding web application archive {0}
+hostConfig.expand.error=Exception while expanding web application archive {0}
+hostConfig.expanding=Expanding discovered web application archives
+hostConfig.ignorePath=Ignoring path [{0}] in appBase for automatic deployment
+hostConfig.illegalWarName=The war name [{0}] is invalid. The archive will be ignored.
+hostConfig.jmx.register=Register context [{0}] failed
+hostConfig.jmx.unregister=Unregister context [{0}] failed
+hostConfig.reload=Reloading context [{0}]
+hostConfig.removeXML=Context [{0}] is undeployed
+hostConfig.removeDIR=Directory {0} is undeployed
+hostConfig.removeWAR=War {0} is undeployed
+hostConfig.start=HostConfig: Processing START
+hostConfig.stop=HostConfig: Processing STOP
+hostConfig.undeploy=Undeploying context [{0}]
+hostConfig.undeploy.error=Error undeploying web application at context path {0}
+tldConfig.addListeners=Adding {0} listeners from TLD files
+tldConfig.cce=Lifecycle event data object {0} is not a Context
+tldConfig.dirFail=Failed to process directory [{0}] for TLD files
+tldConfig.dirScan=Scanning for TLD files in directory [{0}]
+tldConfig.execute=Error processing TLD files for context with name [{0}]
+tldConfig.jarFail=Failed to process JAR [{0}] for TLD files
+tldConfig.webinfFail=Failed to process TLD found at [{0}]
+tldConfig.webinfScan=Scanning WEB-INF for TLD files in [{0}]
+tldConfig.webxmlAdd=Adding path [{0}] for URI [{1}]
+tldConfig.webxmlFail=Failed to process TLD with path [{1}] and URI [{0}]
+tldConfig.webxmlSkip=Path [{1}] skipped since URI [{0}] is a duplicate
+tldConfig.webxmlStart=Scanning <taglib> elements in web.xml
+userConfig.database=Exception loading user database
+userConfig.deploy=Deploying web application for user {0}
+userConfig.deploying=Deploying user web applications
+userConfig.error=Error deploying web application for user {0}
+userConfig.start=UserConfig: Processing START
+userConfig.stop=UserConfig: Processing STOP
+webRuleSet.absoluteOrdering=<absolute-ordering> element not valid in web-fragment.xml and will be ignored
+webRuleSet.relativeOrdering=<ordering> element not valid in web.xml and will be ignored
+xmlErrorHandler.error=Non-fatal error [{0}] reported processing [{1}]. 
+xmlErrorHandler.warning=Warning [{0}] reported processing [{1}]. 
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/LocalStrings_es.properties
new file mode 100644
index 0000000..c42690e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/LocalStrings_es.properties
@@ -0,0 +1,80 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+contextConfig.altDDNotFound = fichero alt-dd {0} no hallado
+contextConfig.applicationMissing = Falta el archivo web.xml de la aplicaci\u00F3n. Utilizando los par\u00E1metros por defecto
+contextConfig.applicationParse = Error de evaluaci\u00F3n (parse) en el archivo web.xml de la aplicaci\u00F3n a {0}
+contextConfig.applicationPosition = Se ha producido en la l\u00EDnea {0} columna {1}
+contextConfig.authenticatorConfigured = Configuraci\u00F3n de un autentificador (authenticator) para el m\u00E9todo {0}
+contextConfig.authenticatorInstantiate = Imposible de instanciar un autenticador (authenticator) para la clase {0}
+contextConfig.authenticatorMissing = Imposible de configurar un autentificador (authenticator) para el m\u00E9todo {0}
+contextConfig.authenticatorResources = Imposible de cargar la lista de correspondencia de autenticadores (authenticators)
+contextConfig.cce = El objeto de los datos de evento de ciclo de vida (Lifecycle event data object) {0} no es un Contexto
+contextConfig.contextClose = Error cerrando context.xml\: {0}
+contextConfig.contextMissing = Falta context.xml\: {0}
+contextConfig.contextParse = Error de an\u00E1lisis en context.xml\: {0}
+contextConfig.defaultPosition = Se ha producido en la l\u00EDnea {0} columna {1}
+contextConfig.fixDocBase = Excepci\u00F3n arreglando docBase\: {0}
+contextConfig.init = ContextConfig\: Inicializando
+contextConfig.jspFile.error = El archivo JSP {0} debe de comenzar con ''/'
+contextConfig.jspFile.warning = AVISO\: El archivo JSP {0} debe de comenzar con ''/'' en Servlet 2.4
+contextConfig.missingRealm = Alg\u00FAn reino (realm) no ha sido configurado para realizar la autenticaci\u00F3n
+contextConfig.role.auth = ATENCI\u00D3N\: El nombre de papel de seguridad {0} es usado en un <auth-constraint> sin haber sido definido en <security-role>
+contextConfig.role.link = ATENCI\u00D3N\: El nombre de papel de seguridad {0} es usado en un <role-link> sin haber sido definido en <security-role>
+contextConfig.role.runas = ATENCI\u00D3N\: El nombre de papel de seguridad {0} es usado en un <run-as> sin haber sido definido en <security-role>
+contextConfig.start = "ContextConfig"\: Tratamiento del "START"
+contextConfig.stop = "ContextConfig"\: Tratamiento del "STOP"
+contextConfig.unavailable = Esta aplicaci\u00F3n est\u00E1 marcada como no disponible debido a los errores precedentes
+embedded.noEngines = Alg\u00FAn motor (engine) no ha sido a\u00FAn definido
+embedded.notmp = No puedo hallar carpeta temporal especificada en {0}
+embedded.authenticatorNotInstanceOfValve = El Autenticado especificado no es un V\u00E1lvula
+engineConfig.cce = El objeto de los datos de evento de ciclo de vida (Lifecycle event data object) {0} no es un motor (engine)
+engineConfig.start = "EngineConfig"\: Tratamiento del "START"
+engineConfig.stop = "EngineConfig"\: Tratamiento del "STOP"
+expandWar.copy = Error copiando {0} a {1}
+hostConfig.appBase = No existe el directorio base de la aplicaci\u00F3n {0}
+hostConfig.canonicalizing = Error al borrar redespliegue de recursos desde contexto [{0}]
+hostConfig.cce = El objeto de los datos de evento de ciclo de vida (Lifecycle event data object) {0} no es una m\u00E1quina (host)
+hostConfig.context.remove = Error al quitar contexto [{0}]
+hostConfig.context.restart = Error durante el arranque del contexto {0}
+hostConfig.deploy = Desplieque del directorio {0} de la aplicaci\u00F3n web
+hostConfig.deployDescriptor = Desplieque del descriptor de configuraci\u00F3n {0}
+hostConfig.deployDescriptor.error = Error durante el despliegue del descriptor de configuraci\u00F3n {0}
+hostConfig.deployDescriptor.localDocBaseSpecified = Se ha especificado un docBase {0} dentro del appBase de la m\u00E1quina y ser\u00E1 ignorado
+hostConfig.deployDir = Despliegue del directorio {0} de la aplicaci\u00F3n web
+hostConfig.deployDir.error = Error durante el despliegue del directorio {0} de la aplicaci\u00F3n web
+hostConfig.deployWar = Despliegue del archivo {0} de la aplicaci\u00F3n web
+hostConfig.deployWar.error = Error durante el despliegue del archivo {0} de la aplicaci\u00F3n web
+hostConfig.deploy.error = Excepci\u00F3n en el directorio {0} de la aplicaci\u00F3n web
+hostConfig.deploying = Desplegando aplicaciones web descubiertas
+hostConfig.expand = Descompresi\u00F3n del archivo {0} de la aplicaci\u00F3n web
+hostConfig.expand.error = Excepci\u00F3n durante la descompresi\u00F3n del archivo {0} de la aplicaci\u00F3n web
+hostConfig.expanding = Descubierta descompresi\u00F3n de archivos de aplicaciones web
+hostConfig.jmx.register = Fall\u00F3 el registro del contexto [{0}]
+hostConfig.jmx.unregister = Fall\u00F3 el desregistro del contexto [{0}]
+hostConfig.reload = Fall\u00F3 la recarga del contexto [{0}]
+hostConfig.removeXML = El context [{0}] est\u00E1 replegado
+hostConfig.removeDIR = El directorio [{0}] est\u00E1 replegado
+hostConfig.removeWAR = El War [{0}] est\u00E1 replegado
+hostConfig.start = "HostConfig"\: Tratamiento del "START"
+hostConfig.stop = "HostConfig"\: Tratamiento del "STOP"
+hostConfig.undeploy = Repliegue (undeploy) de la aplicaci\u00F3n web que tiene como trayectoria de contexto {0}
+hostConfig.undeploy.error = Error durante el repliegue (undeploy) de la aplicaci\u00F3n web que tiene como trayectoria de contexto {0}
+hostConfig.undeploying = Repliegue de las aplicaciones web desplegadas
+userConfig.database = Excepci\u00F3n durante la carga de base de datos de usuario
+userConfig.deploy = Despliegue de la aplicaci\u00F3n web para el usuario {0}
+userConfig.deploying = Desplegando aplicaciones web para el usuario
+userConfig.error = Error durante el despliegue de la aplicaci\u00F3n web para el usario {0}
+userConfig.start = "UserConfig"\: Tratamiento del "START"
+userConfig.stop = "UserConfig"\: Tratamiento del "STOP"
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/LocalStrings_fr.properties
new file mode 100644
index 0000000..321a3ec
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/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.
+
+contextConfig.applicationMissing=Le fichier web.xml de l''application est absent, utilisation des param\u00e8tres par d\u00e9faut
+contextConfig.applicationParse=Erreur d''\u00e9valuation (parse) dans le fichier web.xml de l''application \u00e0 {0}
+contextConfig.applicationPosition=S''est produite \u00e0 la ligne {0} colonne {1}
+contextConfig.authenticatorConfigured=Configuration d''un authentificateur (authenticator) pour la m\u00e9thode {0}
+contextConfig.authenticatorInstantiate=Impossible d''instancier un authentificateur (authenticator) pour la classe {0}
+contextConfig.authenticatorMissing=Impossible de configurer un authentificateur (authenticator) pour la m\u00e9thode {0}
+contextConfig.authenticatorResources=Impossible de charger la liste de correspondance des authentificateurs (authenticators)
+contextConfig.cce=L''objet donn\u00e9e \u00e9v\u00e8nement cycle de vie (Lifecycle event data object) {0} n''est pas un Contexte
+contextConfig.defaultPosition=S''est produite \u00e0 la ligne {0} colonne {1}
+contextConfig.jspFile.error=Le fichier JSP {0} doit commencer par un ''/''
+contextConfig.jspFile.warning=ATTENTION: Le fichier JSP {0} doit commencer par un  ''/'' dans l''API Servlet 2.4
+contextConfig.missingRealm=Aucun royaume (realm) n''a \u00e9t\u00e9 configur\u00e9 pour r\u00e9aliser l''authentification
+contextConfig.role.auth=ATTENTION: Le nom de r\u00f4le de s\u00e9curit\u00e9 {0} est utilis\u00e9 dans un <auth-constraint> sans avoir \u00e9t\u00e9 d\u00e9fini dans <security-role>
+contextConfig.role.link=ATTENTION: Le nom de r\u00f4le de s\u00e9curit\u00e9 {0} est utilis\u00e9 dans un <role-link> sans avoir \u00e9t\u00e9 d\u00e9fini dans <security-role>
+contextConfig.role.runas=ATTENTION: Le nom de r\u00f4le de s\u00e9curit\u00e9 {0} est utilis\u00e9 dans un <run-as> sans avoir \u00e9t\u00e9 d\u00e9fini dans <security-role>
+contextConfig.start="ContextConfig": Traitement du "START"
+contextConfig.stop="ContextConfig": Traitement du "STOP"
+contextConfig.unavailable=Cette application est marqu\u00e9e comme non disponible suite aux erreurs pr\u00e9c\u00e9dentes
+embedded.noEngines=Aucun moteur (engine) n''a encore \u00e9t\u00e9 d\u00e9fini
+engineConfig.cce=L''objet donn\u00e9e \u00e9v\u00e8nement cycle de vie (Lifecycle event data object) {0} n''est pas un moteur (engine)
+engineConfig.start="EngineConfig": Traitement du "START"
+engineConfig.stop="EngineConfig": Traitement du "STOP"
+hostConfig.cce=L''objet donn\u00e9e \u00e9v\u00e8nement cycle de vie (Lifecycle event data object) {0} n''est pas un h\u00f4te
+hostConfig.deploy=D\u00e9ploiement du r\u00e9pertoire {0} de l''application web
+hostConfig.deployDescriptor=D\u00e9ploiement du descripteur de configuration {0}
+hostConfig.deployDescriptor.error=Erreur lors du d\u00e9ploiement du descripteur de configuration {0}
+hostConfig.deployDir=D\u00e9ploiement du r\u00e9pertoire {0} de l''application web
+hostConfig.deployDir.error=Erreur lors du d\u00e9ploiement du r\u00e9pertoire {0} de l''application web
+hostConfig.deployWar=D\u00e9ploiement de l''archive {0} de l''application web
+hostConfig.deployWar.error=Erreur lors du d\u00e9ploiement de l''archive {0} de l''application web
+hostConfig.deploy.error=Exception lors du r\u00e9pertoire {0} de l''application web
+hostConfig.deploying=D\u00e9ploiement des applications web d\u00e9couvertes (discovered)
+hostConfig.expand=D\u00e9compression de l''archive {0} de l''application web
+hostConfig.expand.error=Exception lors de la d\u00e9compression de l''archive {0} de l''application web
+hostConfig.expanding=D\u00e9compression des archives des applications web d\u00e9couvertes (discovered)
+hostConfig.start="HostConfig": Traitement du "START"
+hostConfig.stop="HostConfig": Traitement du "STOP"
+hostConfig.undeploy=Repli (undeploy) de l''application web ayant pour chemin de contexte {0}
+hostConfig.undeploy.error=Erreur lors du repli (undeploy) de l''application web ayant pour chemin de contexte {0}
+hostConfig.undeploying=Repli des applications web d\u00e9ploy\u00e9es
+userConfig.database=Exception lors du chargement de la base de donn\u00e9es utilisateur
+userConfig.deploy=D\u00e9ploiement de l''application web pour l''utilisateur {0}
+userConfig.deploying=D\u00e9ploiement des applications web utilisateur
+userConfig.error=Erreur lors du d\u00e9ploiement de l''application web pour l''utilisateur {0}
+userConfig.start="UserConfig": Traitement du "START"
+userConfig.stop="UserConfig": Traitement du "STOP"
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/LocalStrings_ja.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/LocalStrings_ja.properties
new file mode 100644
index 0000000..a0edb38
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/LocalStrings_ja.properties
@@ -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.
+
+contextConfig.applicationMissing=\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306eweb.xml\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3001\u30c7\u30d5\u30a9\u30eb\u30c8\u3060\u3051\u3092\u4f7f\u7528\u3057\u307e\u3059
+contextConfig.applicationParse=\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306eweb.xml\u30d5\u30a1\u30a4\u30eb {0} \u306e\u89e3\u6790\u30a8\u30e9\u30fc\u3067\u3059
+contextConfig.applicationPosition={0}\u884c\u306e{1}\u5217\u76ee\u3067\u767a\u751f\u3057\u307e\u3057\u305f
+contextConfig.authenticatorConfigured=\u30e1\u30bd\u30c3\u30c9 {0} \u306e\u30aa\u30fc\u30bb\u30f3\u30c6\u30a3\u30b1\u30fc\u30bf\u3092\u8a2d\u5b9a\u3057\u307e\u3059
+contextConfig.authenticatorInstantiate=\u30af\u30e9\u30b9 {0} \u306e\u30aa\u30fc\u30bb\u30f3\u30c6\u30a3\u30b1\u30fc\u30bf\u3092\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u5316\u3067\u304d\u307e\u305b\u3093
+contextConfig.authenticatorMissing=\u30e1\u30bd\u30c3\u30c9 {0} \u306e\u30aa\u30fc\u30bb\u30f3\u30c6\u30a3\u30b1\u30fc\u30bf\u3092\u8a2d\u5b9a\u3067\u304d\u307e\u305b\u3093
+contextConfig.authenticatorResources=\u30aa\u30fc\u30bb\u30f3\u30c6\u30a3\u30b1\u30fc\u30bf\u306e\u30de\u30c3\u30d7\u30ea\u30b9\u30c8\u3092\u30ed\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093
+contextConfig.cce=\u30e9\u30a4\u30d5\u30b5\u30a4\u30af\u30eb\u30a4\u30d9\u30f3\u30c8\u30c7\u30fc\u30bf\u30aa\u30d6\u30b8\u30a7\u30af\u30c8 {0} \u306f\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u3067\u306f\u3042\u308a\u307e\u305b\u3093
+contextConfig.defaultPosition={0}\u884c\u306e{1}\u5217\u76ee\u3067\u767a\u751f\u3057\u307e\u3057\u305f
+contextConfig.jspFile.error=JSP\u30d5\u30a1\u30a4\u30eb {0} \u306f''/''\u3067\u59cb\u307e\u3089\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+contextConfig.jspFile.warning=\u8b66\u544a: Servlet 2.4\u3067\u306fJSP\u30d5\u30a1\u30a4\u30eb {0} \u306f''/''\u3067\u59cb\u307e\u3089\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+contextConfig.missingRealm=\u8a8d\u8a3c\u3059\u308b\u305f\u3081\u306b\u30ec\u30eb\u30e0\u304c\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+contextConfig.role.auth=\u8b66\u544a: <security-role>\u306b\u5b9a\u7fa9\u3055\u308c\u3066\u3044\u306a\u3044\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30ed\u30fc\u30eb\u540d {0} \u304c<auth-constraint>\u306e\u4e2d\u3067\u4f7f\u7528\u3055\u308c\u307e\u3057\u305f
+contextConfig.role.link=\u8b66\u544a: <security-role>\u306b\u5b9a\u7fa9\u3055\u308c\u3066\u3044\u306a\u3044\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30ed\u30fc\u30eb\u540d {0} \u304c<role-link>\u306e\u4e2d\u3067\u4f7f\u7528\u3055\u308c\u307e\u3057\u305f
+contextConfig.role.runas=\u8b66\u544a: <security-role>\u306b\u5b9a\u7fa9\u3055\u308c\u3066\u3044\u306a\u3044\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30ed\u30fc\u30eb\u540d {0} \u304c<run-as>\u306e\u4e2d\u3067\u4f7f\u7528\u3055\u308c\u307e\u3057\u305f
+contextConfig.start=ContextConfig: \u51e6\u7406\u3092\u958b\u59cb\u3057\u307e\u3059
+contextConfig.stop=ContextConfig: \u51e6\u7406\u3092\u505c\u6b62\u3057\u307e\u3059
+contextConfig.unavailable=\u524d\u306e\u30a8\u30e9\u30fc\u306e\u305f\u3081\u306b\u3053\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306f\u5229\u7528\u3067\u304d\u306a\u3044\u3088\u3046\u306b\u30de\u30fc\u30af\u3057\u307e\u3059
+embedded.noEngines=\u307e\u3060\u30a8\u30f3\u30b8\u30f3\u304c\u5b9a\u7fa9\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+engineConfig.cce=\u30e9\u30a4\u30d5\u30b5\u30a4\u30af\u30eb\u30a4\u30d9\u30f3\u30c8\u30c7\u30fc\u30bf\u30aa\u30d6\u30b8\u30a7\u30af\u30c8 {0} \u306f\u30a8\u30f3\u30b8\u30f3\u3067\u306f\u3042\u308a\u307e\u305b\u3093
+engineConfig.start=EngineConfig: \u51e6\u7406\u3092\u958b\u59cb\u3057\u307e\u3059
+engineConfig.stop=EngineConfig: \u51e6\u7406\u3092\u505c\u6b62\u3057\u307e\u3059
+hostConfig.appBase=\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30d9\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea {0} \u306f\u5b58\u5728\u3057\u307e\u305b\u3093
+hostConfig.cce=\u30e9\u30a4\u30d5\u30b5\u30a4\u30af\u30eb\u30a4\u30d9\u30f3\u30c8\u30c7\u30fc\u30bf\u30aa\u30d6\u30b8\u30a7\u30af\u30c8 {0} \u306f\u30db\u30b9\u30c8\u3067\u306f\u3042\u308a\u307e\u305b\u3093
+hostConfig.deploy=Web\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30c7\u30a3\u30ec\u30af\u30c8\u30ea {0} \u3092\u914d\u5099\u3057\u307e\u3059
+hostConfig.deployDescriptor=\u8a2d\u5b9a\u8a18\u8ff0\u5b50 {0} \u3092\u914d\u5099\u3057\u307e\u3059
+hostConfig.deployDescriptor.error=\u8a2d\u5b9a\u8a18\u8ff0\u5b50 {0} \u3092\u914d\u5099\u4e2d\u306e\u30a8\u30e9\u30fc\u3067\u3059
+hostConfig.deployDir=Web\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30c7\u30a3\u30ec\u30af\u30c8\u30ea {0} \u3092\u914d\u5099\u3057\u307e\u3059
+hostConfig.deployDir.error=Web\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30c7\u30a3\u30ec\u30af\u30c8\u30ea {0} \u3092\u914d\u5099\u4e2d\u306e\u30a8\u30e9\u30fc\u3067\u3059
+hostConfig.deployWar=Web\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30a2\u30fc\u30ab\u30a4\u30d6 {0} \u3092\u914d\u5099\u3057\u307e\u3059
+hostConfig.deployWar.error=Web\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30a2\u30fc\u30ab\u30a4\u30d6 {0} \u3092\u914d\u5099\u4e2d\u306e\u30a8\u30e9\u30fc\u3067\u3059
+hostConfig.deploy.error=Web\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30c7\u30a3\u30ec\u30af\u30c8\u30ea {0} \u3092\u914d\u5099\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+hostConfig.deploying=\u898b\u3064\u304b\u3063\u305fWeb\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u914d\u5099\u3057\u307e\u3059
+hostConfig.expand=Web\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30a2\u30fc\u30ab\u30a4\u30d6 {0} \u3092\u5c55\u958b\u3057\u307e\u3059
+hostConfig.expand.error=Web\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30a2\u30fc\u30ab\u30a4\u30d6 {0} \u3092\u5c55\u958b\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+hostConfig.expanding=\u898b\u3064\u304b\u3063\u305fWeb\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30a2\u30fc\u30ab\u30a4\u30d6\u3092\u5c55\u958b\u3057\u307e\u3059
+hostConfig.context.restart=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8 {0} \u3092\u518d\u8d77\u52d5\u4e2d\u306e\u30a8\u30e9\u30fc\u3067\u3059
+hostConfig.removeXML=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8 {0} \u306e\u914d\u5099\u3092\u89e3\u9664\u3057\u307e\u3057\u305f
+hostConfig.removeDIR=\u30c7\u30a3\u30ec\u30af\u30c8\u30ea {0} \u306e\u914d\u5099\u3092\u89e3\u9664\u3057\u307e\u3057\u305f
+hostConfig.removeWAR=WAR\u30d5\u30a1\u30a4\u30eb {0} \u306e\u914d\u5099\u3092\u89e3\u9664\u3057\u307e\u3057\u305f
+hostConfig.start=HostConfig: \u51e6\u7406\u3092\u505c\u6b62\u3057\u307e\u3059
+hostConfig.stop=HostConfig: \u51e6\u7406\u3092\u505c\u6b62\u3057\u307e\u3059
+hostConfig.undeploy=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306eWeb\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u914d\u5099\u3092\u89e3\u9664\u3057\u307e\u3059
+hostConfig.undeploy.error=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306eWeb\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u914d\u5099\u3092\u89e3\u9664\u4e2d\u306e\u30a8\u30e9\u30fc\u3067\u3059
+hostConfig.undeploying=\u914d\u5099\u3055\u308c\u305fWeb\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u914d\u5099\u3092\u89e3\u9664\u3057\u3066\u3044\u307e\u3059
+userConfig.database=\u30e6\u30fc\u30b6\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306e\u30ed\u30fc\u30c9\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+userConfig.deploy=\u30e6\u30fc\u30b6 {0} \u306eWeb\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u914d\u5099\u3057\u307e\u3059
+userConfig.deploying=\u30e6\u30fc\u30b6\u306eWeb\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u914d\u5099\u3057\u307e\u3059
+userConfig.error=\u30e6\u30fc\u30b6 {0} \u306eWeb\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u914d\u5099\u4e2d\u306e\u30a8\u30e9\u30fc\u3067\u3059
+userConfig.start=UserConfig: \u51e6\u7406\u3092\u958b\u59cb\u3057\u307e\u3059
+userConfig.stop=UserConfig: \u51e6\u7406\u3092\u505c\u6b62\u3057\u307e\u3059
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/NamingRuleSet.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/NamingRuleSet.java
new file mode 100644
index 0000000..3e6eebe
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/NamingRuleSet.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.startup;
+
+
+import org.apache.tomcat.util.digester.Digester;
+import org.apache.tomcat.util.digester.RuleSetBase;
+
+
+/**
+ * <p><strong>RuleSet</strong> for processing the JNDI Enterprise Naming
+ * Context resource declaration elements.</p>
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @version $Id: NamingRuleSet.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+public class NamingRuleSet 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 NamingRuleSet() {
+
+        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 NamingRuleSet(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) {
+
+        digester.addObjectCreate(prefix + "Ejb",
+                                 "org.apache.catalina.deploy.ContextEjb");
+        digester.addRule(prefix + "Ejb", new SetAllPropertiesRule());
+        digester.addRule(prefix + "Ejb",
+                new SetNextNamingRule("addEjb",
+                            "org.apache.catalina.deploy.ContextEjb"));
+
+        digester.addObjectCreate(prefix + "Environment",
+                                 "org.apache.catalina.deploy.ContextEnvironment");
+        digester.addSetProperties(prefix + "Environment");
+        digester.addRule(prefix + "Environment",
+                            new SetNextNamingRule("addEnvironment",
+                            "org.apache.catalina.deploy.ContextEnvironment"));
+
+        digester.addObjectCreate(prefix + "LocalEjb",
+                                 "org.apache.catalina.deploy.ContextLocalEjb");
+        digester.addRule(prefix + "LocalEjb", new SetAllPropertiesRule());
+        digester.addRule(prefix + "LocalEjb",
+                new SetNextNamingRule("addLocalEjb",
+                            "org.apache.catalina.deploy.ContextLocalEjb"));
+
+        digester.addObjectCreate(prefix + "Resource",
+                                 "org.apache.catalina.deploy.ContextResource");
+        digester.addRule(prefix + "Resource", new SetAllPropertiesRule());
+        digester.addRule(prefix + "Resource",
+                new SetNextNamingRule("addResource",
+                            "org.apache.catalina.deploy.ContextResource"));
+
+        digester.addObjectCreate(prefix + "ResourceEnvRef",
+            "org.apache.catalina.deploy.ContextResourceEnvRef");
+        digester.addRule(prefix + "ResourceEnvRef", new SetAllPropertiesRule());
+        digester.addRule(prefix + "ResourceEnvRef",
+                new SetNextNamingRule("addResourceEnvRef",
+                            "org.apache.catalina.deploy.ContextResourceEnvRef"));
+
+        digester.addObjectCreate(prefix + "ServiceRef",
+            "org.apache.catalina.deploy.ContextService");
+        digester.addRule(prefix + "ServiceRef", new SetAllPropertiesRule());
+        digester.addRule(prefix + "ServiceRef",
+                new SetNextNamingRule("addService",
+                            "org.apache.catalina.deploy.ContextService"));
+
+        digester.addObjectCreate(prefix + "Transaction",
+            "org.apache.catalina.deploy.ContextTransaction");
+        digester.addRule(prefix + "Transaction", new SetAllPropertiesRule());
+        digester.addRule(prefix + "Transaction",
+                new SetNextNamingRule("setTransaction",
+                            "org.apache.catalina.deploy.ContextTransaction"));
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/PasswdUserDatabase.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/PasswdUserDatabase.java
new file mode 100644
index 0000000..2c7b9ae
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/PasswdUserDatabase.java
@@ -0,0 +1,194 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.startup;
+
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+
+/**
+ * Concrete implementation of the <strong>UserDatabase</code> interface
+ * that processes the <code>/etc/passwd</code> file on a Unix system.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: PasswdUserDatabase.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public final class PasswdUserDatabase
+    implements UserDatabase {
+
+
+    // --------------------------------------------------------- Constructors
+
+
+    /**
+     * Initialize a new instance of this user database component.
+     */
+    public PasswdUserDatabase() {
+
+        super();
+
+    }
+
+
+    // --------------------------------------------------- Instance Variables
+
+
+    /**
+     * The pathname of the Unix password file.
+     */
+    private static final String PASSWORD_FILE = "/etc/passwd";
+
+
+    /**
+     * The set of home directories for all defined users, keyed by username.
+     */
+    private Hashtable<String,String> homes = new Hashtable<String,String>();
+
+
+    /**
+     * The UserConfig listener with which we are associated.
+     */
+    private UserConfig userConfig = null;
+
+
+    // ----------------------------------------------------------- Properties
+
+
+    /**
+     * Return the UserConfig listener with which we are associated.
+     */
+    public UserConfig getUserConfig() {
+
+        return (this.userConfig);
+
+    }
+
+
+    /**
+     * Set the UserConfig listener with which we are associated.
+     *
+     * @param userConfig The new UserConfig listener
+     */
+    public void setUserConfig(UserConfig userConfig) {
+
+        this.userConfig = userConfig;
+        init();
+
+    }
+
+
+    // ------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return an absolute pathname to the home directory for the specified user.
+     *
+     * @param user User for which a home directory should be retrieved
+     */
+    public String getHome(String user) {
+
+        return homes.get(user);
+
+    }
+
+
+    /**
+     * Return an enumeration of the usernames defined on this server.
+     */
+    public Enumeration<String> getUsers() {
+
+        return (homes.keys());
+
+    }
+
+
+    // ------------------------------------------------------ Private Methods
+
+
+    /**
+     * Initialize our set of users and home directories.
+     */
+    private void init() {
+
+        BufferedReader reader = null;
+        try {
+
+            reader = new BufferedReader(new FileReader(PASSWORD_FILE));
+
+            while (true) {
+
+                // Accumulate the next line
+                StringBuilder buffer = new StringBuilder();
+                while (true) {
+                    int ch = reader.read();
+                    if ((ch < 0) || (ch == '\n'))
+                        break;
+                    buffer.append((char) ch);
+                }
+                String line = buffer.toString();
+                if (line.length() < 1)
+                    break;
+
+                // Parse the line into constituent elements
+                int n = 0;
+                String tokens[] = new String[7];
+                for (int i = 0; i < tokens.length; i++)
+                    tokens[i] = null;
+                while (n < tokens.length) {
+                    String token = null;
+                    int colon = line.indexOf(':');
+                    if (colon >= 0) {
+                        token = line.substring(0, colon);
+                        line = line.substring(colon + 1);
+                    } else {
+                        token = line;
+                        line = "";
+                    }
+                    tokens[n++] = token;
+                }
+
+                // Add this user and corresponding directory
+                if ((tokens[0] != null) && (tokens[5] != null))
+                    homes.put(tokens[0], tokens[5]);
+
+            }
+
+            reader.close();
+            reader = null;
+
+        } catch (Exception e) {
+            if (reader != null) {
+                try {
+                    reader.close();
+                } catch (IOException f) {
+                    // Ignore
+                }
+                reader = null;
+            }
+        }
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/RealmRuleSet.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/RealmRuleSet.java
new file mode 100644
index 0000000..6177f6b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/RealmRuleSet.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.catalina.startup;
+
+
+import org.apache.tomcat.util.digester.Digester;
+import org.apache.tomcat.util.digester.RuleSetBase;
+
+
+/**
+ * <p><strong>RuleSet</strong> for processing the contents of a Realm definition
+ * element.  This <code>RuleSet</code> supports Realms such as the
+ * <code>CombinedRealm</code> that used nested Realms.</p>
+ *
+ * @version $Id: RealmRuleSet.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+public class RealmRuleSet 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 RealmRuleSet() {
+
+        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 RealmRuleSet(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) {
+
+        digester.addObjectCreate(prefix + "Realm",
+                                 null, // MUST be specified in the element,
+                                 "className");
+        digester.addSetProperties(prefix + "Realm");
+        digester.addSetNext(prefix + "Realm",
+                            "setRealm",
+                            "org.apache.catalina.Realm");
+
+        digester.addObjectCreate(prefix + "Realm/Realm",
+                                 null, // MUST be specified in the element
+                                 "className");
+        digester.addSetProperties(prefix + "Realm/Realm");
+        digester.addSetNext(prefix + "Realm/Realm",
+                            "addRealm",
+                            "org.apache.catalina.Realm");
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/SetAllPropertiesRule.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/SetAllPropertiesRule.java
new file mode 100644
index 0000000..976765d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/SetAllPropertiesRule.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.startup;
+
+import java.util.HashMap;
+
+import org.apache.tomcat.util.IntrospectionUtils;
+import org.apache.tomcat.util.digester.Rule;
+import org.xml.sax.Attributes;
+
+/**
+ * Rule that uses the introspection utils to set properties.
+ * 
+ * @author Remy Maucherat
+ * @author Filip Hanik
+ */
+public class SetAllPropertiesRule extends Rule {
+
+    
+    // ----------------------------------------------------------- Constructors
+    public SetAllPropertiesRule() {}
+    
+    public SetAllPropertiesRule(String[] exclude) {
+        for (int i=0; i<exclude.length; i++ ) if (exclude[i]!=null) this.excludes.put(exclude[i],exclude[i]);
+    }
+
+    // ----------------------------------------------------- Instance Variables
+    protected HashMap<String,String> excludes = new HashMap<String,String>();
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Handle the beginning of an XML element.
+     *
+     * @param attributes The attributes of this element
+     *
+     * @exception Exception if a processing error occurs
+     */
+    @Override
+    public void begin(String namespace, String nameX, Attributes attributes)
+        throws Exception {
+
+        for (int i = 0; i < attributes.getLength(); i++) {
+            String name = attributes.getLocalName(i);
+            if ("".equals(name)) {
+                name = attributes.getQName(i);
+            }
+            String value = attributes.getValue(i);
+            if ( !excludes.containsKey(name)) {
+                if (!digester.isFakeAttribute(digester.peek(), name) 
+                        && !IntrospectionUtils.setProperty(digester.peek(), name, value) 
+                        && digester.getRulesValidation()) {
+                    digester.getLogger().warn("[SetAllPropertiesRule]{" + digester.getMatch() +
+                            "} Setting property '" + name + "' to '" +
+                            value + "' did not find a matching property.");
+                }
+            }
+        }
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/SetContextPropertiesRule.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/SetContextPropertiesRule.java
new file mode 100644
index 0000000..97d7df3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/SetContextPropertiesRule.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.startup;
+
+import org.apache.tomcat.util.IntrospectionUtils;
+import org.apache.tomcat.util.digester.Rule;
+import org.xml.sax.Attributes;
+
+/**
+ * Rule that uses the introspection utils to set properties of a context 
+ * (everything except "path").
+ * 
+ * @author Remy Maucherat
+ */
+public class SetContextPropertiesRule extends Rule {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Handle the beginning of an XML element.
+     *
+     * @param attributes The attributes of this element
+     *
+     * @exception Exception if a processing error occurs
+     */
+    @Override
+    public void begin(String namespace, String nameX, Attributes attributes)
+        throws Exception {
+
+        for (int i = 0; i < attributes.getLength(); i++) {
+            String name = attributes.getLocalName(i);
+            if ("".equals(name)) {
+                name = attributes.getQName(i);
+            }
+            if ("path".equals(name) || "docBase".equals(name)) {
+                continue;
+            }
+            String value = attributes.getValue(i);
+            if (!digester.isFakeAttribute(digester.peek(), name) 
+                    && !IntrospectionUtils.setProperty(digester.peek(), name, value) 
+                    && digester.getRulesValidation()) {
+                digester.getLogger().warn("[SetContextPropertiesRule]{" + digester.getMatch() +
+                        "} Setting property '" + name + "' to '" +
+                        value + "' did not find a matching property.");
+            }
+        }
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/SetNextNamingRule.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/SetNextNamingRule.java
new file mode 100644
index 0000000..8c0fc65
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/SetNextNamingRule.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.catalina.startup;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.deploy.NamingResources;
+import org.apache.tomcat.util.IntrospectionUtils;
+import org.apache.tomcat.util.digester.Rule;
+
+
+/**
+ * <p>Rule implementation that calls a method on the (top-1) (parent)
+ * object, passing the top object (child) as an argument.  It is
+ * commonly used to establish parent-child relationships.</p>
+ *
+ * <p>This rule now supports more flexible method matching by default.
+ * It is possible that this may break (some) code 
+ * written against release 1.1.1 or earlier.
+ * </p> 
+ */
+
+public class SetNextNamingRule extends Rule {
+
+
+    // ----------------------------------------------------------- Constructors
+
+    
+    /**
+     * Construct a "set next" rule with the specified method name.
+     *
+     * @param methodName Method name of the parent method to call
+     * @param paramType Java class of the parent method's argument
+     *  (if you wish to use a primitive type, specify the corresponding
+     *  Java wrapper class instead, such as <code>java.lang.Boolean</code>
+     *  for a <code>boolean</code> parameter)
+     */
+    public SetNextNamingRule(String methodName,
+                       String paramType) {
+
+        this.methodName = methodName;
+        this.paramType = paramType;
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The method name to call on the parent object.
+     */
+    protected String methodName = null;
+
+
+    /**
+     * The Java class name of the parameter type expected by the method.
+     */
+    protected String paramType = null;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Process the end of this element.
+     * 
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the parser is not namespace aware or the element has
+     *   no namespace
+     * @param name the local name if the parser is namespace aware, or just 
+     *   the element name otherwise
+     */
+    @Override
+    public void end(String namespace, String name) throws Exception {
+
+        // Identify the objects to be used
+        Object child = digester.peek(0);
+        Object parent = digester.peek(1);
+
+        NamingResources namingResources = null;
+        if (parent instanceof Context) {
+            namingResources = ((Context) parent).getNamingResources();
+        } else {
+            namingResources = (NamingResources) parent;
+        }
+        
+        // Call the specified method
+        IntrospectionUtils.callMethod1(namingResources, methodName,
+                child, paramType, digester.getClassLoader());
+
+    }
+
+
+    /**
+     * Render a printable version of this Rule.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("SetNextRule[");
+        sb.append("methodName=");
+        sb.append(methodName);
+        sb.append(", paramType=");
+        sb.append(paramType);
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/TldConfig.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/TldConfig.java
new file mode 100644
index 0000000..4f2f9e3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/TldConfig.java
@@ -0,0 +1,605 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.startup;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.JarURLConnection;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import javax.servlet.ServletContext;
+import javax.servlet.descriptor.TaglibDescriptor;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.core.StandardContext;
+import org.apache.tomcat.JarScanner;
+import org.apache.tomcat.JarScannerCallback;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.digester.Digester;
+import org.apache.tomcat.util.res.StringManager;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+
+/**
+ * Startup event listener for a <b>Context</b> that configures application
+ * listeners configured in any TLD files.
+ *
+ * @author Craig R. McClanahan
+ * @author Jean-Francois Arcand
+ * @author Costin Manolache
+ */
+public final class TldConfig  implements LifecycleListener {
+
+    private static final String TLD_EXT = ".tld";
+    private static final String WEB_INF = "/WEB-INF/";
+    private static final String WEB_INF_LIB = "/WEB-INF/lib/";
+    
+    // Names of JARs that are known not to contain any TLDs
+    private static volatile Set<String> noTldJars = null;
+
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( TldConfig.class );
+
+    /**
+     * The string resources for this package.
+     */
+    private static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    /**
+     * The <code>Digester</code>s available to process tld files.
+     */
+    private static Digester[] tldDigesters = new Digester[4];
+
+    /**
+     * Create (if necessary) and return a Digester configured to process the
+     * tld.
+     */
+    private static Digester createTldDigester(boolean namespaceAware,
+            boolean validation) {
+        
+        Digester digester = null;
+        if (!namespaceAware && !validation) {
+            if (tldDigesters[0] == null) {
+                tldDigesters[0] = DigesterFactory.newDigester(validation,
+                        namespaceAware, new TldRuleSet());
+            }
+            digester = tldDigesters[0];
+        } else if (!namespaceAware && validation) {
+            if (tldDigesters[1] == null) {
+                tldDigesters[1] = DigesterFactory.newDigester(validation,
+                        namespaceAware, new TldRuleSet());
+            }
+            digester = tldDigesters[1];
+        } else if (namespaceAware && !validation) {
+            if (tldDigesters[2] == null) {
+                tldDigesters[2] = DigesterFactory.newDigester(validation,
+                        namespaceAware, new TldRuleSet());
+            }
+            digester = tldDigesters[2];
+        } else {
+            if (tldDigesters[3] == null) {
+                tldDigesters[3] = DigesterFactory.newDigester(validation,
+                        namespaceAware, new TldRuleSet());
+            }
+            digester = tldDigesters[3];
+        }
+        return digester;
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * The Context we are associated with.
+     */
+    private Context context = null;
+
+
+    /**
+     * The <code>Digester</code> we will use to process tag library
+     * descriptor files.
+     */
+    private Digester tldDigester = null;
+
+
+    /**
+     * Attribute value used to turn on/off TLD validation
+     */
+    private boolean tldValidation = false;
+
+
+    /**
+     * Attribute value used to turn on/off TLD  namespace awareness.
+     */
+    private boolean tldNamespaceAware = false;
+
+    private boolean rescan=true;
+
+    /**
+     * Set of URIs discovered for the associated context. Used to enforce the
+     * correct processing priority. Only the TLD associated with the first
+     * instance of any URI will be processed.
+     */
+    private Set<String> taglibUris = new HashSet<String>();
+
+    private Set<String> webxmlTaglibUris = new HashSet<String>();
+
+    private ArrayList<String> listeners = new ArrayList<String>();
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Adds a taglib URI to the list of known URIs.
+     */
+    public void addTaglibUri(String uri) {
+        taglibUris.add(uri);
+    }
+
+    /**
+     * Determines if the provided URI is a known taglib URI.
+     */
+    public boolean isKnownTaglibUri(String uri) {
+        return taglibUris.contains(uri);
+    }
+
+    /**
+     * Determines if the provided URI is a known taglib URI.
+     */
+    public boolean isKnownWebxmlTaglibUri(String uri) {
+        return webxmlTaglibUris.contains(uri);
+    }
+
+    /**
+     * Sets the list of JARs that are known not to contain any TLDs.
+     *
+     * @param jarNames List of comma-separated names of JAR files that are 
+     * known not to contain any TLDs.
+     */
+    public static void setNoTldJars(String jarNames) {
+        if (jarNames == null) {
+            noTldJars = null;
+        } else {
+            if (noTldJars == null) {
+                noTldJars = new HashSet<String>();
+            } else {
+                noTldJars.clear();
+            }
+            StringTokenizer tokenizer = new StringTokenizer(jarNames, ",");
+            while (tokenizer.hasMoreElements()) {
+                noTldJars.add(tokenizer.nextToken());
+            }
+        }
+    }
+
+    /**
+     * Set the validation feature of the XML parser used when
+     * parsing xml instances.
+     * @param tldValidation true to enable xml instance validation
+     */
+    public void setTldValidation(boolean tldValidation){
+        this.tldValidation = tldValidation;
+    }
+
+    /**
+     * Get the server.xml &lt;host&gt; attribute's xmlValidation.
+     * @return true if validation is enabled.
+     *
+     */
+    public boolean getTldValidation(){
+        return this.tldValidation;
+    }
+
+    /**
+     * Get the server.xml &lt;host&gt; attribute's xmlNamespaceAware.
+     * @return true if namespace awareness is enabled.
+     *
+     */
+    public boolean getTldNamespaceAware(){
+        return this.tldNamespaceAware;
+    }
+
+
+    /**
+     * Set the namespace aware feature of the XML parser used when
+     * parsing xml instances.
+     * @param tldNamespaceAware true to enable namespace awareness
+     */
+    public void setTldNamespaceAware(boolean tldNamespaceAware){
+        this.tldNamespaceAware = tldNamespaceAware;
+    }    
+
+
+    public boolean isRescan() {
+        return rescan;
+    }
+
+    public void setRescan(boolean rescan) {
+        this.rescan = rescan;
+    }
+
+    public Context getContext() {
+        return context;
+    }
+
+    public void setContext(Context context) {
+        this.context = context;
+    }
+
+    public void addApplicationListener( String s ) {
+        if(log.isDebugEnabled())
+            log.debug( "Add tld listener " + s);
+        listeners.add(s);
+    }
+
+    public String[] getTldListeners() {
+        String result[]=new String[listeners.size()];
+        listeners.toArray(result);
+        return result;
+    }
+
+
+    /**
+     * Scan for and configure all tag library descriptors found in this
+     * web application.
+     * 
+     * This supports a Tomcat-specific extension to the TLD search
+     * order defined in the JSP spec. It allows tag libraries packaged as JAR
+     * files to be shared by web applications by simply dropping them in a 
+     * location that all web applications have access to (e.g.,
+     * <CATALINA_HOME>/lib). It also supports some of the weird and
+     * wonderful arrangements present when Tomcat gets embedded.
+     *
+     * The set of shared JARs to be scanned for TLDs is narrowed down by
+     * the <tt>noTldJars</tt> class variable, which contains the names of JARs
+     * that are known not to contain any TLDs.
+     */
+    public void execute() {
+        long t1=System.currentTimeMillis();
+
+        /*
+         * Priority order of URIs required by spec is:
+         * 1. J2EE platform taglibs - Tomcat doesn't provide these
+         * 2. web.xml entries
+         * 3. JARS in WEB-INF/lib & TLDs under WEB-INF (equal priority)
+         * 4. Additional entries from the container
+         * 
+         * Keep processing order in sync with o.a.j.compiler.TldLocationsCache
+         */
+        
+        // Stage 2 - web.xml entries
+        tldScanWebXml();
+        
+        // Stage 3a - TLDs under WEB-INF (not lib or classes)
+        tldScanResourcePaths(WEB_INF);
+
+        // Stages 3b & 4
+        JarScanner jarScanner = context.getJarScanner();
+        jarScanner.scan(context.getServletContext(),
+                context.getLoader().getClassLoader(),
+                new TldJarScannerCallback(), noTldJars);
+        
+        // Now add all the listeners we found to the listeners for this context
+        String list[] = getTldListeners();
+
+        if( log.isDebugEnabled() )
+            log.debug(sm.getString("tldConfig.addListeners",
+                    Integer.valueOf(list.length)));
+
+        for( int i=0; list!=null && i<list.length; i++ ) {
+            context.addApplicationListener(list[i]);
+        }
+
+        long t2=System.currentTimeMillis();
+        if( context instanceof StandardContext ) {
+            ((StandardContext)context).setTldScanTime(t2-t1);
+        }
+
+    }
+
+    private class TldJarScannerCallback implements JarScannerCallback {
+
+        @Override
+        public void scan(JarURLConnection urlConn) throws IOException {
+            tldScanJar(urlConn);
+        }
+
+        @Override
+        public void scan(File file) {
+            File metaInf = new File(file, "META-INF");
+            if (metaInf.isDirectory()) {
+                tldScanDir(metaInf);
+            }
+        }
+    }
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Get the taglib entries from web.xml and add them to the map.
+     * 
+     * This is not kept in sync with o.a.j.compiler.TldLocationsCache as this
+     * code needs to scan the TLDs listed in web.xml whereas Jasper only needs
+     * the URI to TLD mappings.
+     */
+    private void tldScanWebXml() {
+        
+        if (log.isTraceEnabled()) {
+            log.trace(sm.getString("tldConfig.webxmlStart"));
+        }
+
+        Collection<TaglibDescriptor> descriptors =
+            context.getJspConfigDescriptor().getTaglibs();
+
+        for (TaglibDescriptor descriptor : descriptors) {
+            String resourcePath = descriptor.getTaglibLocation();
+            // Note: Whilst the Servlet 2.4 DTD implies that the location must
+            // be a context-relative path starting with '/', JSP.7.3.6.1 states
+            // explicitly how paths that do not start with '/' should be
+            // handled.
+            if (!resourcePath.startsWith("/")) {
+                resourcePath = WEB_INF + resourcePath;
+            }
+            if (taglibUris.contains(descriptor.getTaglibURI())) {
+                log.warn(sm.getString("tldConfig.webxmlSkip", resourcePath,
+                        descriptor.getTaglibURI()));
+            } else {
+                if (log.isTraceEnabled()) {
+                    log.trace(sm.getString("tldConfig.webxmlAdd", resourcePath,
+                            descriptor.getTaglibURI()));
+                }
+                try {
+                    InputStream stream = context.getServletContext(
+                            ).getResourceAsStream(resourcePath);
+                    XmlErrorHandler handler = tldScanStream(stream);
+                    handler.logFindings(log, resourcePath);
+                    taglibUris.add(descriptor.getTaglibURI());
+                    webxmlTaglibUris.add(descriptor.getTaglibURI());
+                } catch (IOException ioe) {
+                    log.warn(sm.getString("tldConfig.webxmlFail", resourcePath,
+                            descriptor.getTaglibURI()), ioe);
+                }
+            }
+        }
+    }
+    
+    /*
+     * Scans the web application's sub-directory identified by startPath,
+     * along with its sub-directories, for TLDs.
+     *
+     * Initially, rootPath equals /WEB-INF/. The /WEB-INF/classes and
+     * /WEB-INF/lib sub-directories are excluded from the search, as per the
+     * JSP 2.0 spec.
+     * 
+     * Keep in sync with o.a.j.comiler.TldLocationsCache
+     */
+    private void tldScanResourcePaths(String startPath) {
+
+        if (log.isTraceEnabled()) {
+            log.trace(sm.getString("tldConfig.webinfScan", startPath));
+        }
+
+        ServletContext ctxt = context.getServletContext();
+
+        Set<String> dirList = ctxt.getResourcePaths(startPath);
+        if (dirList != null) {
+            Iterator<String> it = dirList.iterator();
+            while (it.hasNext()) {
+                String path = it.next();
+                if (!path.endsWith(TLD_EXT)
+                        && (path.startsWith(WEB_INF_LIB)
+                                || path.startsWith("/WEB-INF/classes/"))) {
+                    continue;
+                }
+                if (path.endsWith(TLD_EXT)) {
+                    if (path.startsWith("/WEB-INF/tags/") &&
+                            !path.endsWith("implicit.tld")) {
+                        continue;
+                    }
+                    InputStream stream = ctxt.getResourceAsStream(path);
+                    try {
+                        XmlErrorHandler handler = tldScanStream(stream);
+                        handler.logFindings(log, path);
+                    } catch (IOException ioe) {
+                        log.warn(sm.getString("tldConfig.webinfFail", path),
+                                ioe);
+                    } finally {
+                        if (stream != null) {
+                            try {
+                                stream.close();
+                            } catch (Throwable t) {
+                                ExceptionUtils.handleThrowable(t);
+                            }
+                        }
+                    }
+                } else {
+                    tldScanResourcePaths(path);
+                }
+            }
+        }
+    }
+    
+    /*
+     * Scans the directory identified by startPath, along with its
+     * sub-directories, for TLDs.
+     *
+     * Keep in sync with o.a.j.comiler.TldLocationsCache
+     */
+    private void tldScanDir(File start) {
+
+        if (log.isTraceEnabled()) {
+            log.trace(sm.getString("tldConfig.dirScan", start.getAbsolutePath()));
+        }
+
+        File[] fileList = start.listFiles();
+        if (fileList != null) {
+            for (int i = 0; i < fileList.length; i++) {
+                // Scan recursively
+                if (fileList[i].isDirectory()) {
+                    tldScanDir(fileList[i]);
+                } else if (fileList[i].getAbsolutePath().endsWith(TLD_EXT)) {
+                    InputStream stream = null;
+                    try {
+                        stream = new FileInputStream(fileList[i]);
+                        XmlErrorHandler handler = tldScanStream(stream);
+                        handler.logFindings(log, fileList[i].getAbsolutePath());
+                    } catch (IOException ioe) {
+                        log.warn(sm.getString("tldConfig.dirFail",
+                                fileList[i].getAbsolutePath()),
+                                ioe);
+                    } finally {
+                        if (stream != null) {
+                            try {
+                                stream.close();
+                            } catch (Throwable t) {
+                                ExceptionUtils.handleThrowable(t);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /*
+     * Scans the given JarURLConnection for TLD files located in META-INF
+     * (or a sub-directory of it).
+     *
+     * @param conn The JarURLConnection to the JAR file to scan
+     * 
+     * Keep in sync with o.a.j.comiler.TldLocationsCache
+     */
+    private void tldScanJar(JarURLConnection conn) {
+
+        JarFile jarFile = null;
+        String name = null;
+        try {
+            conn.setUseCaches(false);
+            jarFile = conn.getJarFile();
+            Enumeration<JarEntry> entries = jarFile.entries();
+            while (entries.hasMoreElements()) {
+                JarEntry entry = entries.nextElement();
+                name = entry.getName();
+                if (!name.startsWith("META-INF/")) continue;
+                if (!name.endsWith(".tld")) continue;
+                InputStream stream = jarFile.getInputStream(entry);
+                XmlErrorHandler handler = tldScanStream(stream);
+                handler.logFindings(log, conn.getURL() + name);
+            }
+        } catch (IOException ioe) {
+            log.warn(sm.getString("tldConfig.jarFail", conn.getURL() + name),
+                    ioe);
+        } finally {
+            if (jarFile != null) {
+                try {
+                    jarFile.close();
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                }
+            }
+        }
+    }
+
+
+    /*
+     * Scan the TLD contents in the specified input stream, and register
+     * any application event listeners found there.  <b>NOTE</b> - This 
+     * method ensure that the InputStream is correctly closed.
+     *
+     * @param resourceStream InputStream containing a tag library descriptor
+     *
+     * @throws IOException  If the file cannot be read
+     */
+    private XmlErrorHandler tldScanStream(InputStream resourceStream)
+            throws IOException {
+        
+        InputSource source = new InputSource(resourceStream);
+        
+        XmlErrorHandler result = new XmlErrorHandler();
+        
+        synchronized (tldDigester) {
+            try {
+                tldDigester.setErrorHandler(result);
+                tldDigester.push(this);
+                tldDigester.parse(source);
+            } catch (SAXException s) {
+                // Hack - makes exception handling simpler
+                throw new IOException(s);
+            } finally {
+                tldDigester.reset();
+                if (resourceStream != null) {
+                    try {
+                        resourceStream.close();
+                    } catch (Throwable t) {
+                        ExceptionUtils.handleThrowable(t);
+                    }
+                }
+            }
+            return result;
+        }
+    }
+
+    @Override
+    public void lifecycleEvent(LifecycleEvent event) {
+        // Identify the context we are associated with
+        try {
+            context = (Context) event.getLifecycle();
+        } catch (ClassCastException e) {
+            log.error(sm.getString("tldConfig.cce", event.getLifecycle()), e);
+            return;
+        }
+        
+        if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
+            init();
+        } else if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
+            try {
+                execute();
+            } catch (Exception e) {
+                log.error(sm.getString(
+                        "tldConfig.execute", context.getName()), e);
+            }
+        } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
+            taglibUris.clear();
+            webxmlTaglibUris.clear();
+            listeners.clear();
+        }
+    }
+    
+    private void init() {
+        if (tldDigester == null){
+            setTldValidation(context.getTldValidation());
+            setTldNamespaceAware(context.getTldNamespaceAware());
+            tldDigester = createTldDigester(tldNamespaceAware, tldValidation);
+        }
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/TldRuleSet.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/TldRuleSet.java
new file mode 100644
index 0000000..f345afb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/TldRuleSet.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.startup;
+
+
+import org.apache.tomcat.util.digester.Digester;
+import org.apache.tomcat.util.digester.Rule;
+import org.apache.tomcat.util.digester.RuleSetBase;
+
+
+/**
+ * <p><strong>RuleSet</strong> for processing the contents of a tag library
+ * descriptor resource.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: TldRuleSet.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public class TldRuleSet 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 TldRuleSet() {
+
+        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 TldRuleSet(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) {
+
+        // Note the sharing of state between rules
+        TaglibUriRule taglibUriRule = new TaglibUriRule(); 
+
+        digester.addRule(prefix + "taglib", new TaglibRule(taglibUriRule));
+        
+        digester.addRule(prefix + "taglib/uri", taglibUriRule);
+
+        digester.addRule(prefix + "taglib/listener/listener-class",
+                new TaglibListenerRule(taglibUriRule));
+
+    }
+
+
+}
+
+/*
+ * This rule only exists to reset the duplicateUri flag on the TaglibUriRule.
+ */
+final class TaglibRule extends Rule {
+    private final TaglibUriRule taglibUriRule;
+    
+    public TaglibRule(TaglibUriRule taglibUriRule) {
+        this.taglibUriRule = taglibUriRule;
+    }
+    
+    @Override
+    public void body(String namespace, String name, String text)
+    throws Exception {
+        taglibUriRule.setDuplicateUri(false);
+    }
+
+}
+
+final class TaglibUriRule extends Rule {
+    
+    // This is set to false for each file processed by the TaglibRule
+    private boolean duplicateUri;
+    
+    public TaglibUriRule() {
+    }
+
+    @Override
+    public void body(String namespace, String name, String text)
+            throws Exception {
+        TldConfig tldConfig =
+            (TldConfig) digester.peek(digester.getCount() - 1);
+        if (tldConfig.isKnownTaglibUri(text)) {
+            // Already seen this URI
+            duplicateUri = true;
+            // This is expected if the URI was defined in web.xml
+            // Log message at debug in this case
+            if (tldConfig.isKnownWebxmlTaglibUri(text)) {
+                if (digester.getLogger().isDebugEnabled()) {
+                    digester.getLogger().debug(
+                            "TLD skipped. URI: " + text + " is already defined");
+                }
+            } else {
+                digester.getLogger().info(
+                        "TLD skipped. URI: " + text + " is already defined");
+            }
+        } else {
+            // New URI. Add it to known list and carry on
+            tldConfig.addTaglibUri(text);
+        }
+    }
+    
+    public boolean isDuplicateUri() {
+        return duplicateUri;
+    }
+
+    public void setDuplicateUri(boolean duplciateUri) {
+        this.duplicateUri = duplciateUri;
+    }
+
+}
+
+final class TaglibListenerRule extends Rule {
+    
+    private final TaglibUriRule taglibUriRule;
+    
+    public TaglibListenerRule(TaglibUriRule taglibUriRule) {
+        this.taglibUriRule = taglibUriRule;
+    }
+
+    @Override
+    public void body(String namespace, String name, String text)
+            throws Exception {
+        TldConfig tldConfig =
+            (TldConfig) digester.peek(digester.getCount() - 1);
+        
+        // Only process the listener if the URI is not a duplicate
+        if (!taglibUriRule.isDuplicateUri()) {
+            tldConfig.addApplicationListener(text);
+        }
+    }
+    
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/Tomcat.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/Tomcat.java
new file mode 100644
index 0000000..386b4e9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/Tomcat.java
@@ -0,0 +1,953 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.startup;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+
+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.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.Realm;
+import org.apache.catalina.Server;
+import org.apache.catalina.Service;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.authenticator.NonLoginAuthenticator;
+import org.apache.catalina.connector.Connector;
+import org.apache.catalina.core.NamingContextListener;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.core.StandardEngine;
+import org.apache.catalina.core.StandardHost;
+import org.apache.catalina.core.StandardServer;
+import org.apache.catalina.core.StandardService;
+import org.apache.catalina.core.StandardWrapper;
+import org.apache.catalina.deploy.LoginConfig;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.apache.catalina.realm.RealmBase;
+import org.apache.catalina.session.StandardManager;
+
+// TODO: lazy init for the temp dir - only when a JSP is compiled or 
+// get temp dir is called we need to create it. This will avoid the 
+// need for the baseDir
+
+// TODO: allow contexts without a base dir - i.e. 
+// only programmatic. This would disable the default servlet.
+
+/**
+ * Minimal tomcat starter for embedding/unit tests.
+ * 
+ * Tomcat supports multiple styles of configuration and 
+ * startup - the most common and stable is server.xml-based,
+ * implemented in org.apache.catalina.startup.Bootstrap.
+ *
+ * This class is for use in apps that embed tomcat. 
+ * Requirements:
+ * 
+ * - all tomcat classes and possibly servlets are in the classpath.
+ * ( for example all is in one big jar, or in eclipse CP, or in any other
+ * combination )
+ * 
+ * - we need one temporary directory for work files
+ * 
+ * - no config file is required. This class provides methods to 
+ * use if you have a webapp with a web.xml file, but it is 
+ * optional - you can use your own servlets.
+ * 
+ * This class provides a main() and few simple CLI arguments,
+ * see setters for doc. It can be used for simple tests and
+ * demo.
+ * 
+ * @see <a href="http://svn.apache.org/repos/asf/tomcat/trunk/test/org/apache/catalina/startup/TestTomcat.java">TestTomcat</a>
+ * @author Costin Manolache
+ */
+public class Tomcat {
+    // Single engine, service, server, connector - few cases need more,
+    // they can use server.xml
+    protected Server server;
+    protected Service service;
+    protected Engine engine;
+    protected Connector connector; // for more - customize the classes
+    
+    // To make it a bit easier to config for the common case
+    // ( one host, one context ). 
+    protected Host host;
+
+    // TODO: it's easy to add support for more hosts - but is it 
+    // really needed ?
+
+    // TODO: allow use of in-memory connector
+    
+    protected int port = 8080;
+    protected String hostname = "localhost";
+    protected String basedir;
+    
+    // Default in-memory realm, will be set by default on 
+    // created contexts. Can be replaced with setRealm() on 
+    // the context.
+    protected Realm defaultRealm;
+    private Map<String, String> userPass = new HashMap<String, String>();
+    private Map<String, List<String>> userRoles = 
+            new HashMap<String, List<String>>();
+    private Map<String, Principal> userPrincipals = new HashMap<String, Principal>();
+    
+    public Tomcat() {
+        // NOOP
+    }
+    
+    /**
+     * Tomcat needs a directory for temp files. This should be the 
+     * first method called. 
+     * 
+     * By default, if this method is not called, we use:
+     *  - system properties - catalina.base, catalina.home 
+     *  - $HOME/tomcat.$PORT
+     * ( /tmp doesn't seem a good choice for security ).
+     *   
+     *
+     * TODO: better default ? Maybe current dir ? 
+     * TODO: disable work dir if not needed ( no jsp, etc ).
+     */
+    public void setBaseDir(String basedir) {
+        this.basedir = basedir;
+    }
+
+    /** 
+     * Set the port for the default connector. Must 
+     * be called before start().
+     */
+    public void setPort(int port) {
+        this.port = port;
+    }
+    
+    /** 
+     * The the hostname of the default host, default is 
+     * 'localhost'.
+     */
+    public void setHostname(String s) {
+        hostname = s;
+    }
+
+    /** 
+     * Add a webapp using normal WEB-INF/web.xml if found.
+     * 
+     * @param contextPath
+     * @param baseDir
+     * @return new Context
+     * @throws ServletException 
+     */
+    public Context addWebapp(String contextPath, 
+                                     String baseDir) throws ServletException {
+        
+        return addWebapp(getHost(), contextPath, baseDir);    
+    }
+    
+    
+    /** 
+     * Add a context - programmatic mode, no web.xml used.
+     *
+     * API calls equivalent with web.xml:
+     * 
+     * context-param
+     *  ctx.addParameter("name", "value");
+     *     
+     *
+     * error-page
+     *    ErrorPage ep = new ErrorPage();
+     *    ep.setErrorCode(500);
+     *    ep.setLocation("/error.html");
+     *    ctx.addErrorPage(ep);
+     *   
+     * ctx.addMimeMapping("ext", "type");
+     * 
+     * Note: If you reload the Context, all your configuration will be lost. If
+     * you need reload support, consider using a LifecycleListener to provide
+     * your configuration.
+     *  
+     * TODO: add the rest
+     *
+     *  @param contextPath "" for root context.
+     *  @param baseDir base dir for the context, for static files. Must exist, 
+     *  relative to the server home
+     */
+    public Context addContext(String contextPath, 
+                                      String baseDir) {
+        return addContext(getHost(), contextPath, baseDir);
+    }
+
+    /**
+     * Equivalent with 
+     *  <servlet><servlet-name><servlet-class>.
+     *  
+     * In general it is better/faster to use the method that takes a 
+     * Servlet as param - this one can be used if the servlet is not 
+     * commonly used, and want to avoid loading all deps.
+     * ( for example: jsp servlet )
+     * 
+     * You can customize the returned servlet, ex:
+     * 
+     *    wrapper.addInitParameter("name", "value");
+     *    
+     * @param contextPath   Context to add Servlet to
+     * @param servletName   Servlet name (used in mappings)
+     * @param servletClass  The class to be used for the Servlet
+     * @return The wrapper for the servlet
+     */
+    public Wrapper addServlet(String contextPath, 
+            String servletName, 
+            String servletClass) {
+        Container ctx = getHost().findChild(contextPath);
+        return addServlet((Context) ctx, servletName, servletClass);
+    }
+
+    /**
+     * Static version of {@link #addServlet(String, String, String)}
+     * @param ctx           Context to add Servlet to
+     * @param servletName   Servlet name (used in mappings)
+     * @param servletClass  The class to be used for the Servlet
+     * @return The wrapper for the servlet
+     */
+    public static Wrapper addServlet(Context ctx, 
+                                      String servletName, 
+                                      String servletClass) {
+        // will do class for name and set init params
+        Wrapper sw = ctx.createWrapper();
+        sw.setServletClass(servletClass);
+        sw.setName(servletName);
+        ctx.addChild(sw);
+        
+        return sw;
+    }
+
+    /**
+     * Add an existing Servlet to the context with no class.forName or
+     * initialisation.
+     * @param contextPath   Context to add Servlet to
+     * @param servletName   Servlet name (used in mappings)
+     * @param servlet       The Servlet to add
+     * @return The wrapper for the servlet
+     */
+    public Wrapper addServlet(String contextPath, 
+            String servletName, 
+            Servlet servlet) {
+        Container ctx = getHost().findChild(contextPath);
+        return addServlet((Context) ctx, servletName, servlet);
+    }
+
+    /**
+     * Static version of {@link #addServlet(String, String, Servlet)}.
+     * @param ctx           Context to add Servlet to
+     * @param servletName   Servlet name (used in mappings)
+     * @param servlet       The Servlet to add
+     * @return The wrapper for the servlet
+     */
+    public static Wrapper addServlet(Context ctx,
+                                      String servletName, 
+                                      Servlet servlet) {
+        // will do class for name and set init params
+        Wrapper sw = new ExistingStandardWrapper(servlet);
+        sw.setName(servletName);
+        ctx.addChild(sw);
+        
+        return sw;
+    }
+    
+
+    /**
+     * Initialise the server.
+     * 
+     * @throws LifecycleException
+     */
+    public void init() throws LifecycleException {
+        getServer();
+        getConnector();
+        server.init();
+    }
+    
+    
+    /**
+     * Start the server.
+     * 
+     * @throws LifecycleException 
+     */
+    public void start() throws LifecycleException {
+        getServer();
+        getConnector();
+        server.start();
+    }
+
+    /** 
+     * Stop the server.
+     * 
+     * @throws LifecycleException 
+     */
+    public void stop() throws LifecycleException {
+        getServer();
+        server.stop();
+    }
+
+
+    /**
+     * Destroy the server. This object cannot be used once this method has been
+     * called.
+     */
+    public void destroy() throws LifecycleException {
+        getServer();
+        server.destroy();
+        // Could null out objects here
+    }
+    
+    /** 
+     * Add a user for the in-memory realm. All created apps use this 
+     * by default, can be replaced using setRealm().
+     *  
+     */
+    public void addUser(String user, String pass) {
+        userPass.put(user, pass);
+    }
+    
+    /**
+     * @see #addUser(String, String) 
+     */
+    public void addRole(String user, String role) {
+        List<String> roles = userRoles.get(user);
+        if (roles == null) {
+            roles = new ArrayList<String>();
+            userRoles.put(user, roles);
+        }
+        roles.add(role);
+    }
+
+    // ------- Extra customization -------
+    // You can tune individual tomcat objects, using internal APIs
+    
+    /** 
+     * Get the default http connector. You can set more 
+     * parameters - the port is already initialized.
+     * 
+     * Alternatively, you can construct a Connector and set any params,
+     * then call addConnector(Connector)
+     * 
+     * @return A connector object that can be customized
+     */
+    public Connector getConnector() {
+        getServer();
+        if (connector != null) {
+            return connector;
+        }
+        // This will load Apr connector if available,
+        // default to nio. I'm having strange problems with apr
+        // XXX: jfclere weird... Don't add the AprLifecycleListener then.
+        // and for the use case the speed benefit wouldn't matter.
+        
+        connector = new Connector("HTTP/1.1");
+        // connector = new Connector("org.apache.coyote.http11.Http11Protocol"); 
+        connector.setPort(port);
+        service.addConnector( connector );
+        return connector;
+    }
+    
+    public void setConnector(Connector connector) {
+        this.connector = connector;
+    }
+    
+    /** 
+     * Get the service object. Can be used to add more 
+     * connectors and few other global settings.
+     */
+    public Service getService() {
+        getServer();
+        return service;
+    }
+
+    /** 
+     * Sets the current host - all future webapps will
+     * be added to this host. When tomcat starts, the 
+     * host will be the default host.
+     * 
+     * @param host
+     */
+    public void setHost(Host host) {
+        this.host = host;
+    }
+    
+    public Host getHost() {
+        if (host == null) {
+            host = new StandardHost();
+            host.setName(hostname);
+
+            getEngine().addChild( host );
+        }
+        return host;
+    }
+    
+    /** 
+     * Set a custom realm for auth. If not called, a simple
+     * default will be used, using an internal map.
+     * 
+     * Must be called before adding a context.
+     */
+    public void setDefaultRealm(Realm realm) {
+        defaultRealm = realm;
+    }
+    
+
+    /** 
+     * Access to the engine, for further customization.
+     */
+    public Engine getEngine() {
+        if(engine == null ) {
+            getServer();
+            engine = new StandardEngine();
+            engine.setName( "Tomcat" );
+            engine.setDefaultHost(hostname);
+            service.setContainer(engine);
+        }
+        return engine;
+    }
+
+    /**
+     * Get the server object. You can add listeners and few more
+     * customizations. JNDI is disabled by default.  
+     */
+    public Server getServer() {
+        
+        if (server != null) {
+            return server;
+        }
+        
+        initBaseDir(); 
+        
+        System.setProperty("catalina.useNaming", "false");
+        
+        server = new StandardServer();
+        server.setPort( -1 );
+        
+        service = new StandardService();
+        service.setName("Tomcat");
+        server.addService( service );
+        return server;
+    }
+
+    public Context addContext(Host host, String contextPath, String dir) {
+        silence(contextPath);
+        Context ctx = new StandardContext();
+        ctx.setPath( contextPath );
+        ctx.setDocBase(dir);
+        ctx.addLifecycleListener(new FixContextListener());
+        
+        if (host == null) {
+            getHost().addChild(ctx);
+        } else {
+            host.addChild(ctx);
+        }
+        return ctx;
+    }
+    
+    public Context addWebapp(Host host, String url, String path) {
+        silence(url);
+
+        Context ctx = new StandardContext();
+        ctx.setPath( url );
+        ctx.setDocBase(path);
+        if (defaultRealm == null) {
+            initSimpleAuth();
+        }
+        ctx.setRealm(defaultRealm);
+
+        ctx.addLifecycleListener(new DefaultWebXmlListener());
+        
+        ContextConfig ctxCfg = new ContextConfig();
+        ctx.addLifecycleListener(ctxCfg);
+        
+        // prevent it from looking ( if it finds one - it'll have dup error )
+        ctxCfg.setDefaultWebXml("org/apache/catalin/startup/NO_DEFAULT_XML");
+        
+        if (host == null) {
+            getHost().addChild(ctx);
+        } else {
+            host.addChild(ctx);
+        }
+
+        return ctx;
+    }
+
+
+    // ---------- Helper methods and classes -------------------
+    
+    /** 
+     * Initialize an in-memory realm. You can replace it 
+     * for contexts with a real one. 
+     */
+    protected void initSimpleAuth() {
+        defaultRealm = new RealmBase() {
+            @Override
+            protected String getName() {
+                return "Simple";
+            }
+
+            @Override
+            protected String getPassword(String username) {
+                return userPass.get(username);
+            }
+
+            @Override
+            protected Principal getPrincipal(String username) {
+                Principal p = userPrincipals.get(username);
+                if (p == null) {
+                    String pass = userPass.get(username);
+                    if (pass != null) {
+                        p = new GenericPrincipal(username, pass,
+                                userRoles.get(username));
+                        userPrincipals.put(username, p);
+                    }
+                }
+                return p;
+            }
+            
+        };        
+    }
+    
+    protected void initBaseDir() {
+        if (basedir == null) {
+            basedir = System.getProperty(Globals.CATALINA_BASE_PROP);
+        }
+        if (basedir == null) {
+            basedir = System.getProperty(Globals.CATALINA_HOME_PROP);
+        }
+        if (basedir == null) {
+            // Create a temp dir.
+            basedir = System.getProperty("user.dir") + 
+                "/tomcat." + port;
+            File home = new File(basedir);
+            home.mkdir();
+            if (!home.isAbsolute()) {
+                try {
+                    basedir = home.getCanonicalPath();
+                } catch (IOException e) {
+                    basedir = home.getAbsolutePath();
+                }
+            }
+        }
+        System.setProperty(Globals.CATALINA_HOME_PROP, basedir);
+        System.setProperty(Globals.CATALINA_BASE_PROP, basedir);
+    }
+
+    static String[] silences = new String[] {
+        "org.apache.coyote.http11.Http11Protocol",
+        "org.apache.catalina.core.StandardService",
+        "org.apache.catalina.core.StandardEngine",
+        "org.apache.catalina.startup.ContextConfig",
+        "org.apache.catalina.core.ApplicationContext",
+        "org.apache.catalina.core.AprLifecycleListener"
+    };
+    
+    /**
+     * Controls if the loggers will be silenced or not.
+     * @param silent    <code>true</code> sets the log level to WARN for the
+     *                  loggers that log information on Tomcat start up. This
+     *                  prevents the usual startup information being logged.
+     *                  <code>false</code> sets the log level to the default
+     *                  level of INFO.
+     */
+    public void setSilent(boolean silent) {
+        for (String s : silences) {
+            if (silent) {
+                Logger.getLogger(s).setLevel(Level.WARNING);
+            } else {
+                Logger.getLogger(s).setLevel(Level.INFO);
+            }
+        }
+    }
+    
+    private void silence(String ctx) {
+        String base = "org.apache.catalina.core.ContainerBase.[default].[";
+        base += getHost().getName();
+        base += "].[";
+        base += ctx; 
+        base += "]";
+        Logger.getLogger(base).setLevel(Level.WARNING);
+    }
+    
+    /**
+     * Enables JNDI naming which is disabled by default. Server must implement
+     * {@link Lifecycle} in order for the {@link NamingContextListener} to be
+     * used.
+     * 
+     */
+    public void enableNaming() {
+        // Make sure getServer() has been called as that is where naming is
+        // disabled
+        getServer();
+        server.addLifecycleListener(new NamingContextListener());
+        
+        System.setProperty("catalina.useNaming", "true");
+
+        String value = "org.apache.naming";
+        String oldValue =
+            System.getProperty(javax.naming.Context.URL_PKG_PREFIXES);
+        if (oldValue != null) {
+            if (oldValue.contains(value)) {
+                value = oldValue;
+            } else {
+                value = value + ":" + oldValue;
+            }
+        }
+        System.setProperty(javax.naming.Context.URL_PKG_PREFIXES, value);
+
+        value = System.getProperty
+            (javax.naming.Context.INITIAL_CONTEXT_FACTORY);
+        if (value == null) {
+            System.setProperty
+                (javax.naming.Context.INITIAL_CONTEXT_FACTORY,
+                 "org.apache.naming.java.javaURLContextFactory");
+        }
+    }
+
+    /**
+     * Provide default configuration for a context. This is the programmatic
+     * equivalent of the default web.xml. 
+     * 
+     *  TODO: in normal Tomcat, if default-web.xml is not found, use this 
+     *  method
+     *  
+     * @param contextPath   The context to set the defaults for
+     */
+    public void initWebappDefaults(String contextPath) {
+        Container ctx = getHost().findChild(contextPath);
+        initWebappDefaults((Context) ctx);
+    }
+    
+    /**
+     * Static version of {@link #initWebappDefaults(String)}
+     * @param ctx   The context to set the defaults for
+     */
+    public static void initWebappDefaults(Context ctx) {
+        // Default servlet 
+        Wrapper servlet = addServlet(
+                ctx, "default", "org.apache.catalina.servlets.DefaultServlet");
+        servlet.setLoadOnStartup(1);
+
+        // JSP servlet (by class name - to avoid loading all deps)
+        servlet = addServlet(
+                ctx, "jsp", "org.apache.jasper.servlet.JspServlet");
+        servlet.addInitParameter("fork", "false");
+        servlet.setLoadOnStartup(3);
+        
+        // Servlet mappings 
+        ctx.addServletMapping("/", "default");
+        ctx.addServletMapping("*.jsp", "jsp");
+        ctx.addServletMapping("*.jspx", "jsp");
+
+        // Sessions
+        ctx.setManager( new StandardManager());
+        ctx.setSessionTimeout(30);
+        
+        // MIME mappings
+        for (int i = 0; i < DEFAULT_MIME_MAPPINGS.length;) {
+            ctx.addMimeMapping(DEFAULT_MIME_MAPPINGS[i++],
+                    DEFAULT_MIME_MAPPINGS[i++]);
+        }
+        
+        // Welcome files
+        ctx.addWelcomeFile("index.html");
+        ctx.addWelcomeFile("index.htm");
+        ctx.addWelcomeFile("index.jsp");
+    }
+
+    
+    /**
+     * Fix startup sequence - required if you don't use web.xml.
+     * 
+     * The start() method in context will set 'configured' to false - and
+     * expects a listener to set it back to true.
+     */
+    public static class FixContextListener implements LifecycleListener {
+
+        @Override
+        public void lifecycleEvent(LifecycleEvent event) {
+            try {
+                Context context = (Context) event.getLifecycle();
+                if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
+                    context.setConfigured(true);
+                }
+                // LoginConfig is required to process @ServletSecurity
+                // annotations
+                if (context.getLoginConfig() == null) {
+                    context.setLoginConfig(
+                            new LoginConfig("NONE", null, null, null));
+                    context.getPipeline().addValve(new NonLoginAuthenticator());
+                }
+            } catch (ClassCastException e) {
+                return;
+            }
+        }
+        
+    }
+
+
+    /**
+     * Fix reload - required if reloading and using programmatic configuration.
+     * When a context is reloaded, any programmatic configuration is lost. This
+     * listener sets the equivalent of conf/web.xml when the context starts.
+     */
+    public static class DefaultWebXmlListener implements LifecycleListener {
+        @Override
+        public void lifecycleEvent(LifecycleEvent event) {
+            if (Lifecycle.BEFORE_START_EVENT.equals(event.getType())) {
+                initWebappDefaults((Context) event.getLifecycle());
+            }
+        }
+    }
+
+
+    /**
+     * Helper class for wrapping existing servlets. This disables servlet 
+     * lifecycle and normal reloading, but also reduces overhead and provide
+     * more direct control over the servlet.  
+     */
+    public static class ExistingStandardWrapper extends StandardWrapper {
+        private Servlet existing;
+        boolean init = false;
+        
+        public ExistingStandardWrapper( Servlet existing ) {
+            this.existing = existing;
+        }
+        @Override
+        public synchronized Servlet loadServlet() throws ServletException {
+            if (!init) {
+                existing.init(facade);
+                init = true;
+            }
+            return existing;
+
+        }
+        @Override
+        public long getAvailable() {
+            return 0;
+        }
+        @Override
+        public boolean isUnavailable() {
+            return false;       
+        }
+        @Override
+        public Servlet getServlet() {
+            return existing;
+        }
+        @Override
+        public String getServletClass() {
+            return existing.getClass().getName();
+        }
+    }
+    
+    /**
+     * TODO: would a properties resource be better ? Or just parsing
+     * /etc/mime.types ?
+     * This is needed because we don't use the default web.xml, where this 
+     * is encoded.
+     */
+    private static final String[] DEFAULT_MIME_MAPPINGS = {
+        "abs", "audio/x-mpeg", 
+        "ai", "application/postscript", 
+        "aif", "audio/x-aiff", 
+        "aifc", "audio/x-aiff", 
+        "aiff", "audio/x-aiff", 
+        "aim", "application/x-aim", 
+        "art", "image/x-jg", 
+        "asf", "video/x-ms-asf", 
+        "asx", "video/x-ms-asf", 
+        "au", "audio/basic", 
+        "avi", "video/x-msvideo", 
+        "avx", "video/x-rad-screenplay", 
+        "bcpio", "application/x-bcpio", 
+        "bin", "application/octet-stream", 
+        "bmp", "image/bmp", 
+        "body", "text/html", 
+        "cdf", "application/x-cdf", 
+        "cer", "application/x-x509-ca-cert", 
+        "class", "application/java", 
+        "cpio", "application/x-cpio", 
+        "csh", "application/x-csh", 
+        "css", "text/css", 
+        "dib", "image/bmp", 
+        "doc", "application/msword", 
+        "dtd", "application/xml-dtd", 
+        "dv", "video/x-dv", 
+        "dvi", "application/x-dvi", 
+        "eps", "application/postscript", 
+        "etx", "text/x-setext", 
+        "exe", "application/octet-stream", 
+        "gif", "image/gif", 
+        "gtar", "application/x-gtar", 
+        "gz", "application/x-gzip", 
+        "hdf", "application/x-hdf", 
+        "hqx", "application/mac-binhex40", 
+        "htc", "text/x-component", 
+        "htm", "text/html", 
+        "html", "text/html", 
+        "hqx", "application/mac-binhex40", 
+        "ief", "image/ief", 
+        "jad", "text/vnd.sun.j2me.app-descriptor", 
+        "jar", "application/java-archive", 
+        "java", "text/plain", 
+        "jnlp", "application/x-java-jnlp-file", 
+        "jpe", "image/jpeg", 
+        "jpeg", "image/jpeg", 
+        "jpg", "image/jpeg", 
+        "js", "text/javascript", 
+        "jsf", "text/plain", 
+        "jspf", "text/plain", 
+        "kar", "audio/x-midi", 
+        "latex", "application/x-latex", 
+        "m3u", "audio/x-mpegurl", 
+        "mac", "image/x-macpaint", 
+        "man", "application/x-troff-man", 
+        "mathml", "application/mathml+xml", 
+        "me", "application/x-troff-me", 
+        "mid", "audio/x-midi", 
+        "midi", "audio/x-midi", 
+        "mif", "application/x-mif", 
+        "mov", "video/quicktime", 
+        "movie", "video/x-sgi-movie", 
+        "mp1", "audio/x-mpeg", 
+        "mp2", "audio/x-mpeg", 
+        "mp3", "audio/x-mpeg", 
+        "mp4", "video/mp4", 
+        "mpa", "audio/x-mpeg", 
+        "mpe", "video/mpeg", 
+        "mpeg", "video/mpeg", 
+        "mpega", "audio/x-mpeg", 
+        "mpg", "video/mpeg", 
+        "mpv2", "video/mpeg2", 
+        "ms", "application/x-wais-source", 
+        "nc", "application/x-netcdf", 
+        "oda", "application/oda", 
+        "odb", "application/vnd.oasis.opendocument.database", 
+        "odc", "application/vnd.oasis.opendocument.chart", 
+        "odf", "application/vnd.oasis.opendocument.formula", 
+        "odg", "application/vnd.oasis.opendocument.graphics", 
+        "odi", "application/vnd.oasis.opendocument.image", 
+        "odm", "application/vnd.oasis.opendocument.text-master", 
+        "odp", "application/vnd.oasis.opendocument.presentation", 
+        "ods", "application/vnd.oasis.opendocument.spreadsheet", 
+        "odt", "application/vnd.oasis.opendocument.text", 
+        "otg", "application/vnd.oasis.opendocument.graphics-template", 
+        "oth", "application/vnd.oasis.opendocument.text-web", 
+        "otp", "application/vnd.oasis.opendocument.presentation-template", 
+        "ots", "application/vnd.oasis.opendocument.spreadsheet-template ", 
+        "ott", "application/vnd.oasis.opendocument.text-template",
+        "ogx", "application/ogg",
+        "ogv", "video/ogg",
+        "oga", "audio/ogg",
+        "ogg", "audio/ogg",
+        "spx", "audio/ogg",
+        "faca", "audio/flac",
+        "anx", "application/annodex",
+        "axa", "audio/annodex",
+        "axv", "video/annodex",
+        "xspf", "application/xspf+xml",
+        "pbm", "image/x-portable-bitmap", 
+        "pct", "image/pict", 
+        "pdf", "application/pdf", 
+        "pgm", "image/x-portable-graymap", 
+        "pic", "image/pict", 
+        "pict", "image/pict", 
+        "pls", "audio/x-scpls", 
+        "png", "image/png", 
+        "pnm", "image/x-portable-anymap", 
+        "pnt", "image/x-macpaint", 
+        "ppm", "image/x-portable-pixmap", 
+        "ppt", "application/vnd.ms-powerpoint",
+        "pps", "application/vnd.ms-powerpoint",
+        "ps", "application/postscript", 
+        "psd", "image/x-photoshop", 
+        "qt", "video/quicktime", 
+        "qti", "image/x-quicktime", 
+        "qtif", "image/x-quicktime", 
+        "ras", "image/x-cmu-raster", 
+        "rdf", "application/rdf+xml", 
+        "rgb", "image/x-rgb", 
+        "rm", "application/vnd.rn-realmedia", 
+        "roff", "application/x-troff", 
+        "rtf", "application/rtf", 
+        "rtx", "text/richtext", 
+        "sh", "application/x-sh", 
+        "shar", "application/x-shar", 
+        /*"shtml", "text/x-server-parsed-html",*/
+        "smf", "audio/x-midi", 
+        "sit", "application/x-stuffit", 
+        "snd", "audio/basic", 
+        "src", "application/x-wais-source", 
+        "sv4cpio", "application/x-sv4cpio", 
+        "sv4crc", "application/x-sv4crc", 
+        "svg", "image/svg+xml", 
+        "svgz", "image/svg+xml", 
+        "swf", "application/x-shockwave-flash", 
+        "t", "application/x-troff", 
+        "tar", "application/x-tar", 
+        "tcl", "application/x-tcl", 
+        "tex", "application/x-tex", 
+        "texi", "application/x-texinfo", 
+        "texinfo", "application/x-texinfo", 
+        "tif", "image/tiff", 
+        "tiff", "image/tiff", 
+        "tr", "application/x-troff", 
+        "tsv", "text/tab-separated-values", 
+        "txt", "text/plain", 
+        "ulw", "audio/basic", 
+        "ustar", "application/x-ustar", 
+        "vxml", "application/voicexml+xml", 
+        "xbm", "image/x-xbitmap", 
+        "xht", "application/xhtml+xml", 
+        "xhtml", "application/xhtml+xml", 
+        "xls", "application/vnd.ms-excel", 
+        "xml", "application/xml", 
+        "xpm", "image/x-xpixmap", 
+        "xsl", "application/xml", 
+        "xslt", "application/xslt+xml", 
+        "xul", "application/vnd.mozilla.xul+xml", 
+        "xwd", "image/x-xwindowdump", 
+        "vsd", "application/x-visio", 
+        "wav", "audio/x-wav", 
+        "wbmp", "image/vnd.wap.wbmp", 
+        "wml", "text/vnd.wap.wml", 
+        "wmlc", "application/vnd.wap.wmlc", 
+        "wmls", "text/vnd.wap.wmlscript", 
+        "wmlscriptc", "application/vnd.wap.wmlscriptc", 
+        "wmv", "video/x-ms-wmv", 
+        "wrl", "x-world/x-vrml", 
+        "wspolicy", "application/wspolicy+xml", 
+        "Z", "application/x-compress", 
+        "z", "application/x-compress", 
+        "zip", "application/zip" 
+    };
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/Tool.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/Tool.java
new file mode 100644
index 0000000..1c61e3c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/Tool.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.catalina.startup;
+
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+
+import org.apache.catalina.Globals;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+
+
+/**
+ * <p>General purpose wrapper for command line tools that should execute in an
+ * environment with the common class loader environment set up by Catalina.
+ * This should be executed from a command line script that conforms to
+ * the following requirements:</p>
+ * <ul>
+ * <li>Passes the <code>catalina.home</code> system property configured with
+ *     the pathname of the Tomcat installation directory.</li>
+ * <li>Sets the system classpath to include <code>bootstrap.jar</code> and
+ *     <code>$JAVA_HOME/lib/tools.jar</code>.</li>
+ * </ul>
+ *
+ * <p>The command line to execute the tool looks like:</p>
+ * <pre>
+ *   java -classpath $CLASSPATH org.apache.catalina.startup.Tool \
+ *     ${options} ${classname} ${arguments}
+ * </pre>
+ *
+ * <p>with the following replacement contents:
+ * <ul>
+ * <li><strong>${options}</strong> - Command line options for this Tool wrapper.
+ *     The following options are supported:
+ *     <ul>
+ *     <li><em>-ant</em> : Set the <code>ant.home</code> system property
+ *         to corresponding to the value of <code>catalina.home</code>
+ *         (useful when your command line tool runs Ant).</li>
+ *     <li><em>-common</em> : Add <code>common/classes</code> and
+ *         <code>common/lib</codE) to the class loader repositories.</li>
+ *     <li><em>-server</em> : Add <code>server/classes</code> and
+ *         <code>server/lib</code> to the class loader repositories.</li>
+ *     <li><em>-shared</em> : Add <code>shared/classes</code> and
+ *         <code>shared/lib</code> to the class loader repositories.</li>
+ *     </ul>
+ * <li><strong>${classname}</strong> - Fully qualified Java class name of the
+ *     application's main class.</li>
+ * <li><strong>${arguments}</strong> - Command line arguments to be passed to
+ *     the application's <code>main()</code> method.</li>
+ * </ul>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: Tool.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+public final class Tool {
+
+
+    private static final Log log = LogFactory.getLog(Tool.class);
+    
+    // ------------------------------------------------------- Static Variables
+
+
+    /**
+     * Set <code>ant.home</code> system property?
+     */
+    private static boolean ant = false;
+
+
+    /**
+     * The pathname of our installation base directory.
+     */
+    private static String catalinaHome = System.getProperty(Globals.CATALINA_HOME_PROP);
+
+
+    /**
+     * Include common classes in the repositories?
+     */
+    private static boolean common = false;
+
+
+    /**
+     * Include server classes in the repositories?
+     */
+    private static boolean server = false;
+
+
+    /**
+     * Include shared classes in the repositories?
+     */
+    private static boolean shared = false;
+
+
+    // ----------------------------------------------------------- Main Program
+
+
+    /**
+     * The main program for the bootstrap.
+     *
+     * @param args Command line arguments to be processed
+     */
+    public static void main(String args[]) {
+
+        // Verify that "catalina.home" was passed.
+        if (catalinaHome == null) {
+            log.error("Must set '" + Globals.CATALINA_HOME_PROP + "' system property");
+            System.exit(1);
+        }
+
+        // Process command line options
+        int index = 0;
+        while (true) {
+            if (index == args.length) {
+                usage();
+                System.exit(1);
+            }
+            if ("-ant".equals(args[index]))
+                ant = true;
+            else if ("-common".equals(args[index]))
+                common = true;
+            else if ("-server".equals(args[index]))
+                server = true;
+            else if ("-shared".equals(args[index]))
+                shared = true;
+            else
+                break;
+            index++;
+        }
+        if (index > args.length) {
+            usage();
+            System.exit(1);
+        }
+
+        // Set "ant.home" if requested
+        if (ant)
+            System.setProperty("ant.home", catalinaHome);
+
+        // Construct the class loader we will be using
+        ClassLoader classLoader = null;
+        try {
+            ArrayList<File> packed = new ArrayList<File>();
+            ArrayList<File> unpacked = new ArrayList<File>();
+            unpacked.add(new File(catalinaHome, "classes"));
+            packed.add(new File(catalinaHome, "lib"));
+            if (common) {
+                unpacked.add(new File(catalinaHome,
+                                      "common" + File.separator + "classes"));
+                packed.add(new File(catalinaHome,
+                                    "common" + File.separator + "lib"));
+            }
+            if (server) {
+                unpacked.add(new File(catalinaHome,
+                                      "server" + File.separator + "classes"));
+                packed.add(new File(catalinaHome,
+                                    "server" + File.separator + "lib"));
+            }
+            if (shared) {
+                unpacked.add(new File(catalinaHome,
+                                      "shared" + File.separator + "classes"));
+                packed.add(new File(catalinaHome,
+                                    "shared" + File.separator + "lib"));
+            }
+            classLoader =
+                ClassLoaderFactory.createClassLoader
+                (unpacked.toArray(new File[0]),
+                 packed.toArray(new File[0]),
+                 null);
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.error("Class loader creation threw exception", t);
+            System.exit(1);
+        }
+        Thread.currentThread().setContextClassLoader(classLoader);
+
+        // Load our application class
+        Class<?> clazz = null;
+        String className = args[index++];
+        try {
+            if (log.isDebugEnabled())
+                log.debug("Loading application class " + className);
+            clazz = classLoader.loadClass(className);
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.error("Exception creating instance of " + className, t);
+            System.exit(1);
+        }
+
+        // Locate the static main() method of the application class
+        Method method = null;
+        String params[] = new String[args.length - index];
+        System.arraycopy(args, index, params, 0, params.length);
+        try {
+            if (log.isDebugEnabled())
+                log.debug("Identifying main() method");
+            String methodName = "main";
+            Class<?> paramTypes[] = new Class[1];
+            paramTypes[0] = params.getClass();
+            method = clazz.getMethod(methodName, paramTypes);
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.error("Exception locating main() method", t);
+            System.exit(1);
+        }
+
+        // Invoke the main method of the application class
+        try {
+            if (log.isDebugEnabled())
+                log.debug("Calling main() method");
+            Object paramValues[] = new Object[1];
+            paramValues[0] = params;
+            method.invoke(null, paramValues);
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.error("Exception calling main() method", t);
+            System.exit(1);
+        }
+
+    }
+
+
+    /**
+     * Display usage information about this tool.
+     */
+    private static void usage() {
+
+        log.info("Usage:  java org.apache.catalina.startup.Tool [<options>] <class> [<arguments>]");
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/UserConfig.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/UserConfig.java
new file mode 100644
index 0000000..739681e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/UserConfig.java
@@ -0,0 +1,337 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.startup;
+
+
+import java.io.File;
+import java.util.Enumeration;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Host;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleListener;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Startup event listener for a <b>Host</b> that configures Contexts (web
+ * applications) for all defined "users" who have a web application in a
+ * directory with the specified name in their home directories.  The context
+ * path of each deployed application will be set to <code>~xxxxx</code>, where
+ * xxxxx is the username of the owning user for that web application
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: UserConfig.java,v 1.1 2011/06/28 21:08:16 rherrmann Exp $
+ */
+
+public final class UserConfig
+    implements LifecycleListener {
+
+
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( UserConfig.class );
+
+    
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The Java class name of the Context configuration class we should use.
+     */
+    private String configClass = "org.apache.catalina.startup.ContextConfig";
+
+
+    /**
+     * The Java class name of the Context implementation we should use.
+     */
+    private String contextClass = "org.apache.catalina.core.StandardContext";
+
+
+    /**
+     * The directory name to be searched for within each user home directory.
+     */
+    private String directoryName = "public_html";
+
+
+    /**
+     * The base directory containing user home directories.
+     */
+    private String homeBase = null;
+
+
+    /**
+     * The Host we are associated with.
+     */
+    private Host host = null;
+
+
+    /**
+     * The string resources for this package.
+     */
+    private static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
+     * The Java class name of the user database class we should use.
+     */
+    private String userClass =
+        "org.apache.catalina.startup.PasswdUserDatabase";
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the Context configuration class name.
+     */
+    public String getConfigClass() {
+
+        return (this.configClass);
+
+    }
+
+
+    /**
+     * Set the Context configuration class name.
+     *
+     * @param configClass The new Context configuration class name.
+     */
+    public void setConfigClass(String configClass) {
+
+        this.configClass = configClass;
+
+    }
+
+
+    /**
+     * Return the Context implementation class name.
+     */
+    public String getContextClass() {
+
+        return (this.contextClass);
+
+    }
+
+
+    /**
+     * Set the Context implementation class name.
+     *
+     * @param contextClass The new Context implementation class name.
+     */
+    public void setContextClass(String contextClass) {
+
+        this.contextClass = contextClass;
+
+    }
+
+
+    /**
+     * Return the directory name for user web applications.
+     */
+    public String getDirectoryName() {
+
+        return (this.directoryName);
+
+    }
+
+
+    /**
+     * Set the directory name for user web applications.
+     *
+     * @param directoryName The new directory name
+     */
+    public void setDirectoryName(String directoryName) {
+
+        this.directoryName = directoryName;
+
+    }
+
+
+    /**
+     * Return the base directory containing user home directories.
+     */
+    public String getHomeBase() {
+
+        return (this.homeBase);
+
+    }
+
+
+    /**
+     * Set the base directory containing user home directories.
+     *
+     * @param homeBase The new base directory
+     */
+    public void setHomeBase(String homeBase) {
+
+        this.homeBase = homeBase;
+
+    }
+
+
+    /**
+     * Return the user database class name for this component.
+     */
+    public String getUserClass() {
+
+        return (this.userClass);
+
+    }
+
+
+    /**
+     * Set the user database class name for this component.
+     */
+    public void setUserClass(String userClass) {
+
+        this.userClass = userClass;
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Process the START event for an associated Host.
+     *
+     * @param event The lifecycle event that has occurred
+     */
+    public void lifecycleEvent(LifecycleEvent event) {
+
+        // Identify the host we are associated with
+        try {
+            host = (Host) event.getLifecycle();
+        } catch (ClassCastException e) {
+            log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
+            return;
+        }
+
+        // Process the event that has occurred
+        if (event.getType().equals(Lifecycle.START_EVENT))
+            start();
+        else if (event.getType().equals(Lifecycle.STOP_EVENT))
+            stop();
+
+    }
+
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Deploy a web application for any user who has a web application present
+     * in a directory with a specified name within their home directory.
+     */
+    private void deploy() {
+
+        if (host.getLogger().isDebugEnabled())
+            host.getLogger().debug(sm.getString("userConfig.deploying"));
+
+        // Load the user database object for this host
+        UserDatabase database = null;
+        try {
+            Class<?> clazz = Class.forName(userClass);
+            database = (UserDatabase) clazz.newInstance();
+            database.setUserConfig(this);
+        } catch (Exception e) {
+            host.getLogger().error(sm.getString("userConfig.database"), e);
+            return;
+        }
+
+        // Deploy the web application (if any) for each defined user
+        Enumeration<String> users = database.getUsers();
+        while (users.hasMoreElements()) {
+            String user = users.nextElement();
+            String home = database.getHome(user);
+            deploy(user, home);
+        }
+
+    }
+
+
+    /**
+     * Deploy a web application for the specified user if they have such an
+     * application in the defined directory within their home directory.
+     *
+     * @param user Username owning the application to be deployed
+     * @param home Home directory of this user
+     */
+    private void deploy(String user, String home) {
+
+        // Does this user have a web application to be deployed?
+        String contextPath = "/~" + user;
+        if (host.findChild(contextPath) != null)
+            return;
+        File app = new File(home, directoryName);
+        if (!app.exists() || !app.isDirectory())
+            return;
+        /*
+        File dd = new File(app, "/WEB-INF/web.xml");
+        if (!dd.exists() || !dd.isFile() || !dd.canRead())
+            return;
+        */
+        host.getLogger().info(sm.getString("userConfig.deploy", user));
+
+        // Deploy the web application for this user
+        try {
+            Class<?> clazz = Class.forName(contextClass);
+            Context context =
+              (Context) clazz.newInstance();
+            context.setPath(contextPath);
+            context.setDocBase(app.toString());
+            clazz = Class.forName(configClass);
+            LifecycleListener listener =
+                (LifecycleListener) clazz.newInstance();
+            context.addLifecycleListener(listener);
+            host.addChild(context);
+        } catch (Exception e) {
+            host.getLogger().error(sm.getString("userConfig.error", user), e);
+        }
+
+    }
+
+
+    /**
+     * Process a "start" event for this Host.
+     */
+    private void start() {
+
+        if (host.getLogger().isDebugEnabled())
+            host.getLogger().debug(sm.getString("userConfig.start"));
+
+        deploy();
+
+    }
+
+
+    /**
+     * Process a "stop" event for this Host.
+     */
+    private void stop() {
+
+        if (host.getLogger().isDebugEnabled())
+            host.getLogger().debug(sm.getString("userConfig.stop"));
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/UserDatabase.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/UserDatabase.java
new file mode 100644
index 0000000..dea29f7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/UserDatabase.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.startup;
+
+
+import java.util.Enumeration;
+
+
+/**
+ * Abstraction of the set of users defined by the operating system on the
+ * current server platform.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: UserDatabase.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public interface UserDatabase {
+
+
+    // ----------------------------------------------------------- Properties
+
+
+    /**
+     * Return the UserConfig listener with which we are associated.
+     */
+    public UserConfig getUserConfig();
+
+
+    /**
+     * Set the UserConfig listener with which we are associated.
+     *
+     * @param userConfig The new UserConfig listener
+     */
+    public void setUserConfig(UserConfig userConfig);
+
+
+    // ------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return an absolute pathname to the home directory for the specified user.
+     *
+     * @param user User for which a home directory should be retrieved
+     */
+    public String getHome(String user);
+
+
+    /**
+     * Return an enumeration of the usernames defined on this server.
+     */
+    public Enumeration<String> getUsers();
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/WebAnnotationSet.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/WebAnnotationSet.java
new file mode 100644
index 0000000..48b81ea
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/WebAnnotationSet.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.startup;
+
+
+import javax.annotation.Resource;
+import javax.annotation.Resources;
+import javax.annotation.security.DeclareRoles;
+import javax.annotation.security.RunAs;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Context;
+import org.apache.catalina.core.StandardWrapper;
+import org.apache.catalina.deploy.ContextEnvironment;
+import org.apache.catalina.deploy.ContextResource;
+import org.apache.catalina.deploy.ContextResourceEnvRef;
+import org.apache.catalina.deploy.ContextService;
+import org.apache.catalina.deploy.FilterDef;
+import org.apache.catalina.deploy.MessageDestinationRef;
+
+/**
+ * <p><strong>AnnotationSet</strong> for processing the annotations of the web application
+ * classes (<code>/WEB-INF/classes</code> and <code>/WEB-INF/lib</code>).</p>
+ *
+ * @author Fabien Carrion
+ * @version $Id: WebAnnotationSet.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public class WebAnnotationSet {
+    
+    
+    // --------------------------------------------------------- Public Methods
+    
+    
+    /**
+     * Process the annotations on a context.
+     */
+    public static void loadApplicationAnnotations(Context context) {
+        
+        loadApplicationListenerAnnotations(context);
+        loadApplicationFilterAnnotations(context);
+        loadApplicationServletAnnotations(context);
+        
+        
+    }
+    
+    
+    // -------------------------------------------------------- protected Methods
+    
+    
+    /**
+     * Process the annotations for the listeners.
+     */
+    protected static void loadApplicationListenerAnnotations(Context context) {
+        String[] applicationListeners = context.findApplicationListeners();
+        for (int i = 0; i < applicationListeners.length; i++) {
+            loadClassAnnotation(context, applicationListeners[i]);
+        }
+    }
+    
+    
+    /**
+     * Process the annotations for the filters.
+     */
+    protected static void loadApplicationFilterAnnotations(Context context) {
+        FilterDef[] filterDefs = context.findFilterDefs();
+        for (int i = 0; i < filterDefs.length; i++) {
+            loadClassAnnotation(context, (filterDefs[i]).getFilterClass());
+        }
+    }
+    
+    
+    /**
+     * Process the annotations for the servlets.
+     */
+    protected static void loadApplicationServletAnnotations(Context context) {
+        
+        ClassLoader classLoader = context.getLoader().getClassLoader();
+        StandardWrapper wrapper = null;
+        Class<?> classClass = null;
+        
+        Container[] children = context.findChildren();
+        for (int i = 0; i < children.length; i++) {
+            if (children[i] instanceof StandardWrapper) {
+                
+                wrapper = (StandardWrapper) children[i];
+                if (wrapper.getServletClass() == null) {
+                    continue;
+                }
+                
+                try {
+                    classClass = classLoader.loadClass(wrapper.getServletClass());
+                } catch (ClassNotFoundException e) {
+                    // We do nothing
+                } catch (NoClassDefFoundError e) {
+                    // We do nothing
+                }
+                
+                if (classClass == null) {
+                    continue;
+                }
+                
+                loadClassAnnotation(context, wrapper.getServletClass());
+                /* Process RunAs annotation which can be only on servlets.
+                 * Ref JSR 250, equivalent to the run-as element in
+                 * the deployment descriptor
+                 */
+                if (classClass.isAnnotationPresent(RunAs.class)) {
+                    RunAs annotation = classClass.getAnnotation(RunAs.class);
+                    wrapper.setRunAs(annotation.value());
+                }
+            }
+        }
+        
+        
+    }
+    
+    
+    /**
+     * Process the annotations on a context for a given className.
+     */
+    protected static void loadClassAnnotation(Context context, String fileString) {
+        
+        ClassLoader classLoader = context.getLoader().getClassLoader();
+        Class<?> classClass = null;
+        
+        try {
+            classClass = classLoader.loadClass(fileString);
+        } catch (ClassNotFoundException e) {
+            // We do nothing
+        } catch (NoClassDefFoundError e) {
+            // We do nothing
+        }
+        
+        if (classClass == null) {
+            return;
+        }
+        
+        // Initialize the annotations
+        
+        if (classClass.isAnnotationPresent(Resource.class)) {
+            Resource annotation = classClass.getAnnotation(Resource.class);
+            addResource(context, annotation);
+        }
+        /* Process Resources annotation.
+         * Ref JSR 250
+         */
+        if (classClass.isAnnotationPresent(Resources.class)) {
+            Resources annotation = classClass.getAnnotation(Resources.class);
+            for (int i = 0; annotation.value() != null && i < annotation.value().length; i++) {
+                addResource(context, annotation.value()[i]);
+            }
+        }
+        /* Process EJB annotation.
+         * Ref JSR 224, equivalent to the ejb-ref or ejb-local-ref
+         * element in the deployment descriptor.
+        if (classClass.isAnnotationPresent(EJB.class)) {
+            EJB annotation = (EJB) 
+            classClass.getAnnotation(EJB.class);
+            
+            if ((annotation.mappedName().length() == 0) ||
+                    annotation.mappedName().equals("Local")) {
+                
+                ContextLocalEjb ejb = new ContextLocalEjb();
+                
+                ejb.setName(annotation.name());
+                ejb.setType(annotation.beanInterface().getCanonicalName());
+                ejb.setDescription(annotation.description());
+                
+                ejb.setHome(annotation.beanName());
+                
+                context.getNamingResources().addLocalEjb(ejb);
+                
+            } else if (annotation.mappedName().equals("Remote")) {
+                
+                ContextEjb ejb = new ContextEjb();
+                
+                ejb.setName(annotation.name());
+                ejb.setType(annotation.beanInterface().getCanonicalName());
+                ejb.setDescription(annotation.description());
+                
+                ejb.setHome(annotation.beanName());
+                
+                context.getNamingResources().addEjb(ejb);
+                
+            }
+            
+        }
+         */
+        /* Process WebServiceRef annotation.
+         * Ref JSR 224, equivalent to the service-ref element in 
+         * the deployment descriptor.
+         * The service-ref registration is not implemented
+        if (classClass.isAnnotationPresent(WebServiceRef.class)) {
+            WebServiceRef annotation = (WebServiceRef) 
+            classClass.getAnnotation(WebServiceRef.class);
+            
+            ContextService service = new ContextService();
+            
+            service.setName(annotation.name());
+            service.setWsdlfile(annotation.wsdlLocation());
+            
+            service.setType(annotation.type().getCanonicalName());
+            
+            if (annotation.value() == null)
+                service.setServiceinterface(annotation.type().getCanonicalName());
+            
+            if (annotation.type().getCanonicalName().equals("Service"))
+                service.setServiceinterface(annotation.type().getCanonicalName());
+            
+            if (annotation.value().getCanonicalName().equals("Endpoint"))
+                service.setServiceendpoint(annotation.type().getCanonicalName());
+            
+            service.setPortlink(annotation.type().getCanonicalName());
+            
+            context.getNamingResources().addService(service);
+            
+            
+        }
+         */
+        /* Process DeclareRoles annotation.
+         * Ref JSR 250, equivalent to the security-role element in
+         * the deployment descriptor
+         */
+        if (classClass.isAnnotationPresent(DeclareRoles.class)) {
+            DeclareRoles annotation =
+                classClass.getAnnotation(DeclareRoles.class);
+            for (int i = 0; annotation.value() != null && i < annotation.value().length; i++) {
+                context.addSecurityRole(annotation.value()[i]);
+            }
+        }
+        
+        
+    }
+    
+    
+    /**
+     * Process a Resource annotation to set up a Resource.
+     * Ref JSR 250, equivalent to the resource-ref,
+     * message-destination-ref, env-ref, resource-env-ref
+     * or service-ref element in the deployment descriptor.
+     */
+    protected static void addResource(Context context, Resource annotation) {
+        
+        if (annotation.type().getCanonicalName().equals("java.lang.String") ||
+                annotation.type().getCanonicalName().equals("java.lang.Character") ||
+                annotation.type().getCanonicalName().equals("java.lang.Integer") ||
+                annotation.type().getCanonicalName().equals("java.lang.Boolean") ||
+                annotation.type().getCanonicalName().equals("java.lang.Double") ||
+                annotation.type().getCanonicalName().equals("java.lang.Byte") ||
+                annotation.type().getCanonicalName().equals("java.lang.Short") ||
+                annotation.type().getCanonicalName().equals("java.lang.Long") ||
+                annotation.type().getCanonicalName().equals("java.lang.Float")) {
+            
+            // env-ref element
+            ContextEnvironment resource = new ContextEnvironment();
+            
+            resource.setName(annotation.name());
+            resource.setType(annotation.type().getCanonicalName());
+            
+            resource.setDescription(annotation.description());
+            
+            resource.setValue(annotation.mappedName());
+            
+            context.getNamingResources().addEnvironment(resource);
+            
+        } else if (annotation.type().getCanonicalName().equals("javax.xml.rpc.Service")) {
+            
+            // service-ref element
+            ContextService service = new ContextService();
+            
+            service.setName(annotation.name());
+            service.setWsdlfile(annotation.mappedName());
+            
+            service.setType(annotation.type().getCanonicalName());
+            service.setDescription(annotation.description());
+            
+            context.getNamingResources().addService(service);
+            
+        } else if (annotation.type().getCanonicalName().equals("javax.sql.DataSource") ||
+                annotation.type().getCanonicalName().equals("javax.jms.ConnectionFactory") ||
+                annotation.type().getCanonicalName()
+                .equals("javax.jms.QueueConnectionFactory") ||
+                annotation.type().getCanonicalName()
+                .equals("javax.jms.TopicConnectionFactory") ||
+                annotation.type().getCanonicalName().equals("javax.mail.Session") ||
+                annotation.type().getCanonicalName().equals("java.net.URL") ||
+                annotation.type().getCanonicalName()
+                .equals("javax.resource.cci.ConnectionFactory") ||
+                annotation.type().getCanonicalName().equals("org.omg.CORBA_2_3.ORB") ||
+                annotation.type().getCanonicalName().endsWith("ConnectionFactory")) {
+            
+            // resource-ref element
+            ContextResource resource = new ContextResource();
+            
+            resource.setName(annotation.name());
+            resource.setType(annotation.type().getCanonicalName());
+            
+            if (annotation.authenticationType()
+                    == Resource.AuthenticationType.CONTAINER) {
+                resource.setAuth("Container");
+            }
+            else if (annotation.authenticationType()
+                    == Resource.AuthenticationType.APPLICATION) {
+                resource.setAuth("Application");
+            }
+            
+            resource.setScope(annotation.shareable() ? "Shareable" : "Unshareable");
+            resource.setProperty("mappedName", annotation.mappedName());
+            resource.setDescription(annotation.description());
+            
+            context.getNamingResources().addResource(resource);
+            
+        } else if (annotation.type().getCanonicalName().equals("javax.jms.Queue") ||
+                annotation.type().getCanonicalName().equals("javax.jms.Topic")) {
+            
+            // message-destination-ref
+            MessageDestinationRef resource = new MessageDestinationRef();
+            
+            resource.setName(annotation.name());
+            resource.setType(annotation.type().getCanonicalName());
+            
+            resource.setUsage(annotation.mappedName());
+            resource.setDescription(annotation.description());
+            
+            context.getNamingResources().addMessageDestinationRef(resource);
+            
+        } else if (annotation.type().getCanonicalName()
+                .equals("javax.resource.cci.InteractionSpec") ||
+                annotation.type().getCanonicalName()
+                .equals("javax.transaction.UserTransaction") ||
+                true) {
+            
+            // resource-env-ref
+            ContextResourceEnvRef resource = new ContextResourceEnvRef();
+            
+            resource.setName(annotation.name());
+            resource.setType(annotation.type().getCanonicalName());
+            
+            resource.setProperty("mappedName", annotation.mappedName());
+            resource.setDescription(annotation.description());
+            
+            context.getNamingResources().addResourceEnvRef(resource);
+            
+        }
+        
+        
+    }
+    
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/WebRuleSet.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/WebRuleSet.java
new file mode 100644
index 0000000..c29cb50
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/WebRuleSet.java
@@ -0,0 +1,1173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.startup;
+
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+
+import org.apache.catalina.deploy.ContextHandler;
+import org.apache.catalina.deploy.ContextService;
+import org.apache.catalina.deploy.SecurityConstraint;
+import org.apache.catalina.deploy.ServletDef;
+import org.apache.catalina.deploy.WebXml;
+import org.apache.tomcat.util.IntrospectionUtils;
+import org.apache.tomcat.util.digester.CallMethodRule;
+import org.apache.tomcat.util.digester.CallParamRule;
+import org.apache.tomcat.util.digester.Digester;
+import org.apache.tomcat.util.digester.Rule;
+import org.apache.tomcat.util.digester.RuleSetBase;
+import org.apache.tomcat.util.digester.SetNextRule;
+import org.apache.tomcat.util.res.StringManager;
+import org.xml.sax.Attributes;
+
+
+/**
+ * <p><strong>RuleSet</strong> for processing the contents of a web application
+ * deployment descriptor (<code>/WEB-INF/web.xml</code>) resource.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: WebRuleSet.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public class WebRuleSet extends RuleSetBase {
+
+    /**
+     * The string resources for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The matching pattern prefix to use for recognizing our elements.
+     */
+    protected String prefix = null;
+
+    /**
+     * The full pattern matching prefix, including the webapp or web-fragment
+     * component, to use for matching elements
+     */
+    protected String fullPrefix = null;
+
+    /**
+     * Flag that indicates if this ruleset is for a web-fragment.xml file or for
+     * a web.xml file.
+     */
+    protected boolean fragment = false;
+
+    /**
+     * The <code>SetSessionConfig</code> rule used to parse the web.xml
+     */
+    protected SetSessionConfig sessionConfig;
+    
+    
+    /**
+     * The <code>SetLoginConfig</code> rule used to parse the web.xml
+     */
+    protected SetLoginConfig loginConfig;
+
+    
+    /**
+     * The <code>SetJspConfig</code> rule used to parse the web.xml
+     */    
+    protected SetJspConfig jspConfig;
+
+
+    // ------------------------------------------------------------ Constructor
+
+
+    /**
+     * Construct an instance of this <code>RuleSet</code> with the default
+     * matching pattern prefix and default fragment setting.
+     */
+    public WebRuleSet() {
+
+        this("", false);
+
+    }
+
+
+    /**
+     * Construct an instance of this <code>RuleSet</code> with the default
+     * matching pattern prefix.
+     */
+    public WebRuleSet(boolean fragment) {
+
+        this("", fragment);
+
+    }
+
+
+    /**
+     * 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 WebRuleSet(String prefix, boolean fragment) {
+
+        super();
+        this.namespaceURI = null;
+        this.prefix = prefix;
+        this.fragment = fragment;
+
+        if(fragment) {
+            fullPrefix = prefix + "web-fragment";
+        } else {
+            fullPrefix = prefix + "web-app";
+        }
+
+    }
+
+    // --------------------------------------------------------- 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) {
+        sessionConfig = new SetSessionConfig();
+        jspConfig = new SetJspConfig();
+        loginConfig = new SetLoginConfig();
+        
+        digester.addRule(fullPrefix,
+                         new SetPublicIdRule("setPublicId"));
+        digester.addRule(fullPrefix,
+                         new IgnoreAnnotationsRule());
+        digester.addRule(fullPrefix,
+                new VersionRule());
+
+        if (fragment) {
+            // web-fragment.xml
+            digester.addCallMethod(fullPrefix + "/name",
+                    "setName", 0);
+            digester.addRule(fullPrefix + "/absolute-ordering",
+                    new AbsoluteOrderingRule());
+            digester.addCallMethod(fullPrefix + "/ordering/after/name",
+                                   "addAfterOrdering", 0);
+            digester.addCallMethod(fullPrefix + "/ordering/after/others",
+                                   "addAfterOrderingOthers");
+            digester.addCallMethod(fullPrefix + "/ordering/before/name",
+                                   "addBeforeOrdering", 0);
+            digester.addCallMethod(fullPrefix + "/ordering/before/others",
+                                   "addBeforeOrderingOthers");
+        } else {
+            // web.xml
+            digester.addRule(fullPrefix + "/ordering",
+                    new RelativeOrderingRule());
+            digester.addCallMethod(fullPrefix + "/absolute-ordering/name",
+                                   "addAbsoluteOrdering", 0);
+            digester.addCallMethod(fullPrefix + "/absolute-ordering/name/others",
+                                   "addAbsoluteOrderingOthers");
+        }
+
+        digester.addCallMethod(fullPrefix + "/context-param",
+                               "addContextParam", 2);
+        digester.addCallParam(fullPrefix + "/context-param/param-name", 0);
+        digester.addCallParam(fullPrefix + "/context-param/param-value", 1);
+
+        digester.addCallMethod(fullPrefix + "/display-name",
+                               "setDisplayName", 0);
+
+        digester.addRule(fullPrefix + "/distributable",
+                         new SetDistributableRule());
+
+        configureNamingRules(digester);
+
+        digester.addObjectCreate(fullPrefix + "/error-page",
+                                 "org.apache.catalina.deploy.ErrorPage");
+        digester.addSetNext(fullPrefix + "/error-page",
+                            "addErrorPage",
+                            "org.apache.catalina.deploy.ErrorPage");
+
+        digester.addCallMethod(fullPrefix + "/error-page/error-code",
+                               "setErrorCode", 0);
+        digester.addCallMethod(fullPrefix + "/error-page/exception-type",
+                               "setExceptionType", 0);
+        digester.addCallMethod(fullPrefix + "/error-page/location",
+                               "setLocation", 0);
+
+        digester.addObjectCreate(fullPrefix + "/filter",
+                                 "org.apache.catalina.deploy.FilterDef");
+        digester.addSetNext(fullPrefix + "/filter",
+                            "addFilter",
+                            "org.apache.catalina.deploy.FilterDef");
+
+        digester.addCallMethod(fullPrefix + "/filter/description",
+                               "setDescription", 0);
+        digester.addCallMethod(fullPrefix + "/filter/display-name",
+                               "setDisplayName", 0);
+        digester.addCallMethod(fullPrefix + "/filter/filter-class",
+                               "setFilterClass", 0);
+        digester.addCallMethod(fullPrefix + "/filter/filter-name",
+                               "setFilterName", 0);
+        digester.addCallMethod(fullPrefix + "/filter/icon/large-icon",
+                               "setLargeIcon", 0);
+        digester.addCallMethod(fullPrefix + "/filter/icon/small-icon",
+                               "setSmallIcon", 0);
+        digester.addCallMethod(fullPrefix + "/filter/async-supported",
+                "setAsyncSupported", 0);
+
+        digester.addCallMethod(fullPrefix + "/filter/init-param",
+                               "addInitParameter", 2);
+        digester.addCallParam(fullPrefix + "/filter/init-param/param-name",
+                              0);
+        digester.addCallParam(fullPrefix + "/filter/init-param/param-value",
+                              1);
+
+        digester.addObjectCreate(fullPrefix + "/filter-mapping",
+                                 "org.apache.catalina.deploy.FilterMap");
+        digester.addSetNext(fullPrefix + "/filter-mapping",
+                                 "addFilterMapping",
+                                 "org.apache.catalina.deploy.FilterMap");
+
+        digester.addCallMethod(fullPrefix + "/filter-mapping/filter-name",
+                               "setFilterName", 0);
+        digester.addCallMethod(fullPrefix + "/filter-mapping/servlet-name",
+                               "addServletName", 0);
+        digester.addCallMethod(fullPrefix + "/filter-mapping/url-pattern",
+                               "addURLPattern", 0);
+
+        digester.addCallMethod(fullPrefix + "/filter-mapping/dispatcher",
+                               "setDispatcher", 0);
+
+         digester.addCallMethod(fullPrefix + "/listener/listener-class",
+                                "addListener", 0);
+         
+        digester.addRule(fullPrefix + "/jsp-config",
+                         jspConfig);
+
+        digester.addObjectCreate(fullPrefix + "/jsp-config/jsp-property-group",
+                                 "org.apache.catalina.deploy.JspPropertyGroup");
+        digester.addSetNext(fullPrefix + "/jsp-config/jsp-property-group",
+                            "addJspPropertyGroup",
+                            "org.apache.catalina.deploy.JspPropertyGroup");
+        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/deferred-syntax-allowed-as-literal",
+                               "setDeferredSyntax", 0);
+        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/el-ignored",
+                               "setElIgnored", 0);
+        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/include-coda",
+                               "addIncludeCoda", 0);
+        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/include-prelude",
+                               "addIncludePrelude", 0);
+        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/is-xml",
+                               "setIsXml", 0);
+        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/page-encoding",
+                               "setPageEncoding", 0);
+        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/scripting-invalid",
+                               "setScriptingInvalid", 0);
+        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/trim-directive-whitespaces",
+                               "setTrimWhitespace", 0);
+        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/url-pattern",
+                               "setUrlPattern", 0);
+        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/default-content-type",
+                               "setDefaultContentType", 0);
+        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/buffer",
+                               "setBuffer", 0);
+        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/error-on-undeclared-namespace",
+                               "setErrorOnUndeclaredNamespace", 0);
+
+        digester.addRule(fullPrefix + "/login-config",
+                         loginConfig);
+
+        digester.addObjectCreate(fullPrefix + "/login-config",
+                                 "org.apache.catalina.deploy.LoginConfig");
+        digester.addSetNext(fullPrefix + "/login-config",
+                            "setLoginConfig",
+                            "org.apache.catalina.deploy.LoginConfig");
+
+        digester.addCallMethod(fullPrefix + "/login-config/auth-method",
+                               "setAuthMethod", 0);
+        digester.addCallMethod(fullPrefix + "/login-config/realm-name",
+                               "setRealmName", 0);
+        digester.addCallMethod(fullPrefix + "/login-config/form-login-config/form-error-page",
+                               "setErrorPage", 0);
+        digester.addCallMethod(fullPrefix + "/login-config/form-login-config/form-login-page",
+                               "setLoginPage", 0);
+
+        digester.addCallMethod(fullPrefix + "/mime-mapping",
+                               "addMimeMapping", 2);
+        digester.addCallParam(fullPrefix + "/mime-mapping/extension", 0);
+        digester.addCallParam(fullPrefix + "/mime-mapping/mime-type", 1);
+
+
+        digester.addObjectCreate(fullPrefix + "/security-constraint",
+                                 "org.apache.catalina.deploy.SecurityConstraint");
+        digester.addSetNext(fullPrefix + "/security-constraint",
+                            "addSecurityConstraint",
+                            "org.apache.catalina.deploy.SecurityConstraint");
+
+        digester.addRule(fullPrefix + "/security-constraint/auth-constraint",
+                         new SetAuthConstraintRule());
+        digester.addCallMethod(fullPrefix + "/security-constraint/auth-constraint/role-name",
+                               "addAuthRole", 0);
+        digester.addCallMethod(fullPrefix + "/security-constraint/display-name",
+                               "setDisplayName", 0);
+        digester.addCallMethod(fullPrefix + "/security-constraint/user-data-constraint/transport-guarantee",
+                               "setUserConstraint", 0);
+
+        digester.addObjectCreate(fullPrefix + "/security-constraint/web-resource-collection",
+                                 "org.apache.catalina.deploy.SecurityCollection");
+        digester.addSetNext(fullPrefix + "/security-constraint/web-resource-collection",
+                            "addCollection",
+                            "org.apache.catalina.deploy.SecurityCollection");
+        digester.addCallMethod(fullPrefix + "/security-constraint/web-resource-collection/http-method",
+                               "addMethod", 0);
+        digester.addCallMethod(fullPrefix + "/security-constraint/web-resource-collection/http-method-omission",
+                               "addOmittedMethod", 0);
+        digester.addCallMethod(fullPrefix + "/security-constraint/web-resource-collection/url-pattern",
+                               "addPattern", 0);
+        digester.addCallMethod(fullPrefix + "/security-constraint/web-resource-collection/web-resource-name",
+                               "setName", 0);
+
+        digester.addCallMethod(fullPrefix + "/security-role/role-name",
+                               "addSecurityRole", 0);
+
+        digester.addRule(fullPrefix + "/servlet",
+                         new ServletDefCreateRule());
+        digester.addSetNext(fullPrefix + "/servlet",
+                            "addServlet",
+                            "org.apache.catalina.deploy.ServletDef");
+
+        digester.addCallMethod(fullPrefix + "/servlet/init-param",
+                               "addInitParameter", 2);
+        digester.addCallParam(fullPrefix + "/servlet/init-param/param-name",
+                              0);
+        digester.addCallParam(fullPrefix + "/servlet/init-param/param-value",
+                              1);
+
+        digester.addCallMethod(fullPrefix + "/servlet/jsp-file",
+                               "setJspFile", 0);
+        digester.addCallMethod(fullPrefix + "/servlet/load-on-startup",
+                               "setLoadOnStartup", 0);
+        digester.addCallMethod(fullPrefix + "/servlet/run-as/role-name",
+                               "setRunAs", 0);
+
+        digester.addCallMethod(fullPrefix + "/servlet/security-role-ref",
+                               "addSecurityRoleRef", 2);
+        digester.addCallParam(fullPrefix + "/servlet/security-role-ref/role-link", 1);
+        digester.addCallParam(fullPrefix + "/servlet/security-role-ref/role-name", 0);
+
+        digester.addCallMethod(fullPrefix + "/servlet/servlet-class",
+                              "setServletClass", 0);
+        digester.addCallMethod(fullPrefix + "/servlet/servlet-name",
+                              "setServletName", 0);
+        
+        digester.addObjectCreate(fullPrefix + "/servlet/multipart-config",
+                                 "org.apache.catalina.deploy.MultipartDef");
+        digester.addSetNext(fullPrefix + "/servlet/multipart-config",
+                            "setMultipartDef",
+                            "org.apache.catalina.deploy.MultipartDef");
+        digester.addCallMethod(fullPrefix + "/servlet/multipart-config/location",
+                               "setLocation", 0);
+        digester.addCallMethod(fullPrefix + "/servlet/multipart-config/max-file-size",
+                               "setMaxFileSize", 0);
+        digester.addCallMethod(fullPrefix + "/servlet/multipart-config/max-request-size",
+                               "setMaxRequestSize", 0);
+        digester.addCallMethod(fullPrefix + "/servlet/multipart-config/file-size-threshold",
+                               "setFileSizeThreshold", 0);
+
+        digester.addCallMethod(fullPrefix + "/servlet/async-supported",
+                               "setAsyncSupported", 0);
+        digester.addCallMethod(fullPrefix + "/servlet/enabled",
+                               "setEnabled", 0);
+
+        
+        digester.addRule(fullPrefix + "/servlet-mapping",
+                               new CallMethodMultiRule("addServletMapping", 2, 0));
+        digester.addCallParam(fullPrefix + "/servlet-mapping/servlet-name", 1);
+        digester.addRule(fullPrefix + "/servlet-mapping/url-pattern", new CallParamMultiRule(0));
+
+        digester.addRule(fullPrefix + "/session-config", sessionConfig);
+        digester.addObjectCreate(fullPrefix + "/session-config",
+                                 "org.apache.catalina.deploy.SessionConfig");
+        digester.addSetNext(fullPrefix + "/session-config", "setSessionConfig",
+                            "org.apache.catalina.deploy.SessionConfig");
+        digester.addCallMethod(fullPrefix + "/session-config/session-timeout",
+                               "setSessionTimeout", 0);
+        digester.addCallMethod(fullPrefix + "/session-config/cookie-config/name",
+                               "setCookieName", 0);
+        digester.addCallMethod(fullPrefix + "/session-config/cookie-config/domain",
+                               "setCookieDomain", 0);
+        digester.addCallMethod(fullPrefix + "/session-config/cookie-config/path",
+                               "setCookiePath", 0);
+        digester.addCallMethod(fullPrefix + "/session-config/cookie-config/comment",
+                               "setCookieComment", 0);
+        digester.addCallMethod(fullPrefix + "/session-config/cookie-config/http-only",
+                               "setCookieHttpOnly", 0);
+        digester.addCallMethod(fullPrefix + "/session-config/cookie-config/secure",
+                               "setCookieSecure", 0);
+        digester.addCallMethod(fullPrefix + "/session-config/cookie-config/max-age",
+                               "setCookieMaxAge", 0);
+        digester.addCallMethod(fullPrefix + "/session-config/tracking-mode",
+                               "addSessionTrackingMode", 0);
+
+        // Taglibs pre Servlet 2.4
+        digester.addRule(fullPrefix + "/taglib", new TaglibLocationRule(false));
+        digester.addCallMethod(fullPrefix + "/taglib",
+                               "addTaglib", 2);
+        digester.addCallParam(fullPrefix + "/taglib/taglib-location", 1);
+        digester.addCallParam(fullPrefix + "/taglib/taglib-uri", 0);
+
+        // Taglibs Servlet 2.4 onwards
+        digester.addRule(fullPrefix + "/jsp-config/taglib", new TaglibLocationRule(true));
+        digester.addCallMethod(fullPrefix + "/jsp-config/taglib",
+                "addTaglib", 2);
+        digester.addCallParam(fullPrefix + "/jsp-config/taglib/taglib-location", 1);
+        digester.addCallParam(fullPrefix + "/jsp-config/taglib/taglib-uri", 0);
+
+        digester.addCallMethod(fullPrefix + "/welcome-file-list/welcome-file",
+                               "addWelcomeFile", 0);
+
+        digester.addCallMethod(fullPrefix + "/locale-encoding-mapping-list/locale-encoding-mapping",
+                              "addLocaleEncodingMapping", 2);
+        digester.addCallParam(fullPrefix + "/locale-encoding-mapping-list/locale-encoding-mapping/locale", 0);
+        digester.addCallParam(fullPrefix + "/locale-encoding-mapping-list/locale-encoding-mapping/encoding", 1);
+
+    }
+
+    protected void configureNamingRules(Digester digester) {
+        //ejb-local-ref
+        digester.addObjectCreate(fullPrefix + "/ejb-local-ref",
+                                 "org.apache.catalina.deploy.ContextLocalEjb");
+        digester.addSetNext(fullPrefix + "/ejb-local-ref",
+                            "addEjbLocalRef",
+                            "org.apache.catalina.deploy.ContextLocalEjb");
+        digester.addCallMethod(fullPrefix + "/ejb-local-ref/description",
+                               "setDescription", 0);
+        digester.addCallMethod(fullPrefix + "/ejb-local-ref/ejb-link",
+                               "setLink", 0);
+        digester.addCallMethod(fullPrefix + "/ejb-local-ref/ejb-ref-name",
+                               "setName", 0);
+        digester.addCallMethod(fullPrefix + "/ejb-local-ref/ejb-ref-type",
+                               "setType", 0);
+        digester.addCallMethod(fullPrefix + "/ejb-local-ref/local",
+                               "setLocal", 0);
+        digester.addCallMethod(fullPrefix + "/ejb-local-ref/local-home",
+                               "setHome", 0);
+        configureInjectionRules(digester, "web-app/ejb-local-ref/");
+
+        //ejb-ref
+        digester.addObjectCreate(fullPrefix + "/ejb-ref",
+                                 "org.apache.catalina.deploy.ContextEjb");
+        digester.addSetNext(fullPrefix + "/ejb-ref", 
+                            "addEjbRef",
+                            "org.apache.catalina.deploy.ContextEjb");
+        digester.addCallMethod(fullPrefix + "/ejb-ref/description",
+                               "setDescription", 0);
+        digester.addCallMethod(fullPrefix + "/ejb-ref/ejb-link",
+                               "setLink", 0);
+        digester.addCallMethod(fullPrefix + "/ejb-ref/ejb-ref-name",
+                               "setName", 0);
+        digester.addCallMethod(fullPrefix + "/ejb-ref/ejb-ref-type",
+                               "setType", 0);
+        digester.addCallMethod(fullPrefix + "/ejb-ref/home",
+                               "setHome", 0);
+        digester.addCallMethod(fullPrefix + "/ejb-ref/remote",
+                               "setRemote", 0);
+        configureInjectionRules(digester, "web-app/ejb-ref/");
+
+        //env-entry
+        digester.addObjectCreate(fullPrefix + "/env-entry",
+                                 "org.apache.catalina.deploy.ContextEnvironment");
+        digester.addSetNext(fullPrefix + "/env-entry",
+                            "addEnvEntry",
+                            "org.apache.catalina.deploy.ContextEnvironment");
+        digester.addCallMethod(fullPrefix + "/env-entry/description",
+                               "setDescription", 0);
+        digester.addCallMethod(fullPrefix + "/env-entry/env-entry-name",
+                               "setName", 0);
+        digester.addCallMethod(fullPrefix + "/env-entry/env-entry-type",
+                               "setType", 0);
+        digester.addCallMethod(fullPrefix + "/env-entry/env-entry-value",
+                               "setValue", 0);
+        configureInjectionRules(digester, "web-app/env-entry/");
+
+        //resource-env-ref
+        digester.addObjectCreate(fullPrefix + "/resource-env-ref",
+            "org.apache.catalina.deploy.ContextResourceEnvRef");
+        digester.addSetNext(fullPrefix + "/resource-env-ref",
+                            "addResourceEnvRef",
+                            "org.apache.catalina.deploy.ContextResourceEnvRef");
+        digester.addCallMethod(fullPrefix + "/resource-env-ref/resource-env-ref-name",
+                "setName", 0);
+        digester.addCallMethod(fullPrefix + "/resource-env-ref/resource-env-ref-type",
+                "setType", 0);
+        configureInjectionRules(digester, "web-app/resource-env-ref/");
+
+        //message-destination
+        digester.addObjectCreate(fullPrefix + "/message-destination",
+                                 "org.apache.catalina.deploy.MessageDestination");
+        digester.addSetNext(fullPrefix + "/message-destination",
+                            "addMessageDestination",
+                            "org.apache.catalina.deploy.MessageDestination");
+        digester.addCallMethod(fullPrefix + "/message-destination/description",
+                               "setDescription", 0);
+        digester.addCallMethod(fullPrefix + "/message-destination/display-name",
+                               "setDisplayName", 0);
+        digester.addCallMethod(fullPrefix + "/message-destination/icon/large-icon",
+                               "setLargeIcon", 0);
+        digester.addCallMethod(fullPrefix + "/message-destination/icon/small-icon",
+                               "setSmallIcon", 0);
+        digester.addCallMethod(fullPrefix + "/message-destination/message-destination-name",
+                               "setName", 0);
+
+        //message-destination-ref
+        digester.addObjectCreate(fullPrefix + "/message-destination-ref",
+                                 "org.apache.catalina.deploy.MessageDestinationRef");
+        digester.addSetNext(fullPrefix + "/message-destination-ref",
+                            "addMessageDestinationRef",
+                            "org.apache.catalina.deploy.MessageDestinationRef");
+        digester.addCallMethod(fullPrefix + "/message-destination-ref/description",
+                               "setDescription", 0);
+        digester.addCallMethod(fullPrefix + "/message-destination-ref/message-destination-link",
+                               "setLink", 0);
+        digester.addCallMethod(fullPrefix + "/message-destination-ref/message-destination-ref-name",
+                               "setName", 0);
+        digester.addCallMethod(fullPrefix + "/message-destination-ref/message-destination-type",
+                               "setType", 0);
+        digester.addCallMethod(fullPrefix + "/message-destination-ref/message-destination-usage",
+                               "setUsage", 0);
+
+        configureInjectionRules(digester, "web-app/message-destination-ref/");
+
+        //resource-ref
+        digester.addObjectCreate(fullPrefix + "/resource-ref",
+                                 "org.apache.catalina.deploy.ContextResource");
+        digester.addSetNext(fullPrefix + "/resource-ref",
+                            "addResourceRef",
+                            "org.apache.catalina.deploy.ContextResource");
+        digester.addCallMethod(fullPrefix + "/resource-ref/description",
+                               "setDescription", 0);
+        digester.addCallMethod(fullPrefix + "/resource-ref/res-auth",
+                               "setAuth", 0);
+        digester.addCallMethod(fullPrefix + "/resource-ref/res-ref-name",
+                               "setName", 0);
+        digester.addCallMethod(fullPrefix + "/resource-ref/res-sharing-scope",
+                               "setScope", 0);
+        digester.addCallMethod(fullPrefix + "/resource-ref/res-type",
+                               "setType", 0);
+        configureInjectionRules(digester, "web-app/resource-ref/");
+
+        //service-ref
+        digester.addObjectCreate(fullPrefix + "/service-ref",
+                                 "org.apache.catalina.deploy.ContextService");
+        digester.addSetNext(fullPrefix + "/service-ref",
+                            "addServiceRef",
+                            "org.apache.catalina.deploy.ContextService");
+        digester.addCallMethod(fullPrefix + "/service-ref/description",
+                               "setDescription", 0);
+        digester.addCallMethod(fullPrefix + "/service-ref/display-name",
+                               "setDisplayname", 0);
+        digester.addCallMethod(fullPrefix + "/service-ref/icon/large-icon",
+                               "setLargeIcon", 0);
+        digester.addCallMethod(fullPrefix + "/service-ref/icon/small-icon",
+                               "setSmallIcon", 0);
+        digester.addCallMethod(fullPrefix + "/service-ref/service-ref-name",
+                               "setName", 0);
+        digester.addCallMethod(fullPrefix + "/service-ref/service-interface",
+                               "setInterface", 0);
+        digester.addCallMethod(fullPrefix + "/service-ref/service-ref-type",
+                               "setType", 0);
+        digester.addCallMethod(fullPrefix + "/service-ref/wsdl-file",
+                               "setWsdlfile", 0);
+        digester.addCallMethod(fullPrefix + "/service-ref/jaxrpc-mapping-file",
+                               "setJaxrpcmappingfile", 0);
+        digester.addRule(fullPrefix + "/service-ref/service-qname", new ServiceQnameRule());
+
+        digester.addRule(fullPrefix + "/service-ref/port-component-ref",
+                               new CallMethodMultiRule("addPortcomponent", 2, 1));
+        digester.addCallParam(fullPrefix + "/service-ref/port-component-ref/service-endpoint-interface", 0);
+        digester.addRule(fullPrefix + "/service-ref/port-component-ref/port-component-link", new CallParamMultiRule(1));
+
+        digester.addObjectCreate(fullPrefix + "/service-ref/handler",
+                                 "org.apache.catalina.deploy.ContextHandler");
+        digester.addRule(fullPrefix + "/service-ref/handler",
+                         new SetNextRule("addHandler",
+                         "org.apache.catalina.deploy.ContextHandler"));
+
+        digester.addCallMethod(fullPrefix + "/service-ref/handler/handler-name",
+                               "setName", 0);
+        digester.addCallMethod(fullPrefix + "/service-ref/handler/handler-class",
+                               "setHandlerclass", 0);
+
+        digester.addCallMethod(fullPrefix + "/service-ref/handler/init-param",
+                               "setProperty", 2);
+        digester.addCallParam(fullPrefix + "/service-ref/handler/init-param/param-name",
+                              0);
+        digester.addCallParam(fullPrefix + "/service-ref/handler/init-param/param-value",
+                              1);
+
+        digester.addRule(fullPrefix + "/service-ref/handler/soap-header", new SoapHeaderRule());
+
+        digester.addCallMethod(fullPrefix + "/service-ref/handler/soap-role",
+                               "addSoapRole", 0);
+        digester.addCallMethod(fullPrefix + "/service-ref/handler/port-name",
+                               "addPortName", 0);
+        configureInjectionRules(digester, "web-app/service-ref/");
+
+
+    }
+
+    protected void configureInjectionRules(Digester digester, String base) {
+
+        digester.addCallMethod(prefix + base + "injection-target", "addInjectionTarget", 2);
+        digester.addCallParam(prefix + base + "injection-target/injection-target-class", 0);
+        digester.addCallParam(prefix + base + "injection-target/injection-target-name", 1);
+
+    }
+
+
+    /**
+     * Reset counter used for validating the web.xml file.
+     */
+    public void recycle(){
+        jspConfig.isJspConfigSet = false;
+        sessionConfig.isSessionConfigSet = false;
+        loginConfig.isLoginConfigSet = false;
+    }
+}
+
+
+// ----------------------------------------------------------- Private Classes
+
+
+/**
+ * Rule to check that the <code>login-config</code> is occurring 
+ * only 1 time within the web.xml
+ */
+final class SetLoginConfig extends Rule {
+    protected boolean isLoginConfigSet = false;
+    public SetLoginConfig() {
+        // NO-OP
+    }
+
+    @Override
+    public void begin(String namespace, String name, Attributes attributes)
+        throws Exception {
+        if (isLoginConfigSet){
+            throw new IllegalArgumentException(
+            "<login-config> element is limited to 1 occurrence");
+        }
+        isLoginConfigSet = true;
+    }
+
+}
+
+
+/**
+ * Rule to check that the <code>jsp-config</code> is occurring 
+ * only 1 time within the web.xml
+ */
+final class SetJspConfig extends Rule {
+    protected boolean isJspConfigSet = false;
+    public SetJspConfig() {
+        // NO-OP
+    }
+
+    @Override
+    public void begin(String namespace, String name, Attributes attributes)
+        throws Exception {
+        if (isJspConfigSet){
+            throw new IllegalArgumentException(
+            "<jsp-config> element is limited to 1 occurrence");
+        }
+        isJspConfigSet = true;
+    }
+
+}
+
+
+/**
+ * Rule to check that the <code>session-config</code> is occurring 
+ * only 1 time within the web.xml
+ */
+final class SetSessionConfig extends Rule {
+    protected boolean isSessionConfigSet = false;
+    public SetSessionConfig() {
+        // NO-OP
+    }
+
+    @Override
+    public void begin(String namespace, String name, Attributes attributes)
+        throws Exception {
+        if (isSessionConfigSet){
+            throw new IllegalArgumentException(
+            "<session-config> element is limited to 1 occurrence");
+        }
+        isSessionConfigSet = true;
+    }
+
+}
+
+/**
+ * A Rule that calls the <code>setAuthConstraint(true)</code> method of
+ * the top item on the stack, which must be of type
+ * <code>org.apache.catalina.deploy.SecurityConstraint</code>.
+ */
+
+final class SetAuthConstraintRule extends Rule {
+
+    public SetAuthConstraintRule() {
+        // NO-OP
+    }
+
+    @Override
+    public void begin(String namespace, String name, Attributes attributes)
+        throws Exception {
+        SecurityConstraint securityConstraint =
+            (SecurityConstraint) digester.peek();
+        securityConstraint.setAuthConstraint(true);
+        if (digester.getLogger().isDebugEnabled()) {
+            digester.getLogger()
+               .debug("Calling SecurityConstraint.setAuthConstraint(true)");
+        }
+    }
+
+}
+
+
+/**
+ * Class that calls <code>setDistributable(true)</code> for the top object
+ * on the stack, which must be a <code>org.apache.catalina.Context</code>.
+ */
+
+final class SetDistributableRule extends Rule {
+
+    public SetDistributableRule() {
+        // NO-OP
+    }
+
+    @Override
+    public void begin(String namespace, String name, Attributes attributes)
+        throws Exception {
+        WebXml webXml = (WebXml) digester.peek();
+        webXml.setDistributable(true);
+        if (digester.getLogger().isDebugEnabled()) {
+            digester.getLogger().debug
+               (webXml.getClass().getName() + ".setDistributable(true)");
+        }
+    }
+
+}
+
+
+/**
+ * Class that calls a property setter for the top object on the stack,
+ * passing the public ID of the entity we are currently processing.
+ */
+
+final class SetPublicIdRule extends Rule {
+
+    public SetPublicIdRule(String method) {
+        this.method = method;
+    }
+
+    private String method = null;
+
+    @Override
+    public void begin(String namespace, String name, Attributes attributes)
+        throws Exception {
+
+        Object top = digester.peek();
+        Class<?> paramClasses[] = new Class[1];
+        paramClasses[0] = "String".getClass();
+        String paramValues[] = new String[1];
+        paramValues[0] = digester.getPublicId();
+
+        Method m = null;
+        try {
+            m = top.getClass().getMethod(method, paramClasses);
+        } catch (NoSuchMethodException e) {
+            digester.getLogger().error("Can't find method " + method + " in "
+                                       + top + " CLASS " + top.getClass());
+            return;
+        }
+
+        m.invoke(top, (Object [])paramValues);
+        if (digester.getLogger().isDebugEnabled())
+            digester.getLogger().debug("" + top.getClass().getName() + "." 
+                                       + method + "(" + paramValues[0] + ")");
+
+    }
+
+}
+
+
+/**
+ * A Rule that calls the factory method on the specified Context to
+ * create the object that is to be added to the stack.
+ */
+
+final class ServletDefCreateRule extends Rule {
+
+    public ServletDefCreateRule() {
+        // NO-OP
+    }
+
+    @Override
+    public void begin(String namespace, String name, Attributes attributes)
+        throws Exception {
+        ServletDef servletDef = new ServletDef();
+        digester.push(servletDef);
+        if (digester.getLogger().isDebugEnabled())
+            digester.getLogger().debug("new " + servletDef.getClass().getName());
+    }
+
+    @Override
+    public void end(String namespace, String name)
+        throws Exception {
+        ServletDef servletDef = (ServletDef) digester.pop();
+        if (digester.getLogger().isDebugEnabled())
+            digester.getLogger().debug("pop " + servletDef.getClass().getName());
+    }
+
+}
+
+
+/**
+ * A Rule that can be used to call multiple times a method as many times as needed
+ * (used for addServletMapping).
+ */
+final class CallParamMultiRule extends CallParamRule {
+
+    public CallParamMultiRule(int paramIndex) {
+        super(paramIndex);
+    }
+
+    @Override
+    public void end(String namespace, String name) {
+        if (bodyTextStack != null && !bodyTextStack.empty()) {
+            // what we do now is push one parameter onto the top set of parameters
+            Object parameters[] = (Object[]) digester.peekParams();
+            ArrayList<String> params = (ArrayList<String>) parameters[paramIndex];
+            if (params == null) {
+                params = new ArrayList<String>();
+                parameters[paramIndex] = params;
+            }
+            params.add(bodyTextStack.pop());
+        }
+    }
+
+}
+
+
+/**
+ * A Rule that can be used to call multiple times a method as many times as needed
+ * (used for addServletMapping).
+ */
+final class CallMethodMultiRule extends CallMethodRule {
+
+    protected int multiParamIndex = 0;
+    
+    public CallMethodMultiRule(String methodName, int paramCount, int multiParamIndex) {
+        super(methodName, paramCount);
+        this.multiParamIndex = multiParamIndex;
+    }
+
+    /**
+     * Process the end of this element.
+     * 
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the parser is not namespace aware or the element has
+     *   no namespace
+     * @param name the local name if the parser is namespace aware, or just 
+     *   the element name otherwise
+     */
+    @Override
+    public void end(String namespace, String name) throws Exception {
+
+        // Retrieve or construct the parameter values array
+        Object parameters[] = null;
+        if (paramCount > 0) {
+            parameters = (Object[]) digester.popParams();
+        } else {
+            parameters = new Object[0];
+            super.end(namespace, name);
+        }
+        
+        ArrayList<?> multiParams = (ArrayList<?>) parameters[multiParamIndex];
+        
+        // Construct the parameter values array we will need
+        // We only do the conversion if the param value is a String and
+        // the specified paramType is not String. 
+        Object paramValues[] = new Object[paramTypes.length];
+        for (int i = 0; i < paramTypes.length; i++) {
+            if (i != multiParamIndex) {
+                // convert nulls and convert stringy parameters 
+                // for non-stringy param types
+                if(parameters[i] == null || (parameters[i] instanceof String 
+                        && !String.class.isAssignableFrom(paramTypes[i]))) {
+                    paramValues[i] =
+                        IntrospectionUtils.convert((String) parameters[i], paramTypes[i]);
+                } else {
+                    paramValues[i] = parameters[i];
+                }
+            }
+        }
+
+        // Determine the target object for the method call
+        Object target;
+        if (targetOffset >= 0) {
+            target = digester.peek(targetOffset);
+        } else {
+            target = digester.peek(digester.getCount() + targetOffset);
+        }
+
+        if (target == null) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("[CallMethodRule]{");
+            sb.append("");
+            sb.append("} Call target is null (");
+            sb.append("targetOffset=");
+            sb.append(targetOffset);
+            sb.append(",stackdepth=");
+            sb.append(digester.getCount());
+            sb.append(")");
+            throw new org.xml.sax.SAXException(sb.toString());
+        }
+        
+        if (multiParams == null) {
+            paramValues[multiParamIndex] = null;
+            IntrospectionUtils.callMethodN(target, methodName, paramValues,
+                    paramTypes);   
+            return;
+        }
+        
+        for (int j = 0; j < multiParams.size(); j++) {
+            Object param = multiParams.get(j);
+            if(param == null || (param instanceof String 
+                    && !String.class.isAssignableFrom(paramTypes[multiParamIndex]))) {
+                paramValues[multiParamIndex] =
+                    IntrospectionUtils.convert((String) param, paramTypes[multiParamIndex]);
+            } else {
+                paramValues[multiParamIndex] = param;
+            }
+            IntrospectionUtils.callMethodN(target, methodName, paramValues,
+                    paramTypes);   
+        }
+        
+    }
+
+}
+
+
+
+/**
+ * A Rule that check if the annotations have to be loaded.
+ * 
+ */
+
+final class IgnoreAnnotationsRule extends Rule {
+
+    public IgnoreAnnotationsRule() {
+        // NO-OP
+    }
+
+    @Override
+    public void begin(String namespace, String name, Attributes attributes)
+        throws Exception {
+        WebXml webxml = (WebXml) digester.peek(digester.getCount() - 1);
+        String value = attributes.getValue("metadata-complete");
+        if ("true".equals(value)) {
+            webxml.setMetadataComplete(true);
+        } else if ("false".equals(value)) {
+            webxml.setMetadataComplete(false);
+        }
+        if (digester.getLogger().isDebugEnabled()) {
+            digester.getLogger().debug
+                (webxml.getClass().getName() + ".setMetadataComplete( " +
+                        webxml.isMetadataComplete() + ")");
+        }
+    }
+
+}
+
+/**
+ * A Rule that records the spec version of the web.xml being parsed
+ * 
+ */
+
+final class VersionRule extends Rule {
+
+    public VersionRule() {
+        // NO-OP
+    }
+
+    @Override
+    public void begin(String namespace, String name, Attributes attributes)
+        throws Exception {
+        WebXml webxml = (WebXml) digester.peek(digester.getCount() - 1);
+        webxml.setVersion(attributes.getValue("version"));
+        
+        if (digester.getLogger().isDebugEnabled()) {
+            digester.getLogger().debug
+                (webxml.getClass().getName() + ".setVersion( " +
+                        webxml.getVersion() + ")");
+        }
+    }
+
+}
+
+
+/**
+ * A rule that logs a warning if absolute ordering is configured.
+ */
+final class AbsoluteOrderingRule extends Rule {
+    
+    public AbsoluteOrderingRule() {
+        // NO-OP
+    }
+    
+    @Override
+    public void begin(String namespace, String name, Attributes attributes)
+            throws Exception {
+        digester.getLogger().warn(
+                WebRuleSet.sm.getString("webRuleSet.absoluteOrdering"));
+    } 
+}
+
+/**
+ * A rule that logs a warning if relative ordering is configured.
+ */
+final class RelativeOrderingRule extends Rule {
+    
+    public RelativeOrderingRule() {
+        // NO-OP
+    }
+    
+    @Override
+    public void begin(String namespace, String name, Attributes attributes)
+            throws Exception {
+        digester.getLogger().warn(
+                WebRuleSet.sm.getString("webRuleSet.relativeOrdering"));
+    } 
+}
+
+/**
+ * A Rule that sets soap headers on the ContextHandler.
+ * 
+ */
+final class SoapHeaderRule extends Rule {
+
+    public SoapHeaderRule() {
+        // NO-OP
+    }
+
+    /**
+     * Process the body text of this element.
+     *
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the parser is not namespace aware or the element has
+     *   no namespace
+     * @param name the local name if the parser is namespace aware, or just 
+     *   the element name otherwise
+     * @param text The body text of this element
+     */
+    @Override
+    public void body(String namespace, String name, String text)
+            throws Exception {
+        String namespaceuri = null;
+        String localpart = text;
+        int colon = text.indexOf(':');
+        if (colon >= 0) {
+            String prefix = text.substring(0,colon);
+            namespaceuri = digester.findNamespaceURI(prefix);
+            localpart = text.substring(colon+1);
+        }
+        ContextHandler contextHandler = (ContextHandler)digester.peek();
+        contextHandler.addSoapHeaders(localpart,namespaceuri);
+    }
+}
+
+/**
+ * A Rule that sets service qname on the ContextService.
+ * 
+ */
+final class ServiceQnameRule extends Rule {
+
+    public ServiceQnameRule() {
+        // NO-OP
+    }
+
+    /**
+     * Process the body text of this element.
+     *
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the parser is not namespace aware or the element has
+     *   no namespace
+     * @param name the local name if the parser is namespace aware, or just 
+     *   the element name otherwise
+     * @param text The body text of this element
+     */
+    @Override
+    public void body(String namespace, String name, String text)
+            throws Exception {
+        String namespaceuri = null;
+        String localpart = text;
+        int colon = text.indexOf(':');
+        if (colon >= 0) {
+            String prefix = text.substring(0,colon);
+            namespaceuri = digester.findNamespaceURI(prefix);
+            localpart = text.substring(colon+1);
+        }
+        ContextService contextService = (ContextService)digester.peek();
+        contextService.setServiceqnameLocalpart(localpart);
+        contextService.setServiceqnameNamespaceURI(namespaceuri);
+    }
+    
+}
+
+/**
+ * A rule that checks if the taglib element is in the right place. 
+ */
+final class TaglibLocationRule extends Rule {
+
+    final boolean isServlet24OrLater;
+    
+    public TaglibLocationRule(boolean isServlet24OrLater) {
+        this.isServlet24OrLater = isServlet24OrLater;
+    }
+    
+    @Override
+    public void begin(String namespace, String name, Attributes attributes)
+            throws Exception {
+        WebXml webXml = (WebXml) digester.peek(digester.getCount() - 1);
+        // If we have a public ID, this is not a 2.4 or later webapp
+        boolean havePublicId = (webXml.getPublicId() != null);
+        // havePublicId and isServlet24OrLater should be mutually exclusive
+        if (havePublicId == isServlet24OrLater) {
+            throw new IllegalArgumentException(
+                    "taglib definition not consistent with specification version");
+        }
+    }
+
+    
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/XmlErrorHandler.java b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/XmlErrorHandler.java
new file mode 100644
index 0000000..deb439a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/XmlErrorHandler.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.startup;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.juli.logging.Log;
+import org.apache.tomcat.util.res.StringManager;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+public class XmlErrorHandler implements ErrorHandler {
+
+    private static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    private Set<SAXParseException> errors =
+        new HashSet<SAXParseException>();
+    
+    private Set<SAXParseException> warnings =
+        new HashSet<SAXParseException>();
+
+    @Override
+    public void error(SAXParseException exception) throws SAXException {
+        // Collect non-fatal errors
+        errors.add(exception);
+    }
+
+    @Override
+    public void fatalError(SAXParseException exception) throws SAXException {
+        // Re-throw fatal errors
+        throw exception;
+    }
+
+    @Override
+    public void warning(SAXParseException exception) throws SAXException {
+        // Collect warnings
+        warnings.add(exception);
+    }
+    
+    public Set<SAXParseException> getErrors() {
+        // Internal use only - don't worry about immutability
+        return errors;
+    }
+    
+    public Set<SAXParseException> getWarnings() {
+        // Internal use only - don't worry about immutability
+        return warnings;
+    }
+
+    public void logFindings(Log log, String source) {
+        for (SAXParseException e : getWarnings()) {
+            log.warn(sm.getString(
+                    "xmlErrorHandler.warning", e.getMessage(), source));
+        }
+        for (SAXParseException e : getErrors()) {
+            log.warn(sm.getString(
+                    "xmlErrorHandler.error", e.getMessage(), source));
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/catalina.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/catalina.properties
new file mode 100644
index 0000000..d226f8a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/catalina.properties
@@ -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.
+
+#
+# List of comma-separated packages that start with or equal this string
+# will cause a security exception to be thrown when
+# passed to checkPackageAccess unless the
+# corresponding RuntimePermission ("accessClassInPackage."+package) has
+# been granted.
+package.access=sun.,org.apache.catalina.,org.apache.coyote.,org.apache.tomcat.,org.apache.jasper.
+#
+# List of comma-separated packages that start with or equal this string
+# will cause a security exception to be thrown when
+# passed to checkPackageDefinition unless the
+# corresponding RuntimePermission ("defineClassInPackage."+package) has
+# been granted.
+#
+# by default, no packages are restricted for definition, and none of
+# the class loaders supplied with the JDK call checkPackageDefinition.
+#
+package.definition=sun.,java.,org.apache.catalina.,org.apache.coyote.,org.apache.tomcat.,org.apache.jasper.
+
+#
+#
+# List of comma-separated paths defining the contents of the "common" 
+# classloader. Prefixes should be used to define what is the repository type.
+# Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute.
+# If left as blank,the JVM system loader will be used as Catalina's "common" 
+# loader.
+# Examples:
+#     "foo": Add this folder as a class repository
+#     "foo/*.jar": Add all the JARs of the specified folder as class 
+#                  repositories
+#     "foo/bar.jar": Add bar.jar as a class repository
+common.loader=${catalina.home}/lib,${catalina.home}/lib/*.jar
+
+#
+# List of comma-separated paths defining the contents of the "server" 
+# classloader. Prefixes should be used to define what is the repository type.
+# Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute.
+# If left as blank, the "common" loader will be used as Catalina's "server" 
+# loader.
+# Examples:
+#     "foo": Add this folder as a class repository
+#     "foo/*.jar": Add all the JARs of the specified folder as class 
+#                  repositories
+#     "foo/bar.jar": Add bar.jar as a class repository
+server.loader=
+
+#
+# List of comma-separated paths defining the contents of the "shared" 
+# classloader. Prefixes should be used to define what is the repository type.
+# Path may be relative to the CATALINA_BASE path or absolute. If left as blank,
+# the "common" loader will be used as Catalina's "shared" loader.
+# Examples:
+#     "foo": Add this folder as a class repository
+#     "foo/*.jar": Add all the JARs of the specified folder as class 
+#                  repositories
+#     "foo/bar.jar": Add bar.jar as a class repository 
+# Please note that for single jars, e.g. bar.jar, you need the URL form
+# starting with file:.
+shared.loader=
+
+#
+# String cache configuration.
+tomcat.util.buf.StringCache.byte.enabled=true
+#tomcat.util.buf.StringCache.char.enabled=true
+#tomcat.util.buf.StringCache.trainThreshold=500000
+#tomcat.util.buf.StringCache.cacheSize=5000
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/startup/mbeans-descriptors.xml b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/mbeans-descriptors.xml
new file mode 100644
index 0000000..cffff34
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/startup/mbeans-descriptors.xml
@@ -0,0 +1,168 @@
+<?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="ContextConfig"
+         description="Startup event listener for a Context that configures the properties of that Context, and the associated defined servlets"
+         domain="Catalina"
+         group="Listener"
+         type="org.apache.catalina.startup.ContextConfig">
+    
+    <attribute name="className"
+               description="Fully qualified class name of the managed object"
+               type="java.lang.String"
+               writeable="false"/>
+               
+    <attribute name="defaultContextXml"
+               description="The location of the default context file"
+               type="java.lang.String"/>  
+
+    <attribute name="defaultWebXml"
+               description="The location of the default deployment descriptor"
+               type="java.lang.String"/>     
+
+  </mbean>
+
+  <mbean name="EngineConfig"
+         description="Startup event listener for a Engine that configures the properties of that Engine, and the associated defined contexts"
+         domain="Catalina"
+         group="Listener"
+         type="org.apache.catalina.startup.EngineConfig">
+
+    <attribute name="className"
+               description="Fully qualified class name of the managed object"
+               type="java.lang.String"
+               writeable="false"/>
+      
+  </mbean>
+
+
+  <mbean name="HostConfig"
+         description="Startup event listener for a Host that configures the properties of that Host, and the associated defined contexts"
+         domain="Catalina"
+         group="Listener"
+         type="org.apache.catalina.startup.HostConfig">
+    
+    <attribute name="className"
+               description="Fully qualified class name of the managed object"
+               type="java.lang.String"
+               writeable="false"/>
+
+    <attribute name="configBaseName"
+               description="The base directory for Context configuration files"
+               type="java.lang.String"
+               writeable="false" />
+
+    <attribute name="configClass"
+               description="The Java class name of the Context configuration class we should use"
+               type="java.lang.String"/>
+
+    <attribute name="contextClass"
+               description="The Java class name of the Context implementation we should use"
+               type="java.lang.String"/>
+               
+     <attribute name="copyXML"
+               description="The copy XML config file flag for this component"
+               is="true"
+               type="boolean"/>
+               
+     <attribute name="deployXML"
+               description="The deploy XML config file flag for this component"
+               is="true"
+               type="boolean"/>
+               
+     <attribute name="unpackWARs"
+               description="The unpack WARs flag"
+               is="true"
+               type="boolean"/>
+               
+    <operation name="addServiced"
+               description="Add a web application to the serviced list to show it gets serviced by another component"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="name"
+                 description="Application name"
+                 type="java.lang.String"/>
+    </operation>
+      
+    <operation name="check"
+               description="Check a web application name for updates"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="name"
+                 description="Application name"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="getDeploymentTime"
+               description="Get the instant where an application was deployed"
+               impact="ACTION"
+               returnType="long">
+      <parameter name="name"
+                 description="Application name"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation name="isDeployed"
+               description="Was this web application deployed by this component"
+               impact="ACTION"
+               returnType="boolean">
+      <parameter name="name"
+                 description="Application name"
+                 type="java.lang.String"/>
+    </operation>
+    
+    <operation name="isServiced"
+               description="Is a web application serviced by another component"
+               impact="ACTION"
+               returnType="boolean">
+      <parameter name="name"
+                 description="Application name"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation name="manageApp"
+               description="Add a web application managed externally"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="context"
+                 description="Context to add"
+                 type="org.apache.catalina.Context" />
+    </operation>
+
+    <operation name="removeServiced"
+               description="Remove a web application from the serviced list to show it isn't serviced by another component"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="name"
+                 description="Application name"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation name="unmanageApp"
+               description="Remove a web application from checks"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="contextPath"
+                 description="The application path"
+                 type="java.lang.String" />
+    </operation>
+
+  </mbean>
+
+</mbeans-descriptors>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/DomainFilterInterceptor.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/DomainFilterInterceptor.java
new file mode 100644
index 0000000..fc81f2f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/DomainFilterInterceptor.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.catalina.tribes.group.interceptors;
+
+import java.util.Arrays;
+
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+import org.apache.catalina.tribes.membership.MemberImpl;
+import org.apache.catalina.tribes.membership.Membership;
+
+/**
+ * <p>Title: Member domain filter interceptor </p>
+ *
+ * <p>Description: Filters membership based on domain.
+ * </p>
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class DomainFilterInterceptor extends ChannelInterceptorBase {
+
+    protected Membership membership = null;
+    
+    protected byte[] domain = new byte[0];
+
+    @Override
+    public void messageReceived(ChannelMessage msg) {
+        //should we filter incoming based on domain?
+        super.messageReceived(msg);
+    }//messageReceived
+
+
+    @Override
+    public void memberAdded(Member member) {
+        if ( membership == null ) setupMembership();
+        boolean notify = false;
+        synchronized (membership) {
+            notify = Arrays.equals(domain,member.getDomain());
+            if ( notify ) notify = membership.memberAlive((MemberImpl)member);
+        }
+        if ( notify ) super.memberAdded(member);
+    }
+
+    @Override
+    public void memberDisappeared(Member member) {
+        if ( membership == null ) setupMembership();
+        boolean notify = false;
+        synchronized (membership) {
+            notify = Arrays.equals(domain,member.getDomain());
+            membership.removeMember((MemberImpl)member);
+        }
+        if ( notify ) super.memberDisappeared(member);
+    }
+
+    @Override
+    public boolean hasMembers() {
+        if ( membership == null ) setupMembership();
+        return membership.hasMembers();
+    }
+
+    @Override
+    public Member[] getMembers() {
+        if ( membership == null ) setupMembership();
+        return membership.getMembers();
+    }
+
+    @Override
+    public Member getMember(Member mbr) {
+        if ( membership == null ) setupMembership();
+        return membership.getMember(mbr);
+    }
+
+    @Override
+    public Member getLocalMember(boolean incAlive) {
+        return super.getLocalMember(incAlive);
+    }
+
+
+    protected synchronized void setupMembership() {
+        if ( membership == null ) {
+            membership = new Membership((MemberImpl)super.getLocalMember(true));
+        }
+
+    }
+
+    public byte[] getDomain() {
+        return domain;
+    }
+
+    public void setDomain(byte[] domain) {
+        this.domain = domain;
+    }
+
+    public void setDomain(String domain) {
+        if ( domain == null ) return;
+        if (domain.startsWith("{"))
+            setDomain(org.apache.catalina.tribes.util.Arrays.fromString(domain));
+        else
+            setDomain(org.apache.catalina.tribes.util.Arrays.convert(domain));
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/FragmentationInterceptor.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/FragmentationInterceptor.java
new file mode 100644
index 0000000..f18ba4b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/FragmentationInterceptor.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.tribes.group.interceptors;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Set;
+
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+import org.apache.catalina.tribes.group.InterceptorPayload;
+import org.apache.catalina.tribes.io.XByteBuffer;
+
+/**
+ *
+ * The fragmentation interceptor splits up large messages into smaller messages and assembles them on the other end.
+ * This is very useful when you don't want large messages hogging the sending sockets
+ * and smaller messages can make it through.
+ * 
+ * <br><b>Configuration Options</b><br>
+ * OrderInteceptor.expire=<milliseconds> - how long do we keep the fragments in memory and wait for the rest to arrive<b>default=60,000ms -> 60seconds</b>
+ * This setting is useful to avoid OutOfMemoryErrors<br>
+ * OrderInteceptor.maxSize=<max message size> - message size in bytes <b>default=1024*100 (around a tenth of a MB)</b><br>
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class FragmentationInterceptor extends ChannelInterceptorBase {
+    private static final org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog( FragmentationInterceptor.class );
+    
+    protected HashMap<FragKey, FragCollection> fragpieces = new HashMap<FragKey, FragCollection>();
+    private int maxSize = 1024*100;
+    private long expire = 1000 * 60; //one minute expiration
+    protected boolean deepclone = true;
+
+
+    @Override
+    public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+        int size = msg.getMessage().getLength();
+        boolean frag = (size>maxSize) && okToProcess(msg.getOptions());
+        if ( frag ) {
+            frag(destination, msg, payload);
+        } else {
+            msg.getMessage().append(frag);
+            super.sendMessage(destination, msg, payload);
+        }
+    }
+    
+    @Override
+    public void messageReceived(ChannelMessage msg) {
+        boolean isFrag = XByteBuffer.toBoolean(msg.getMessage().getBytesDirect(),msg.getMessage().getLength()-1);
+        msg.getMessage().trim(1);
+        if ( isFrag ) {
+            defrag(msg);
+        } else {
+            super.messageReceived(msg);
+        }
+    }
+
+    
+    public FragCollection getFragCollection(FragKey key, ChannelMessage msg) {
+        FragCollection coll = fragpieces.get(key);
+        if ( coll == null ) {
+            synchronized (fragpieces) {
+                coll = fragpieces.get(key);
+                if ( coll == null ) {
+                    coll = new FragCollection(msg);
+                    fragpieces.put(key, coll);
+                }
+            }
+        } 
+        return coll;
+    }
+    
+    public void removeFragCollection(FragKey key) {
+        fragpieces.remove(key);
+    }
+    
+    public void defrag(ChannelMessage msg ) { 
+        FragKey key = new FragKey(msg.getUniqueId());
+        FragCollection coll = getFragCollection(key,msg);
+        coll.addMessage((ChannelMessage)msg.deepclone());
+
+        if ( coll.complete() ) {
+            removeFragCollection(key);
+            ChannelMessage complete = coll.assemble();
+            super.messageReceived(complete);
+            
+        }
+    }
+
+    public void frag(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+        int size = msg.getMessage().getLength();
+
+        int count = ((size / maxSize )+(size%maxSize==0?0:1));
+        ChannelMessage[] messages = new ChannelMessage[count];
+        int remaining = size;
+        for ( int i=0; i<count; i++ ) {
+            ChannelMessage tmp = (ChannelMessage)msg.clone();
+            int offset = (i*maxSize);
+            int length = Math.min(remaining,maxSize);
+            tmp.getMessage().clear();
+            tmp.getMessage().append(msg.getMessage().getBytesDirect(),offset,length);
+            //add the msg nr
+            //tmp.getMessage().append(XByteBuffer.toBytes(i),0,4);
+            tmp.getMessage().append(i);
+            //add the total nr of messages
+            //tmp.getMessage().append(XByteBuffer.toBytes(count),0,4);
+            tmp.getMessage().append(count);
+            //add true as the frag flag
+            //byte[] flag = XByteBuffer.toBytes(true);
+            //tmp.getMessage().append(flag,0,flag.length);
+            tmp.getMessage().append(true);
+            messages[i] = tmp;
+            remaining -= length;
+            
+        }
+        for ( int i=0; i<messages.length; i++ ) {
+            super.sendMessage(destination,messages[i],payload);
+        }
+    }
+    
+    @Override
+    public void heartbeat() {
+        try {
+            Set<FragKey> set = fragpieces.keySet(); 
+            Object[] keys = set.toArray();
+            for ( int i=0; i<keys.length; i++ ) {
+                FragKey key = (FragKey)keys[i];
+                if ( key != null && key.expired(getExpire()) ) 
+                    removeFragCollection(key);
+            }
+        }catch ( Exception x ) {
+            if ( log.isErrorEnabled() ) {
+                log.error("Unable to perform heartbeat clean up in the frag interceptor",x);
+            }
+        }
+        super.heartbeat();
+    }
+
+
+    public int getMaxSize() {
+        return maxSize;
+    }
+
+    public long getExpire() {
+        return expire;
+    }
+
+    public void setMaxSize(int maxSize) {
+        this.maxSize = maxSize;
+    }
+
+    public void setExpire(long expire) {
+        this.expire = expire;
+    }
+
+    public static class FragCollection {
+        private long received = System.currentTimeMillis();
+        private ChannelMessage msg;
+        private XByteBuffer[] frags;
+        public FragCollection(ChannelMessage msg) {
+            //get the total messages
+            int count = XByteBuffer.toInt(msg.getMessage().getBytesDirect(),msg.getMessage().getLength()-4);
+            frags = new XByteBuffer[count];
+            this.msg = msg;
+        }
+        
+        public void addMessage(ChannelMessage msg) {
+            //remove the total messages
+            msg.getMessage().trim(4);
+            //get the msg nr
+            int nr = XByteBuffer.toInt(msg.getMessage().getBytesDirect(),msg.getMessage().getLength()-4);
+            //remove the msg nr
+            msg.getMessage().trim(4);
+            frags[nr] = msg.getMessage();
+            
+        }
+        
+        public boolean complete() {
+            boolean result = true;
+            for ( int i=0; (i<frags.length) && (result); i++ ) result = (frags[i] != null);
+            return result;
+        }
+        
+        public ChannelMessage assemble() {
+            if ( !complete() ) throw new IllegalStateException("Fragments are missing.");
+            int buffersize = 0;
+            for (int i=0; i<frags.length; i++ ) buffersize += frags[i].getLength();
+            XByteBuffer buf = new XByteBuffer(buffersize,false);
+            msg.setMessage(buf);
+            for ( int i=0; i<frags.length; i++ ) {
+                msg.getMessage().append(frags[i].getBytesDirect(),0,frags[i].getLength());
+            }
+            return msg;
+        }
+        
+        public boolean expired(long expire) {
+            return (System.currentTimeMillis()-received)>expire;
+        }
+
+
+    }
+    
+    public static class FragKey {
+        private byte[] uniqueId;
+        private long received = System.currentTimeMillis();
+        public FragKey(byte[] id ) {
+            this.uniqueId = id;
+        }
+        @Override
+        public int hashCode() {
+            return XByteBuffer.toInt(uniqueId,0);
+        }
+        
+        @Override
+        public boolean equals(Object o ) {
+            if ( o instanceof FragKey ) {
+            return Arrays.equals(uniqueId,((FragKey)o).uniqueId);
+        } else return false;
+
+        }
+        
+        public boolean expired(long expire) {
+            return (System.currentTimeMillis()-received)>expire;
+        }
+
+    }
+    
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/GzipInterceptor.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/GzipInterceptor.java
new file mode 100644
index 0000000..2576c0c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/GzipInterceptor.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.group.interceptors;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+import org.apache.catalina.tribes.group.InterceptorPayload;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+
+
+/**
+ *
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class GzipInterceptor extends ChannelInterceptorBase {
+
+    private static final Log log = LogFactory.getLog(GzipInterceptor.class);
+
+    public static final int DEFAULT_BUFFER_SIZE = 2048;
+    
+    @Override
+    public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+        try {
+            byte[] data = compress(msg.getMessage().getBytes());
+            msg.getMessage().trim(msg.getMessage().getLength());
+            msg.getMessage().append(data,0,data.length);
+            getNext().sendMessage(destination, msg, payload);
+        } catch ( IOException x ) {
+            log.error("Unable to compress byte contents");
+            throw new ChannelException(x);
+        }
+    }
+
+    @Override
+    public void messageReceived(ChannelMessage msg) {
+        try {
+            byte[] data = decompress(msg.getMessage().getBytes());
+            msg.getMessage().trim(msg.getMessage().getLength());
+            msg.getMessage().append(data,0,data.length);
+            getPrevious().messageReceived(msg);
+        } catch ( IOException x ) {
+            log.error("Unable to decompress byte contents",x);
+        }
+    }
+    
+    public static byte[] compress(byte[] data) throws IOException {
+        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+        GZIPOutputStream gout = new GZIPOutputStream(bout);
+        gout.write(data);
+        gout.flush();
+        gout.close();
+        return bout.toByteArray();
+    }
+    
+    /**
+     * TODO Fix to create an automatically growing buffer.
+     * @param data byte[]
+     * @return byte[]
+     * @throws IOException
+     */
+    public static byte[] decompress(byte[] data) throws IOException {
+        ByteArrayInputStream bin = new ByteArrayInputStream(data);
+        GZIPInputStream gin = new GZIPInputStream(bin);
+        byte[] tmp = new byte[DEFAULT_BUFFER_SIZE];
+        int length = gin.read(tmp);
+        byte[] result = new byte[length];
+        System.arraycopy(tmp,0,result,0,length);
+        return result;
+    }
+    
+    public static void main(String[] arg) throws Exception {
+        byte[] data = new byte[1024];
+        Arrays.fill(data,(byte)1);
+        byte[] compress = compress(data);
+        decompress(compress);
+        System.out.println("Debug test");
+        
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/MessageDispatch15Interceptor.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/MessageDispatch15Interceptor.java
new file mode 100644
index 0000000..0a41a8d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/MessageDispatch15Interceptor.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.tribes.group.interceptors;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.group.InterceptorPayload;
+import org.apache.catalina.tribes.transport.bio.util.LinkObject;
+import org.apache.catalina.tribes.util.TcclThreadFactory;
+
+/**
+ * 
+ * Same implementation as the MessageDispatchInterceptor
+ * except it uses an atomic long for the currentSize calculation
+ * and uses a thread pool for message sending.
+ * 
+ * @author Filip Hanik
+ * @version 1.0
+ */
+
+public class MessageDispatch15Interceptor extends MessageDispatchInterceptor {
+
+    protected AtomicLong currentSize = new AtomicLong(0);
+    protected ThreadPoolExecutor executor = null;
+    protected int maxThreads = 10;
+    protected int maxSpareThreads = 2;
+    protected long keepAliveTime = 5000;
+    protected LinkedBlockingQueue<Runnable> runnablequeue = new LinkedBlockingQueue<Runnable>();
+
+    @Override
+    public long getCurrentSize() {
+        return currentSize.get();
+    }
+
+    @Override
+    public long addAndGetCurrentSize(long inc) {
+        return currentSize.addAndGet(inc);
+    }
+
+    @Override
+    public long setAndGetCurrentSize(long value) {
+        currentSize.set(value);
+        return value;
+    }
+    
+    @Override
+    public boolean addToQueue(ChannelMessage msg, Member[] destination, InterceptorPayload payload) {
+        final LinkObject obj = new LinkObject(msg,destination,payload);
+        Runnable r = new Runnable() {
+            public void run() {
+                sendAsyncData(obj);
+            }
+        };
+        executor.execute(r);
+        return true;
+    }
+
+    @Override
+    public LinkObject removeFromQueue() {
+        return null; //not used, thread pool contains its own queue.
+    }
+
+    @Override
+    public void startQueue() {
+        if ( run ) return;
+        executor = new ThreadPoolExecutor(maxSpareThreads, maxThreads,
+                keepAliveTime, TimeUnit.MILLISECONDS, runnablequeue,
+                new TcclThreadFactory());
+        run = true;
+    }
+
+    @Override
+    public void stopQueue() {
+        run = false;
+        executor.shutdownNow();
+        setAndGetCurrentSize(0);
+        runnablequeue.clear();
+    }
+
+    public long getKeepAliveTime() {
+        return keepAliveTime;
+    }
+
+    public int getMaxSpareThreads() {
+        return maxSpareThreads;
+    }
+
+    public int getMaxThreads() {
+        return maxThreads;
+    }
+
+    public void setKeepAliveTime(long keepAliveTime) {
+        this.keepAliveTime = keepAliveTime;
+    }
+
+    public void setMaxSpareThreads(int maxSpareThreads) {
+        this.maxSpareThreads = maxSpareThreads;
+    }
+
+    public void setMaxThreads(int maxThreads) {
+        this.maxThreads = maxThreads;
+    }
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/MessageDispatchInterceptor.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/MessageDispatchInterceptor.java
new file mode 100644
index 0000000..bada1dd
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/MessageDispatchInterceptor.java
@@ -0,0 +1,217 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.interceptors;
+
+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.group.ChannelInterceptorBase;
+import org.apache.catalina.tribes.group.InterceptorPayload;
+import org.apache.catalina.tribes.transport.bio.util.FastQueue;
+import org.apache.catalina.tribes.transport.bio.util.LinkObject;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ *
+ * The message dispatcher is a way to enable asynchronous communication
+ * through a channel. The dispatcher will look for the <code>Channel.SEND_OPTIONS_ASYNCHRONOUS</code>
+ * flag to be set, if it is, it will queue the message for delivery and immediately return to the sender.
+ * 
+ * 
+ * 
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class MessageDispatchInterceptor extends ChannelInterceptorBase implements Runnable {
+    private static final Log log = LogFactory.getLog(MessageDispatchInterceptor.class);
+
+    protected long maxQueueSize = 1024*1024*64; //64MB
+    protected FastQueue queue = new FastQueue();
+    protected volatile boolean run = false;
+    protected Thread msgDispatchThread = null;
+    protected long currentSize = 0;
+    protected boolean useDeepClone = true;
+    protected boolean alwaysSend = true;
+
+    public MessageDispatchInterceptor() {
+        setOptionFlag(Channel.SEND_OPTIONS_ASYNCHRONOUS);
+    }
+
+    @Override
+    public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+        boolean async = (msg.getOptions() & Channel.SEND_OPTIONS_ASYNCHRONOUS) == Channel.SEND_OPTIONS_ASYNCHRONOUS;
+        if ( async && run ) {
+            if ( (getCurrentSize()+msg.getMessage().getLength()) > maxQueueSize ) {
+                if ( alwaysSend ) {
+                    super.sendMessage(destination,msg,payload);
+                    return;
+                } else {
+                    throw new ChannelException("Asynchronous queue is full, reached its limit of " + maxQueueSize +" bytes, current:" + getCurrentSize() + " bytes.");
+                }//end if
+            }//end if
+            //add to queue
+            if ( useDeepClone ) msg = (ChannelMessage)msg.deepclone();
+            if (!addToQueue(msg, destination, payload) ) {
+                throw new ChannelException("Unable to add the message to the async queue, queue bug?");
+            }
+            addAndGetCurrentSize(msg.getMessage().getLength());
+        } else {
+            super.sendMessage(destination, msg, payload);
+        }
+    }
+    
+    public boolean addToQueue(ChannelMessage msg, Member[] destination, InterceptorPayload payload) {
+        return queue.add(msg,destination,payload);
+    }
+    
+    public LinkObject removeFromQueue() {
+        return queue.remove();
+    }
+    
+    public void startQueue() {
+        msgDispatchThread = new Thread(this);
+        msgDispatchThread.setName("MessageDispatchInterceptor.MessageDispatchThread");
+        msgDispatchThread.setDaemon(true);
+        msgDispatchThread.setPriority(Thread.MAX_PRIORITY);
+        queue.setEnabled(true);
+        run = true;
+        msgDispatchThread.start();
+    }
+    
+    public void stopQueue() {
+        run = false;
+        msgDispatchThread.interrupt();
+        queue.setEnabled(false);
+        setAndGetCurrentSize(0);
+    }
+    
+    
+    @Override
+    public void setOptionFlag(int flag) {
+        if ( flag != Channel.SEND_OPTIONS_ASYNCHRONOUS ) log.warn("Warning, you are overriding the asynchronous option flag, this will disable the Channel.SEND_OPTIONS_ASYNCHRONOUS that other apps might use.");
+        super.setOptionFlag(flag);
+    }
+
+    public void setMaxQueueSize(long maxQueueSize) {
+        this.maxQueueSize = maxQueueSize;
+    }
+
+    public void setUseDeepClone(boolean useDeepClone) {
+        this.useDeepClone = useDeepClone;
+    }
+
+    public long getMaxQueueSize() {
+        return maxQueueSize;
+    }
+
+    public boolean getUseDeepClone() {
+        return useDeepClone;
+    }
+    
+    public long getCurrentSize() {
+        return currentSize;
+    }
+    
+    public synchronized long addAndGetCurrentSize(long inc) {
+        currentSize += inc;
+        return currentSize;
+    }
+    
+    public synchronized long setAndGetCurrentSize(long value) {
+        currentSize = value;
+        return value;
+    }
+
+    @Override
+    public void start(int svc) throws ChannelException {
+        //start the thread
+        if (!run ) {
+            synchronized (this) {
+                if ( !run && ((svc & Channel.SND_TX_SEQ)==Channel.SND_TX_SEQ) ) {//only start with the sender
+                    startQueue();
+                }//end if
+            }//sync
+        }//end if
+        super.start(svc);
+    }
+
+    
+    @Override
+    public void stop(int svc) throws ChannelException {
+        //stop the thread
+        if ( run ) {
+            synchronized (this) {
+                if ( run && ((svc & Channel.SND_TX_SEQ)==Channel.SND_TX_SEQ)) {
+                    stopQueue();
+                }//end if
+            }//sync
+        }//end if
+
+        super.stop(svc);
+    }
+    
+    public void run() {
+        while ( run ) {
+            LinkObject link = removeFromQueue();
+            if ( link == null ) continue; //should not happen unless we exceed wait time
+            while ( link != null && run ) {
+                link = sendAsyncData(link);
+            }//while
+        }//while
+    }//run
+
+    protected LinkObject sendAsyncData(LinkObject link) {
+        ChannelMessage msg = link.data();
+        Member[] destination = link.getDestination();
+        try {
+            super.sendMessage(destination,msg,null);
+            try {
+                if ( link.getHandler() != null ) link.getHandler().handleCompletion(new UniqueId(msg.getUniqueId())); 
+            } catch ( Exception ex ) {
+                log.error("Unable to report back completed message.",ex);
+            }
+        } catch ( Exception x ) {
+            ChannelException cx = null;
+            if ( x instanceof ChannelException ) cx = (ChannelException)x;
+            else cx = new ChannelException(x);
+            if ( log.isDebugEnabled() ) log.debug("Error while processing async message.",x);
+            try {
+                if (link.getHandler() != null) link.getHandler().handleError(cx, new UniqueId(msg.getUniqueId()));
+            } catch ( Exception ex ) {
+                log.error("Unable to report back error message.",ex);
+            }
+        } finally {
+            addAndGetCurrentSize(-msg.getMessage().getLength());
+            link = link.next();
+        }//try
+        return link;
+    }
+
+    public boolean isAlwaysSend() {
+        return alwaysSend;
+    }
+
+    public void setAlwaysSend(boolean alwaysSend) {
+        this.alwaysSend = alwaysSend;
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/NonBlockingCoordinator.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/NonBlockingCoordinator.java
new file mode 100644
index 0000000..4111986
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/NonBlockingCoordinator.java
@@ -0,0 +1,851 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.interceptors;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.catalina.tribes.Channel;
+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.catalina.tribes.UniqueId;
+import org.apache.catalina.tribes.group.AbsoluteOrder;
+import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+import org.apache.catalina.tribes.group.InterceptorPayload;
+import org.apache.catalina.tribes.io.ChannelData;
+import org.apache.catalina.tribes.io.XByteBuffer;
+import org.apache.catalina.tribes.membership.MemberImpl;
+import org.apache.catalina.tribes.membership.Membership;
+import org.apache.catalina.tribes.util.Arrays;
+import org.apache.catalina.tribes.util.UUIDGenerator;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * <p>Title: Auto merging leader election algorithm</p>
+ *
+ * <p>Description: Implementation of a simple coordinator algorithm that not only selects a coordinator,
+ *    it also merges groups automatically when members are discovered that werent part of the 
+ *    </p>
+ * <p>This algorithm is non blocking meaning it allows for transactions while the coordination phase is going on
+ * </p>
+ * <p>This implementation is based on a home brewed algorithm that uses the AbsoluteOrder of a membership
+ * to pass a token ring of the current membership.<br>
+ * This is not the same as just using AbsoluteOrder! Consider the following scenario:<br>
+ * Nodes, A,B,C,D,E on a network, in that priority. AbsoluteOrder will only work if all
+ * nodes are receiving pings from all the other nodes. 
+ * meaning, that node{i} receives pings from node{all}-node{i}<br>
+ * but the following could happen if a multicast problem occurs.
+ * A has members {B,C,D}<br>
+ * B has members {A,C}<br>
+ * C has members {D,E}<br>
+ * D has members {A,B,C,E}<br>
+ * E has members {A,C,D}<br>
+ * Because the default Tribes membership implementation, relies on the multicast packets to 
+ * arrive at all nodes correctly, there is nothing guaranteeing that it will.<br>
+ * <br>
+ * To best explain how this algorithm works, lets take the above example:
+ * For simplicity we assume that a send operation is O(1) for all nodes, although this algorithm will work
+ * where messages overlap, as they all depend on absolute order<br>
+ * Scenario 1: A,B,C,D,E all come online at the same time
+ * Eval phase, A thinks of itself as leader, B thinks of A as leader,
+ * C thinks of itself as leader, D,E think of A as leader<br>
+ * Token phase:<br>
+ * (1) A sends out a message X{A-ldr, A-src, mbrs-A,B,C,D} to B where X is the id for the message(and the view)<br>
+ * (1) C sends out a message Y{C-ldr, C-src, mbrs-C,D,E} to D where Y is the id for the message(and the view)<br>
+ * (2) B receives X{A-ldr, A-src, mbrs-A,B,C,D}, sends X{A-ldr, A-src, mbrs-A,B,C,D} to C <br>
+ * (2) D receives Y{C-ldr, C-src, mbrs-C,D,E} D is aware of A,B, sends Y{A-ldr, C-src, mbrs-A,B,C,D,E} to E<br>
+ * (3) C receives X{A-ldr, A-src, mbrs-A,B,C,D}, sends X{A-ldr, A-src, mbrs-A,B,C,D,E} to D<br>
+ * (3) E receives Y{A-ldr, C-src, mbrs-A,B,C,D,E} sends Y{A-ldr, C-src, mbrs-A,B,C,D,E} to A<br>
+ * (4) D receives X{A-ldr, A-src, mbrs-A,B,C,D,E} sends sends X{A-ldr, A-src, mbrs-A,B,C,D,E} to A<br>
+ * (4) A receives Y{A-ldr, C-src, mbrs-A,B,C,D,E}, holds the message, add E to its list of members<br>
+ * (5) A receives X{A-ldr, A-src, mbrs-A,B,C,D,E} <br>
+ * At this point, the state looks like<br>
+ * A - {A-ldr, mbrs-A,B,C,D,E, id=X}<br>
+ * B - {A-ldr, mbrs-A,B,C,D, id=X}<br>
+ * C - {A-ldr, mbrs-A,B,C,D,E, id=X}<br>
+ * D - {A-ldr, mbrs-A,B,C,D,E, id=X}<br>
+ * E - {A-ldr, mbrs-A,B,C,D,E, id=Y}<br>
+ * <br>
+ * A message doesn't stop until it reaches its original sender, unless its dropped by a higher leader.
+ * As you can see, E still thinks the viewId=Y, which is not correct. But at this point we have 
+ * arrived at the same membership and all nodes are informed of each other.<br>
+ * To synchronize the rest we simply perform the following check at A when A receives X:<br>
+ * Original X{A-ldr, A-src, mbrs-A,B,C,D} == Arrived X{A-ldr, A-src, mbrs-A,B,C,D,E}<br>
+ * Since the condition is false, A, will resend the token, and A sends X{A-ldr, A-src, mbrs-A,B,C,D,E} to B
+ * When A receives X again, the token is complete. <br>
+ * Optionally, A can send a message X{A-ldr, A-src, mbrs-A,B,C,D,E confirmed} to A,B,C,D,E who then
+ * install and accept the view.
+ * </p>
+ * <p>
+ * Lets assume that C1 arrives, C1 has lower priority than C, but higher priority than D.<br>
+ * Lets also assume that C1 sees the following view {B,D,E}<br>
+ * C1 waits for a token to arrive. When the token arrives, the same scenario as above will happen.<br>
+ * In the scenario where C1 sees {D,E} and A,B,C can not see C1, no token will ever arrive.<br>
+ * In this case, C1 sends a Z{C1-ldr, C1-src, mbrs-C1,D,E} to D<br>
+ * D receives Z{C1-ldr, C1-src, mbrs-C1,D,E} and sends Z{A-ldr, C1-src, mbrs-A,B,C,C1,D,E} to E<br>
+ * E receives Z{A-ldr, C1-src, mbrs-A,B,C,C1,D,E} and sends it to A<br>
+ * A sends Z{A-ldr, A-src, mbrs-A,B,C,C1,D,E} to B and the chain continues until A receives the token again.
+ * At that time A optionally sends out Z{A-ldr, A-src, mbrs-A,B,C,C1,D,E, confirmed} to A,B,C,C1,D,E
+ * </p>
+ * <p>To ensure that the view gets implemented at all nodes at the same time, 
+ *    A will send out a VIEW_CONF message, this is the 'confirmed' message that is optional above.
+ * <p>Ideally, the interceptor below this one would be the TcpFailureDetector to ensure correct memberships</p>
+ *
+ * <p>The example above, of course can be simplified with a finite statemachine:<br>
+ * But I suck at writing state machines, my head gets all confused. One day I will document this algorithm though.<br>
+ * Maybe I'll do a state diagram :)
+ * </p>
+ * <h2>State Diagrams</h2>
+ * <a href="http://people.apache.org/~fhanik/tribes/docs/leader-election-initiate-election.jpg">Initiate an election</a><br><br>
+ * <a href="http://people.apache.org/~fhanik/tribes/docs/leader-election-message-arrives.jpg">Receive an election message</a><br><br>
+ * 
+ * @author Filip Hanik
+ * @version 1.0
+ * 
+ * 
+ * 
+ */
+public class NonBlockingCoordinator extends ChannelInterceptorBase {
+    
+    private static final Log log = LogFactory.getLog(NonBlockingCoordinator.class);
+
+    /**
+     * header for a coordination message
+     */
+    protected static final byte[] COORD_HEADER = new byte[] {-86, 38, -34, -29, -98, 90, 65, 63, -81, -122, -6, -110, 99, -54, 13, 63};
+    /**
+     * Coordination request
+     */
+    protected static final byte[] COORD_REQUEST = new byte[] {104, -95, -92, -42, 114, -36, 71, -19, -79, 20, 122, 101, -1, -48, -49, 30};
+    /**
+     * Coordination confirmation, for blocking installations
+     */
+    protected static final byte[] COORD_CONF = new byte[] {67, 88, 107, -86, 69, 23, 76, -70, -91, -23, -87, -25, -125, 86, 75, 20};
+    
+    /**
+     * Alive message
+     */
+    protected static final byte[] COORD_ALIVE = new byte[] {79, -121, -25, -15, -59, 5, 64, 94, -77, 113, -119, -88, 52, 114, -56, -46,
+                                                            -18, 102, 10, 34, -127, -9, 71, 115, -70, 72, -101, 88, 72, -124, 127, 111,
+                                                            74, 76, -116, 50, 111, 103, 65, 3, -77, 51, -35, 0, 119, 117, 9, -26,
+                                                            119, 50, -75, -105, -102, 36, 79, 37, -68, -84, -123, 15, -22, -109, 106, -55};
+    /**
+     * Time to wait for coordination timeout
+     */
+    protected long waitForCoordMsgTimeout = 15000;
+    /**
+     * Our current view
+     */
+    protected Membership view = null;
+    /**
+     * Out current viewId
+     */
+    protected UniqueId viewId;
+
+    /**
+     * Our nonblocking membership
+     */
+    protected Membership membership = null;
+    
+    /**
+     * indicates that we are running an election 
+     * and this is the one we are running
+     */
+    protected UniqueId suggestedviewId;
+    protected Membership suggestedView;
+    
+    protected boolean started = false;
+    protected final int startsvc = 0xFFFF;
+    
+    protected Object electionMutex = new Object();
+    
+    protected AtomicBoolean coordMsgReceived = new AtomicBoolean(false);
+    
+    public NonBlockingCoordinator() {
+        super();
+    }
+    
+//============================================================================================================    
+//              COORDINATION HANDLING
+//============================================================================================================
+    
+    public void startElection(boolean force) throws ChannelException {
+        synchronized (electionMutex) {
+            MemberImpl local = (MemberImpl)getLocalMember(false);
+            MemberImpl[] others = membership.getMembers();
+            fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_START_ELECT,this,"Election initated"));
+            if ( others.length == 0 ) {
+                this.viewId = new UniqueId(UUIDGenerator.randomUUID(false));
+                this.view = new Membership(local,AbsoluteOrder.comp, true);
+                this.handleViewConf(this.createElectionMsg(local,others,local),local,view);
+                return; //the only member, no need for an election
+            }
+            if ( suggestedviewId != null ) {
+                
+                if ( view != null && Arrays.diff(view,suggestedView,local).length == 0 &&  Arrays.diff(suggestedView,view,local).length == 0) {
+                    suggestedviewId = null;
+                    suggestedView = null;
+                    fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_ELECT_ABANDONED,this,"Election abandoned, running election matches view"));
+                } else {
+                    fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_ELECT_ABANDONED,this,"Election abandoned, election running"));
+                }
+                return; //election already running, I'm not allowed to have two of them
+            }
+            if ( view != null && Arrays.diff(view,membership,local).length == 0 &&  Arrays.diff(membership,view,local).length == 0) {
+                fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_ELECT_ABANDONED,this,"Election abandoned, view matches membership"));
+                return; //already have this view installed
+            }            
+            int prio = AbsoluteOrder.comp.compare(local,others[0]);
+            MemberImpl leader = ( prio < 0 )?local:others[0];//am I the leader in my view?
+            if ( local.equals(leader) || force ) {
+                CoordinationMessage msg = createElectionMsg(local, others, leader);
+                suggestedviewId = msg.getId();
+                suggestedView = new Membership(local,AbsoluteOrder.comp,true);
+                Arrays.fill(suggestedView,msg.getMembers());
+                fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_PROCESS_ELECT,this,"Election, sending request"));
+                sendElectionMsg(local,others[0],msg);
+            } else {
+                try {
+                    coordMsgReceived.set(false);
+                    fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_WAIT_FOR_MSG,this,"Election, waiting for request"));
+                    electionMutex.wait(waitForCoordMsgTimeout);
+                }catch ( InterruptedException x ) {
+                    Thread.interrupted();
+                }
+                if ( suggestedviewId == null && (!coordMsgReceived.get())) {
+                    //no message arrived, send the coord msg
+//                    fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_WAIT_FOR_MSG,this,"Election, waiting timed out."));
+//                    startElection(true);
+                    fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_ELECT_ABANDONED,this,"Election abandoned, waiting timed out."));
+                } else {
+                    fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_ELECT_ABANDONED,this,"Election abandoned, received a message"));
+                }
+            }//end if
+            
+        }
+    }
+
+    private CoordinationMessage createElectionMsg(MemberImpl local, MemberImpl[] others, MemberImpl leader) {
+        Membership m = new Membership(local,AbsoluteOrder.comp,true);
+        Arrays.fill(m,others);
+        MemberImpl[] mbrs = m.getMembers();
+        m.reset(); 
+        CoordinationMessage msg = new CoordinationMessage(leader, local, mbrs,new UniqueId(UUIDGenerator.randomUUID(true)), COORD_REQUEST);
+        return msg;
+    }
+
+    protected void sendElectionMsg(MemberImpl local, MemberImpl next, CoordinationMessage msg) throws ChannelException {
+        fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_SEND_MSG,this,"Sending election message to("+next.getName()+")"));
+        super.sendMessage(new Member[] {next}, createData(msg, local), null);
+    }
+    
+    protected void sendElectionMsgToNextInline(MemberImpl local, CoordinationMessage msg) throws ChannelException { 
+        int next = Arrays.nextIndex(local,msg.getMembers());
+        int current = next;
+        msg.leader = msg.getMembers()[0];
+        boolean sent =  false;
+        while ( !sent && current >= 0 ) {
+            try {
+                sendElectionMsg(local, msg.getMembers()[current], msg);
+                sent = true;
+            }catch ( ChannelException x  ) {
+                log.warn("Unable to send election message to:"+msg.getMembers()[current]);
+                current = Arrays.nextIndex(msg.getMembers()[current],msg.getMembers());
+                if ( current == next ) throw x;
+            }
+        }
+    }
+    
+    public Member getNextInLine(MemberImpl local, MemberImpl[] others) {
+        MemberImpl result = null;
+        for ( int i=0; i<others.length; i++ ) {
+            
+        }
+        return result;
+    }
+    
+    public ChannelData createData(CoordinationMessage msg, MemberImpl local) {
+        msg.write();
+        ChannelData data = new ChannelData(true);
+        data.setAddress(local);
+        data.setMessage(msg.getBuffer());
+        data.setOptions(Channel.SEND_OPTIONS_USE_ACK);
+        data.setTimestamp(System.currentTimeMillis());
+        return data;
+    }
+    
+    protected void viewChange(UniqueId viewId, Member[] view) {
+        //invoke any listeners
+    }
+    
+    protected boolean alive(Member mbr) {
+        return TcpFailureDetector.memberAlive(mbr,
+                                              COORD_ALIVE,
+                                              false,
+                                              false,
+                                              waitForCoordMsgTimeout,
+                                              waitForCoordMsgTimeout,
+                                              getOptionFlag());
+    }
+    
+    protected Membership mergeOnArrive(CoordinationMessage msg, Member sender) {
+        fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_PRE_MERGE,this,"Pre merge"));
+        MemberImpl local = (MemberImpl)getLocalMember(false);
+        Membership merged = new Membership(local,AbsoluteOrder.comp,true);
+        Arrays.fill(merged,msg.getMembers());
+        Arrays.fill(merged,getMembers());
+        Member[] diff = Arrays.diff(merged,membership,local);
+        for ( int i=0; i<diff.length; i++ ) {
+            if (!alive(diff[i])) merged.removeMember((MemberImpl)diff[i]);
+            else memberAdded(diff[i],false);
+        }
+        fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_POST_MERGE,this,"Post merge"));
+        return merged;
+    }
+    
+    protected void processCoordMessage(CoordinationMessage msg, Member sender) throws ChannelException {
+        if ( !coordMsgReceived.get() ) {
+            coordMsgReceived.set(true);
+            synchronized (electionMutex) { electionMutex.notifyAll();}
+        } 
+        msg.timestamp = System.currentTimeMillis();
+        Membership merged = mergeOnArrive(msg, sender);
+        if (isViewConf(msg)) handleViewConf(msg, sender, merged);
+        else handleToken(msg, sender, merged);
+    }
+    
+    protected void handleToken(CoordinationMessage msg, Member sender,Membership merged) throws ChannelException {
+        MemberImpl local = (MemberImpl)getLocalMember(false);
+        if ( local.equals(msg.getSource()) ) {
+            //my message msg.src=local
+            handleMyToken(local, msg, sender,merged);
+        } else {
+            handleOtherToken(local, msg, sender,merged);
+        }
+    }
+    
+    protected void handleMyToken(MemberImpl local, CoordinationMessage msg, Member sender,Membership merged) throws ChannelException {
+        if ( local.equals(msg.getLeader()) ) {
+            //no leadership change
+            if ( Arrays.sameMembers(msg.getMembers(),merged.getMembers()) ) {
+                msg.type = COORD_CONF;
+                super.sendMessage(Arrays.remove(msg.getMembers(),local),createData(msg,local),null);
+                handleViewConf(msg,local,merged);
+            } else {
+                //membership change
+                suggestedView = new Membership(local,AbsoluteOrder.comp,true);
+                suggestedviewId = msg.getId();
+                Arrays.fill(suggestedView,merged.getMembers());
+                msg.view = merged.getMembers();
+                sendElectionMsgToNextInline(local,msg);
+            }
+        } else {
+            //leadership change
+            suggestedView = null;
+            suggestedviewId = null;
+            msg.view = merged.getMembers();
+            sendElectionMsgToNextInline(local,msg);
+        }
+    }
+    
+    protected void handleOtherToken(MemberImpl local, CoordinationMessage msg, Member sender,Membership merged) throws ChannelException {
+        if ( local.equals(msg.getLeader()) ) {
+            //I am the new leader
+            //startElection(false);
+        } else {
+            msg.view = merged.getMembers();
+            sendElectionMsgToNextInline(local,msg);
+        }
+    }
+    
+    protected void handleViewConf(CoordinationMessage msg, Member sender,Membership merged) throws ChannelException {
+        if ( viewId != null && msg.getId().equals(viewId) ) return;//we already have this view
+        view = new Membership((MemberImpl)getLocalMember(false),AbsoluteOrder.comp,true);
+        Arrays.fill(view,msg.getMembers());
+        viewId = msg.getId();
+        
+        if ( viewId.equals(suggestedviewId) ) {
+            suggestedView = null;
+            suggestedviewId = null;
+        }
+        
+        if (suggestedView != null && AbsoluteOrder.comp.compare(suggestedView.getMembers()[0],merged.getMembers()[0])<0 ) {
+            suggestedView = null;
+            suggestedviewId = null;
+        }
+        
+        viewChange(viewId,view.getMembers());
+        fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_CONF_RX,this,"Accepted View"));
+        
+        if ( suggestedviewId == null && hasHigherPriority(merged.getMembers(),membership.getMembers()) ) {
+            startElection(false);
+        }
+    }
+    
+    protected boolean isViewConf(CoordinationMessage msg) {
+        return Arrays.contains(msg.getType(),0,COORD_CONF,0,COORD_CONF.length);
+    }
+    
+    protected boolean hasHigherPriority(Member[] complete, Member[] local) {
+        if ( local == null || local.length == 0 ) return false;
+        if ( complete == null || complete.length == 0 ) return true;
+        AbsoluteOrder.absoluteOrder(complete);
+        AbsoluteOrder.absoluteOrder(local);
+        return (AbsoluteOrder.comp.compare(complete[0],local[0]) > 0);
+        
+    }
+
+    
+    /**
+     * Returns coordinator if one is available
+     * @return Member
+     */
+    public Member getCoordinator() {
+        return (view != null && view.hasMembers()) ? view.getMembers()[0] : null;
+    }
+    
+    public Member[] getView() {
+        return (view != null && view.hasMembers()) ? view.getMembers() : new Member[0];
+    }
+    
+    public UniqueId getViewId() {
+        return viewId;
+    }
+    
+    /**
+    * Block in/out messages while a election is going on
+    */
+   protected void halt() {
+
+   }
+
+   /**
+    * Release lock for in/out messages election is completed
+    */
+   protected void release() {
+
+   }
+
+   /**
+    * Wait for an election to end
+    */
+   protected void waitForRelease() {
+
+   }
+
+    
+//============================================================================================================    
+//              OVERRIDDEN METHODS FROM CHANNEL INTERCEPTOR BASE    
+//============================================================================================================
+    @Override
+    public void start(int svc) throws ChannelException {
+            if (membership == null) setupMembership();
+            if (started)return;
+            fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_START, this, "Before start"));
+            super.start(startsvc);
+            started = true;
+            if (view == null) view = new Membership( (MemberImpl)super.getLocalMember(true), AbsoluteOrder.comp, true);
+            fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_START, this, "After start"));
+            startElection(false);
+    }
+
+    @Override
+    public void stop(int svc) throws ChannelException {
+        try {
+            halt();
+            synchronized (electionMutex) {
+                if (!started)return;
+                started = false;
+                fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_STOP, this, "Before stop"));
+                super.stop(startsvc);
+                this.view = null;
+                this.viewId = null;
+                this.suggestedView = null;
+                this.suggestedviewId = null;
+                this.membership.reset();
+                fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_STOP, this, "After stop"));
+            }
+        }finally {
+            release();
+        }
+    }
+    
+    
+    @Override
+    public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+        waitForRelease();
+        super.sendMessage(destination, msg, payload);
+    }
+
+    @Override
+    public void messageReceived(ChannelMessage msg) {
+        if ( Arrays.contains(msg.getMessage().getBytesDirect(),0,COORD_ALIVE,0,COORD_ALIVE.length) ) {
+            //ignore message, its an alive message
+            fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_MSG_ARRIVE,this,"Alive Message"));
+
+        } else if ( Arrays.contains(msg.getMessage().getBytesDirect(),0,COORD_HEADER,0,COORD_HEADER.length) ) {
+            try {
+                CoordinationMessage cmsg = new CoordinationMessage(msg.getMessage());
+                Member[] cmbr = cmsg.getMembers();
+                fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_MSG_ARRIVE,this,"Coord Msg Arrived("+Arrays.toNameString(cmbr)+")"));
+                processCoordMessage(cmsg, msg.getAddress());
+            }catch ( ChannelException x ) {
+                log.error("Error processing coordination message. Could be fatal.",x);
+            }
+        } else {
+            super.messageReceived(msg);
+        }
+    }
+
+    @Override
+    public boolean accept(ChannelMessage msg) {
+        return super.accept(msg);
+    }
+
+    @Override
+    public void memberAdded(Member member) {
+        memberAdded(member,true);
+    }
+
+    public void memberAdded(Member member,boolean elect) {
+        try {
+            if ( membership == null ) setupMembership();
+            if ( membership.memberAlive((MemberImpl)member) ) super.memberAdded(member);
+            try {
+                fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_MBR_ADD,this,"Member add("+member.getName()+")"));
+                if (started && elect) startElection(false);
+            }catch ( ChannelException x ) {
+                log.error("Unable to start election when member was added.",x);
+            }
+        }finally {
+        }
+        
+    }
+
+    @Override
+    public void memberDisappeared(Member member) {
+        try {
+            
+            membership.removeMember((MemberImpl)member);
+            super.memberDisappeared(member);
+            try {
+                fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_MBR_DEL,this,"Member remove("+member.getName()+")"));
+                if ( started && (isCoordinator() || isHighest()) ) 
+                    startElection(true); //to do, if a member disappears, only the coordinator can start
+            }catch ( ChannelException x ) {
+                log.error("Unable to start election when member was removed.",x);
+            }
+        }finally {
+        }
+    }
+    
+    public boolean isHighest() {
+        Member local = getLocalMember(false);
+        if ( membership.getMembers().length == 0 ) return true;
+        else return AbsoluteOrder.comp.compare(local,membership.getMembers()[0])<=0;
+    }
+    
+    public boolean isCoordinator() {
+        Member coord = getCoordinator();
+        return coord != null && getLocalMember(false).equals(coord);
+    }
+
+    @Override
+    public void heartbeat() {
+        try {
+            MemberImpl local = (MemberImpl)getLocalMember(false);
+            if ( view != null && (Arrays.diff(view,membership,local).length != 0 ||  Arrays.diff(membership,view,local).length != 0) ) {
+                if ( isHighest() ) {
+                    fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_START_ELECT, this,
+                                                               "Heartbeat found inconsistency, restart election"));
+                    startElection(true);
+                }            
+            }
+        } catch ( Exception x  ){
+            log.error("Unable to perform heartbeat.",x);
+        } finally {
+            super.heartbeat();
+        }
+    }
+
+    /**
+     * has members
+     */
+    @Override
+    public boolean hasMembers() {
+        
+        return membership.hasMembers();
+    }
+
+    /**
+     * Get all current cluster members
+     * @return all members or empty array
+     */
+    @Override
+    public Member[] getMembers() {
+        
+        return membership.getMembers();
+    }
+
+    /**
+     *
+     * @param mbr Member
+     * @return Member
+     */
+    @Override
+    public Member getMember(Member mbr) {
+        
+        return membership.getMember(mbr);
+    }
+
+    /**
+     * Return the member that represents this node.
+     *
+     * @return Member
+     */
+    @Override
+    public Member getLocalMember(boolean incAlive) {
+        Member local = super.getLocalMember(incAlive);
+        if ( view == null && (local != null)) setupMembership();
+        return local;
+    }
+    
+    protected synchronized void setupMembership() {
+        if ( membership == null ) {
+            membership  = new Membership((MemberImpl)super.getLocalMember(true),AbsoluteOrder.comp,false);
+        }
+    }
+    
+    
+//============================================================================================================    
+//              HELPER CLASSES FOR COORDINATION
+//============================================================================================================
+
+
+    public static class CoordinationMessage {
+        //X{A-ldr, A-src, mbrs-A,B,C,D}
+        protected XByteBuffer buf;
+        protected MemberImpl leader;
+        protected MemberImpl source;
+        protected MemberImpl[] view;
+        protected UniqueId id;
+        protected byte[] type;
+        protected long timestamp = System.currentTimeMillis();
+        
+        public CoordinationMessage(XByteBuffer buf) {
+            this.buf = buf;
+            parse();
+        }
+
+        public CoordinationMessage(MemberImpl leader,
+                                   MemberImpl source, 
+                                   MemberImpl[] view,
+                                   UniqueId id,
+                                   byte[] type) {
+            this.buf = new XByteBuffer(4096,false);
+            this.leader = leader;
+            this.source = source;
+            this.view = view;
+            this.id = id;
+            this.type = type;
+            this.write();
+        }
+        
+
+        public byte[] getHeader() {
+            return NonBlockingCoordinator.COORD_HEADER;
+        }
+        
+        public MemberImpl getLeader() {
+            if ( leader == null ) parse();
+            return leader;
+        }
+        
+        public MemberImpl getSource() {
+            if ( source == null ) parse();
+            return source;
+        }
+        
+        public UniqueId getId() {
+            if ( id == null ) parse();
+            return id;
+        }
+        
+        public MemberImpl[] getMembers() {
+            if ( view == null ) parse();
+            return view;
+        }
+        
+        public byte[] getType() {
+            if (type == null ) parse();
+            return type;
+        }
+        
+        public XByteBuffer getBuffer() {
+            return this.buf;
+        }
+        
+        public void parse() {
+            //header
+            int offset = 16;
+            //leader
+            int ldrLen = XByteBuffer.toInt(buf.getBytesDirect(),offset);
+            offset += 4;
+            byte[] ldr = new byte[ldrLen];
+            System.arraycopy(buf.getBytesDirect(),offset,ldr,0,ldrLen);
+            leader = MemberImpl.getMember(ldr);
+            offset += ldrLen;
+            //source
+            int srcLen = XByteBuffer.toInt(buf.getBytesDirect(),offset);
+            offset += 4;
+            byte[] src = new byte[srcLen];
+            System.arraycopy(buf.getBytesDirect(),offset,src,0,srcLen);
+            source = MemberImpl.getMember(src);
+            offset += srcLen;
+            //view
+            int mbrCount = XByteBuffer.toInt(buf.getBytesDirect(),offset);
+            offset += 4;
+            view = new MemberImpl[mbrCount];
+            for (int i=0; i<view.length; i++ ) {
+                int mbrLen = XByteBuffer.toInt(buf.getBytesDirect(),offset);
+                offset += 4;
+                byte[] mbr = new byte[mbrLen];
+                System.arraycopy(buf.getBytesDirect(), offset, mbr, 0, mbrLen);
+                view[i] = MemberImpl.getMember(mbr);
+                offset += mbrLen;
+            }
+            //id
+            this.id = new UniqueId(buf.getBytesDirect(),offset,16);
+            offset += 16;
+            type = new byte[16];
+            System.arraycopy(buf.getBytesDirect(), offset, type, 0, type.length);
+            offset += 16;
+            
+        }
+        
+        public void write() {
+            buf.reset();
+            //header
+            buf.append(COORD_HEADER,0,COORD_HEADER.length);
+            //leader
+            byte[] ldr = leader.getData(false,false);
+            buf.append(ldr.length);
+            buf.append(ldr,0,ldr.length);
+            ldr = null;
+            //source
+            byte[] src = source.getData(false,false);
+            buf.append(src.length);
+            buf.append(src,0,src.length);
+            src = null;
+            //view
+            buf.append(view.length);
+            for (int i=0; i<view.length; i++ ) {
+                byte[] mbr = view[i].getData(false,false);
+                buf.append(mbr.length);
+                buf.append(mbr,0,mbr.length);
+            }
+            //id
+            buf.append(id.getBytes(),0,id.getBytes().length);
+            buf.append(type,0,type.length);
+        }
+    }
+    
+    @Override
+    public void fireInterceptorEvent(InterceptorEvent event) {
+        if (event instanceof CoordinationEvent &&
+            ((CoordinationEvent)event).type == CoordinationEvent.EVT_CONF_RX) 
+            log.info(event);
+    }
+    
+    public static class CoordinationEvent implements InterceptorEvent {
+        public static final int EVT_START = 1;
+        public static final int EVT_MBR_ADD = 2;
+        public static final int EVT_MBR_DEL = 3;
+        public static final int EVT_START_ELECT = 4;
+        public static final int EVT_PROCESS_ELECT = 5;
+        public static final int EVT_MSG_ARRIVE = 6;
+        public static final int EVT_PRE_MERGE = 7;
+        public static final int EVT_POST_MERGE = 8;
+        public static final int EVT_WAIT_FOR_MSG = 9;
+        public static final int EVT_SEND_MSG = 10;
+        public static final int EVT_STOP = 11;
+        public static final int EVT_CONF_RX = 12;
+        public static final int EVT_ELECT_ABANDONED = 13;
+        
+        int type;
+        ChannelInterceptor interceptor;
+        Member coord; 
+        Member[] mbrs;
+        String info;
+        Membership view;
+        Membership suggestedView;
+        public CoordinationEvent(int type,ChannelInterceptor interceptor, String info) {
+            this.type = type;
+            this.interceptor = interceptor;
+            this.coord = ((NonBlockingCoordinator)interceptor).getCoordinator();
+            this.mbrs = ((NonBlockingCoordinator)interceptor).membership.getMembers();
+            this.info = info;
+            this.view = ((NonBlockingCoordinator)interceptor).view;
+            this.suggestedView = ((NonBlockingCoordinator)interceptor).suggestedView;
+        }
+        
+        public int getEventType() {
+            return type;
+        }
+        
+        public String getEventTypeDesc() {
+            switch (type) {
+                case  EVT_START: return "EVT_START:"+info;
+                case  EVT_MBR_ADD: return "EVT_MBR_ADD:"+info;
+                case  EVT_MBR_DEL: return "EVT_MBR_DEL:"+info;
+                case  EVT_START_ELECT: return "EVT_START_ELECT:"+info;
+                case  EVT_PROCESS_ELECT: return "EVT_PROCESS_ELECT:"+info;
+                case  EVT_MSG_ARRIVE: return "EVT_MSG_ARRIVE:"+info;
+                case  EVT_PRE_MERGE: return "EVT_PRE_MERGE:"+info;
+                case  EVT_POST_MERGE: return "EVT_POST_MERGE:"+info;
+                case  EVT_WAIT_FOR_MSG: return "EVT_WAIT_FOR_MSG:"+info;
+                case  EVT_SEND_MSG: return "EVT_SEND_MSG:"+info;
+                case  EVT_STOP: return "EVT_STOP:"+info;
+                case  EVT_CONF_RX: return "EVT_CONF_RX:"+info;
+                case EVT_ELECT_ABANDONED: return "EVT_ELECT_ABANDONED:"+info;
+                default: return "Unknown";
+            }
+        }
+        
+        public ChannelInterceptor getInterceptor() {
+            return interceptor;
+        }
+        
+        @Override
+        public String toString() {
+            StringBuilder buf = new StringBuilder("CoordinationEvent[type=");
+            buf.append(type).append("\n\tLocal:");
+            Member local = interceptor.getLocalMember(false);
+            buf.append(local!=null?local.getName():"").append("\n\tCoord:");
+            buf.append(coord!=null?coord.getName():"").append("\n\tView:");
+            buf.append(Arrays.toNameString(view!=null?view.getMembers():null)).append("\n\tSuggested View:");
+            buf.append(Arrays.toNameString(suggestedView!=null?suggestedView.getMembers():null)).append("\n\tMembers:");
+            buf.append(Arrays.toNameString(mbrs)).append("\n\tInfo:");
+            buf.append(info).append("]");
+            return buf.toString();
+        }
+    }
+
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/OrderInterceptor.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/OrderInterceptor.java
new file mode 100644
index 0000000..831d09b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/OrderInterceptor.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.interceptors;
+
+import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+import org.apache.catalina.tribes.group.InterceptorPayload;
+import org.apache.catalina.tribes.io.XByteBuffer;
+
+
+/**
+ *
+ * The order interceptor guarantees that messages are received in the same order they were 
+ * sent.
+ * This interceptor works best with the ack=true setting. <br>
+ * There is no point in 
+ * using this with the replicationMode="fastasynchqueue" as this mode guarantees ordering.<BR>
+ * If you are using the mode ack=false replicationMode=pooled, and have a lot of concurrent threads,
+ * this interceptor can really slow you down, as many messages will be completely out of order
+ * and the queue might become rather large. If this is the case, then you might want to set 
+ * the value OrderInterceptor.maxQueue = 25 (meaning that we will never keep more than 25 messages in our queue)
+ * <br><b>Configuration Options</b><br>
+ * OrderInteceptor.expire=<milliseconds> - if a message arrives out of order, how long before we act on it <b>default=3000ms</b><br>
+ * OrderInteceptor.maxQueue=<max queue size> - how much can the queue grow to ensure ordering. 
+ *   This setting is useful to avoid OutOfMemoryErrors<b>default=Integer.MAX_VALUE</b><br>
+ * OrderInterceptor.forwardExpired=<boolean> - this flag tells the interceptor what to 
+ * do when a message has expired or the queue has grown larger than the maxQueue value.
+ * true means that the message is sent up the stack to the receiver that will receive and out of order message
+ * false means, forget the message and reset the message counter. <b>default=true</b>
+ * 
+ * 
+ * @author Filip Hanik
+ * @version 1.1
+ */
+public class OrderInterceptor extends ChannelInterceptorBase {
+    private HashMap<Member, Counter> outcounter = new HashMap<Member, Counter>();
+    private HashMap<Member, Counter> incounter = new HashMap<Member, Counter>();
+    private HashMap<Member, MessageOrder> incoming = new HashMap<Member, MessageOrder>();
+    private long expire = 3000;
+    private boolean forwardExpired = true;
+    private int maxQueue = Integer.MAX_VALUE;
+    
+    final ReentrantReadWriteLock inLock = new ReentrantReadWriteLock(true);
+    final ReentrantReadWriteLock outLock= new ReentrantReadWriteLock(true);
+
+    @Override
+    public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+        if ( !okToProcess(msg.getOptions()) ) {
+            super.sendMessage(destination, msg, payload);
+            return;
+        }
+        ChannelException cx = null;
+        for (int i=0; i<destination.length; i++ ) {
+            try {
+                int nr = 0;
+                try {
+                    outLock.writeLock().lock();
+                    nr = incCounter(destination[i]);
+                } finally {
+                    outLock.writeLock().unlock();
+                }
+                //reduce byte copy
+                msg.getMessage().append(nr);
+                try {
+                    getNext().sendMessage(new Member[] {destination[i]}, msg, payload);
+                } finally {
+                    msg.getMessage().trim(4);
+                }
+            }catch ( ChannelException x ) {
+                if ( cx == null ) cx = x;
+                cx.addFaultyMember(x.getFaultyMembers());
+            }
+        }//for
+        if ( cx != null ) throw cx;
+    }
+
+    @Override
+    public void messageReceived(ChannelMessage msg) {
+        if ( !okToProcess(msg.getOptions()) ) {
+            super.messageReceived(msg);
+            return;
+        }
+        int msgnr = XByteBuffer.toInt(msg.getMessage().getBytesDirect(),msg.getMessage().getLength()-4);
+        msg.getMessage().trim(4);
+        MessageOrder order = new MessageOrder(msgnr,(ChannelMessage)msg.deepclone());
+        try {
+            inLock.writeLock().lock();
+            if ( processIncoming(order) ) processLeftOvers(msg.getAddress(),false);
+        }finally {
+            inLock.writeLock().unlock();
+        }
+    }
+    protected void processLeftOvers(Member member, boolean force) {
+        MessageOrder tmp = incoming.get(member);
+        if ( force ) {
+            Counter cnt = getInCounter(member);
+            cnt.setCounter(Integer.MAX_VALUE);
+        }
+        if ( tmp!= null ) processIncoming(tmp);
+    }
+    /**
+     * 
+     * @param order MessageOrder
+     * @return boolean - true if a message expired and was processed
+     */
+    protected boolean processIncoming(MessageOrder order) {
+        boolean result = false;
+        Member member = order.getMessage().getAddress();
+        Counter cnt = getInCounter(member);
+        
+        MessageOrder tmp = incoming.get(member);
+        if ( tmp != null ) {
+            order = MessageOrder.add(tmp,order);
+        }
+        
+        
+        while ( (order!=null) && (order.getMsgNr() <= cnt.getCounter())  ) {
+            //we are right on target. process orders
+            if ( order.getMsgNr() == cnt.getCounter() ) cnt.inc();
+            else if ( order.getMsgNr() > cnt.getCounter() ) cnt.setCounter(order.getMsgNr());
+            super.messageReceived(order.getMessage());
+            order.setMessage(null);
+            order = order.next;
+        }
+        MessageOrder head = order;
+        MessageOrder prev = null;
+        tmp = order;
+        //flag to empty out the queue when it larger than maxQueue
+        boolean empty = order!=null?order.getCount()>=maxQueue:false;
+        while ( tmp != null ) {
+            //process expired messages or empty out the queue
+            if ( tmp.isExpired(expire) || empty ) {
+                //reset the head
+                if ( tmp == head ) head = tmp.next;
+                cnt.setCounter(tmp.getMsgNr()+1);
+                if ( getForwardExpired() ) 
+                    super.messageReceived(tmp.getMessage());
+                tmp.setMessage(null);
+                tmp = tmp.next;
+                if ( prev != null ) prev.next = tmp;  
+                result = true;
+            } else {
+                prev = tmp;
+                tmp = tmp.next;
+            }
+        }
+        if ( head == null ) incoming.remove(member);
+        else incoming.put(member, head);
+        return result;
+    }
+    
+    @Override
+    public void memberAdded(Member member) {
+        //notify upwards
+        super.memberAdded(member);
+    }
+
+    @Override
+    public void memberDisappeared(Member member) {
+        //reset counters - lock free
+        incounter.remove(member);
+        outcounter.remove(member);
+        //clear the remaining queue
+        processLeftOvers(member,true);
+        //notify upwards
+        super.memberDisappeared(member);
+    }
+    
+    protected int incCounter(Member mbr) { 
+        Counter cnt = getOutCounter(mbr);
+        return cnt.inc();
+    }
+    
+    protected Counter getInCounter(Member mbr) {
+        Counter cnt = incounter.get(mbr);
+        if ( cnt == null ) {
+            cnt = new Counter();
+            cnt.inc(); //always start at 1 for incoming
+            incounter.put(mbr,cnt);
+        }
+        return cnt;
+    }
+
+    protected Counter getOutCounter(Member mbr) {
+        Counter cnt = outcounter.get(mbr);
+        if ( cnt == null ) {
+            cnt = new Counter();
+            outcounter.put(mbr,cnt);
+        }
+        return cnt;
+    }
+
+    protected static class Counter {
+        private AtomicInteger value = new AtomicInteger(0);
+        
+        public int getCounter() {
+            return value.get();
+        }
+        
+        public void setCounter(int counter) {
+            this.value.set(counter);
+        }
+        
+        public int inc() {
+            return value.addAndGet(1);
+        }
+    }
+    
+    protected static class MessageOrder {
+        private long received = System.currentTimeMillis();
+        private MessageOrder next;
+        private int msgNr;
+        private ChannelMessage msg = null;
+        public MessageOrder(int msgNr,ChannelMessage msg) {
+            this.msgNr = msgNr;
+            this.msg = msg;
+        }
+        
+        public boolean isExpired(long expireTime) {
+            return (System.currentTimeMillis()-received) > expireTime;
+        }
+        
+        public ChannelMessage getMessage() {
+            return msg;
+        }
+        
+        public void setMessage(ChannelMessage msg) {
+            this.msg = msg;
+        }
+        
+        public void setNext(MessageOrder order) {
+            this.next = order;
+        }
+        public MessageOrder getNext() {
+            return next;
+        }
+        
+        public int getCount() {
+            int counter = 1;
+            MessageOrder tmp = next;
+            while ( tmp != null ) {
+                counter++;
+                tmp = tmp.next;
+            }
+            return counter;
+        }
+        
+        public static MessageOrder add(MessageOrder head, MessageOrder add) {
+            if ( head == null ) return add;
+            if ( add == null ) return head;
+            if ( head == add ) return add;
+
+            if ( head.getMsgNr() > add.getMsgNr() ) {
+                add.next = head;
+                return add;
+            }
+            
+            MessageOrder iter = head;
+            MessageOrder prev = null;
+            while ( iter.getMsgNr() < add.getMsgNr() && (iter.next !=null ) ) {
+                prev = iter;
+                iter = iter.next;
+            }
+            if ( iter.getMsgNr() < add.getMsgNr() ) {
+                //add after
+                add.next = iter.next;
+                iter.next = add;
+            } else if (iter.getMsgNr() > add.getMsgNr()) {
+                //add before
+                prev.next = add;
+                add.next = iter;
+                
+            } else {
+                throw new ArithmeticException("Message added has the same counter, synchronization bug. Disable the order interceptor");
+            }
+            
+            return head;
+        }
+        
+        public int getMsgNr() {
+            return msgNr;
+        }
+
+
+    }
+
+    public void setExpire(long expire) {
+        this.expire = expire;
+    }
+
+    public void setForwardExpired(boolean forwardExpired) {
+        this.forwardExpired = forwardExpired;
+    }
+
+    public void setMaxQueue(int maxQueue) {
+        this.maxQueue = maxQueue;
+    }
+
+    public long getExpire() {
+        return expire;
+    }
+
+    public boolean getForwardExpired() {
+        return forwardExpired;
+    }
+
+    public int getMaxQueue() {
+        return maxQueue;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/SimpleCoordinator.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/SimpleCoordinator.java
new file mode 100644
index 0000000..8809026
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/SimpleCoordinator.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.catalina.tribes.group.interceptors;
+
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.group.AbsoluteOrder;
+import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+
+/**
+ * A dinky coordinator, just uses a sorted version of the member array.
+ * 
+ * @author rnewson
+ * 
+ */
+public class SimpleCoordinator extends ChannelInterceptorBase {
+
+    private Member[] view;
+
+    private AtomicBoolean membershipChanged = new AtomicBoolean();
+
+    private void membershipChanged() {
+        membershipChanged.set(true);
+    }
+
+    @Override
+    public void memberAdded(final Member member) {
+        super.memberAdded(member);
+        membershipChanged();
+        installViewWhenStable();
+    }
+
+    @Override
+    public void memberDisappeared(final Member member) {
+        super.memberDisappeared(member);
+        membershipChanged();
+        installViewWhenStable();
+    }
+
+    /**
+     * Override to receive view changes.
+     * 
+     * @param view
+     */
+    protected void viewChange(final Member[] view) {
+    }
+
+    @Override
+    public void start(int svc) throws ChannelException {
+        super.start(svc);
+        installViewWhenStable();
+    }
+
+    private void installViewWhenStable() {
+        int stableCount = 0;
+
+        while (stableCount < 10) {
+            if (membershipChanged.compareAndSet(true, false)) {
+                stableCount = 0;
+            } else {
+                stableCount++;
+            }
+            try {
+                TimeUnit.MILLISECONDS.sleep(250);
+            } catch (final InterruptedException e) {
+                Thread.currentThread().interrupt();
+            }
+        }
+
+        final Member[] members = getMembers();
+        final Member[] view = new Member[members.length+1];
+        System.arraycopy(members, 0, view, 0, members.length);
+        view[members.length] = getLocalMember(false);
+        Arrays.sort(view, AbsoluteOrder.comp);
+        if (Arrays.equals(view, this.view)) {
+            return;
+        }
+        this.view = view;
+        viewChange(view);
+    }
+
+    @Override
+    public void stop(int svc) throws ChannelException {
+        super.stop(svc);
+    }
+
+    public Member[] getView() {
+        return view;
+    }
+
+    public Member getCoordinator() {
+        return view == null ? null : view[0];
+    }
+
+    public boolean isCoordinator() {
+        return view == null ? false : getLocalMember(false).equals(
+                getCoordinator());
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/StaticMembershipInterceptor.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/StaticMembershipInterceptor.java
new file mode 100644
index 0000000..0103964
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/StaticMembershipInterceptor.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.catalina.tribes.group.interceptors;
+
+import java.util.ArrayList;
+
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.group.AbsoluteOrder;
+import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+
+public class StaticMembershipInterceptor
+    extends ChannelInterceptorBase {
+    protected ArrayList<Member> members = new ArrayList<Member>();
+    protected Member localMember = null;
+
+    public StaticMembershipInterceptor() {
+        super();
+    }
+
+    public void addStaticMember(Member member) {
+        synchronized (members) {
+            if (!members.contains(member)) members.add(member);
+        }
+    }
+
+    public void removeStaticMember(Member member) {
+        synchronized (members) {
+            if (members.contains(member)) members.remove(member);
+        }
+    }
+
+    public void setLocalMember(Member member) {
+        this.localMember = member;
+    }
+
+    /**
+     * has members
+     */
+    @Override
+    public boolean hasMembers() {
+        return super.hasMembers() || (members.size()>0);
+    }
+
+    /**
+     * Get all current cluster members
+     * @return all members or empty array
+     */
+    @Override
+    public Member[] getMembers() {
+        if ( members.size() == 0 ) return super.getMembers();
+        else {
+            synchronized (members) {
+                Member[] others = super.getMembers();
+                Member[] result = new Member[members.size() + others.length];
+                for (int i = 0; i < others.length; i++) result[i] = others[i];
+                for (int i = 0; i < members.size(); i++) result[i + others.length] = members.get(i);
+                AbsoluteOrder.absoluteOrder(result);
+                return result;
+            }//sync
+        }//end if
+    }
+
+    /**
+     *
+     * @param mbr Member
+     * @return Member
+     */
+    @Override
+    public Member getMember(Member mbr) {
+        if ( members.contains(mbr) ) return members.get(members.indexOf(mbr));
+        else return super.getMember(mbr);
+    }
+
+    /**
+     * Return the member that represents this node.
+     *
+     * @return Member
+     */
+    @Override
+    public Member getLocalMember(boolean incAlive) {
+        if (this.localMember != null ) return localMember;
+        else return super.getLocalMember(incAlive);
+    }
+    
+    /**
+     * Send notifications upwards
+     * @param svc int
+     * @throws ChannelException
+     */
+    @Override
+    public void start(int svc) throws ChannelException {
+        if ( (Channel.SND_RX_SEQ&svc)==Channel.SND_RX_SEQ ) super.start(Channel.SND_RX_SEQ); 
+        if ( (Channel.SND_TX_SEQ&svc)==Channel.SND_TX_SEQ ) super.start(Channel.SND_TX_SEQ); 
+        final Member[] mbrs = members.toArray(new Member[members.size()]);
+        final ChannelInterceptorBase base = this;
+        Thread t = new Thread() {
+            @Override
+            public void run() {
+                for (int i=0; i<mbrs.length; i++ ) {
+                    base.memberAdded(mbrs[i]);
+                }
+            }
+        };
+        t.start();
+        super.start(svc & (~Channel.SND_RX_SEQ) & (~Channel.SND_TX_SEQ));
+    }
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/TcpFailureDetector.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/TcpFailureDetector.java
new file mode 100644
index 0000000..b4951d7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/TcpFailureDetector.java
@@ -0,0 +1,375 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.group.interceptors;
+
+import java.net.ConnectException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+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.ChannelException.FaultyMember;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.RemoteProcessException;
+import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+import org.apache.catalina.tribes.group.InterceptorPayload;
+import org.apache.catalina.tribes.io.ChannelData;
+import org.apache.catalina.tribes.io.XByteBuffer;
+import org.apache.catalina.tribes.membership.MemberImpl;
+import org.apache.catalina.tribes.membership.Membership;
+
+/**
+ * <p>Title: A perfect failure detector </p>
+ *
+ * <p>Description: The TcpFailureDetector is a useful interceptor
+ * that adds reliability to the membership layer.</p>
+ * <p>
+ * If the network is busy, or the system is busy so that the membership receiver thread
+ * is not getting enough time to update its table, members can be &quot;timed out&quot;
+ * This failure detector will intercept the memberDisappeared message(unless its a true shutdown message)
+ * and connect to the member using TCP.
+ * </p>
+ * <p>
+ * The TcpFailureDetector works in two ways. <br>
+ * 1. It intercepts memberDisappeared events
+ * 2. It catches send errors 
+ * </p>
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class TcpFailureDetector extends ChannelInterceptorBase {
+    
+    private static final org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog( TcpFailureDetector.class );
+    
+    protected static byte[] TCP_FAIL_DETECT = new byte[] {
+        79, -89, 115, 72, 121, -126, 67, -55, -97, 111, -119, -128, -95, 91, 7, 20,
+        125, -39, 82, 91, -21, -15, 67, -102, -73, 126, -66, -113, -127, 103, 30, -74,
+        55, 21, -66, -121, 69, 126, 76, -88, -65, 10, 77, 19, 83, 56, 21, 50,
+        85, -10, -108, -73, 58, -6, 64, 120, -111, 4, 125, -41, 114, -124, -64, -43};      
+    
+    protected boolean performConnectTest = true;
+
+    protected long connectTimeout = 1000;//1 second default
+    
+    protected boolean performSendTest = true;
+
+    protected boolean performReadTest = false;
+    
+    protected long readTestTimeout = 5000;//5 seconds
+    
+    protected Membership membership = null;
+    
+    protected HashMap<Member, Long> removeSuspects = new HashMap<Member, Long>();
+    
+    protected HashMap<Member, Long> addSuspects = new HashMap<Member, Long>();
+    
+    @Override
+    public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+        try {
+            super.sendMessage(destination, msg, payload);
+        }catch ( ChannelException cx ) {
+            FaultyMember[] mbrs = cx.getFaultyMembers();
+            for ( int i=0; i<mbrs.length; i++ ) {
+                if ( mbrs[i].getCause()!=null &&  
+                     (!(mbrs[i].getCause() instanceof RemoteProcessException)) ) {//RemoteProcessException's are ok
+                    this.memberDisappeared(mbrs[i].getMember());
+                }//end if
+            }//for
+            throw cx;
+        }
+    }
+
+    @Override
+    public void messageReceived(ChannelMessage msg) {
+        //catch incoming 
+        boolean process = true;
+        if ( okToProcess(msg.getOptions()) ) {
+            //check to see if it is a testMessage, if so, process = false
+            process = ( (msg.getMessage().getLength() != TCP_FAIL_DETECT.length) ||
+                        (!Arrays.equals(TCP_FAIL_DETECT,msg.getMessage().getBytes()) ) );
+        }//end if
+            
+        //ignore the message, it doesnt have the flag set
+        if ( process ) super.messageReceived(msg);
+        else if ( log.isDebugEnabled() ) log.debug("Received a failure detector packet:"+msg);
+    }//messageReceived
+    
+    
+    @Override
+    public void memberAdded(Member member) {
+        if ( membership == null ) setupMembership();
+        boolean notify = false;
+        synchronized (membership) {
+            if (removeSuspects.containsKey(member)) {
+                //previously marked suspect, system below picked up the member again
+                removeSuspects.remove(member);
+            } else if (membership.getMember(member) == null){
+                //if we add it here, then add it upwards too
+                //check to see if it is alive
+                if (memberAlive(member)) {
+                    membership.memberAlive( (MemberImpl) member);
+                    notify = true;
+                } else {
+                    addSuspects.put(member, Long.valueOf(System.currentTimeMillis()));
+                }
+            }
+        }
+        if ( notify ) super.memberAdded(member);
+    }
+
+    @Override
+    public void memberDisappeared(Member member) {
+        if ( membership == null ) setupMembership();
+        boolean notify = false;
+        boolean shutdown = Arrays.equals(member.getCommand(),Member.SHUTDOWN_PAYLOAD);
+        if ( !shutdown ) 
+            if(log.isInfoEnabled())
+                log.info("Received memberDisappeared["+member+"] message. Will verify.");
+        synchronized (membership) {
+            if (!membership.contains(member)) {
+                if(log.isInfoEnabled())
+                    log.info("Verification complete. Member already disappeared["+member+"]");
+                return;
+            }
+            //check to see if the member really is gone
+            //if the payload is not a shutdown message
+            if (shutdown || !memberAlive(member)) {
+                //not correct, we need to maintain the map
+                membership.removeMember( (MemberImpl) member);
+                removeSuspects.remove(member);
+                notify = true;
+            } else {
+                //add the member as suspect
+                removeSuspects.put(member, Long.valueOf(System.currentTimeMillis()));
+            }
+        }
+        if ( notify ) {
+            if(log.isInfoEnabled())
+                log.info("Verification complete. Member disappeared["+member+"]");
+            super.memberDisappeared(member);
+        } else {
+            if(log.isInfoEnabled())
+                log.info("Verification complete. Member still alive["+member+"]");
+
+        }
+    }
+    
+    @Override
+    public boolean hasMembers() {
+        if ( membership == null ) setupMembership();
+        return membership.hasMembers();
+    }
+
+    @Override
+    public Member[] getMembers() {
+        if ( membership == null ) setupMembership();
+        return membership.getMembers();
+    }
+
+    @Override
+    public Member getMember(Member mbr) {
+        if ( membership == null ) setupMembership();
+        return membership.getMember(mbr);
+    }
+
+    @Override
+    public Member getLocalMember(boolean incAlive) {
+        return super.getLocalMember(incAlive);
+    }
+    
+    @Override
+    public void heartbeat() {
+        super.heartbeat();
+        checkMembers(false);
+    }
+    public void checkMembers(boolean checkAll) {
+        
+        try {
+            if (membership == null) setupMembership();
+            synchronized (membership) {
+                if ( !checkAll ) performBasicCheck();
+                else performForcedCheck();
+            }
+        }catch ( Exception x ) {
+            log.warn("Unable to perform heartbeat on the TcpFailureDetector.",x);
+        } finally {
+            
+        }
+    }
+    
+    protected void performForcedCheck() {
+        //update all alive times
+        Member[] members = super.getMembers();
+        for (int i = 0; members != null && i < members.length; i++) {
+            if (memberAlive(members[i])) {
+                if (membership.memberAlive((MemberImpl)members[i])) super.memberAdded(members[i]);
+                addSuspects.remove(members[i]);
+            } else {
+                if (membership.getMember(members[i])!=null) {
+                    membership.removeMember((MemberImpl)members[i]);
+                    removeSuspects.remove(members[i]);
+                    super.memberDisappeared(members[i]);
+                }
+            } //end if
+        } //for
+
+    }
+
+    protected void performBasicCheck() {
+        //update all alive times
+        Member[] members = super.getMembers();
+        for (int i = 0; members != null && i < members.length; i++) {
+            if (membership.memberAlive( (MemberImpl) members[i])) {
+                //we don't have this one in our membership, check to see if he/she is alive
+                if (memberAlive(members[i])) {
+                    log.warn("Member added, even though we werent notified:" + members[i]);
+                    super.memberAdded(members[i]);
+                } else {
+                    membership.removeMember( (MemberImpl) members[i]);
+                } //end if
+            } //end if
+        } //for
+
+        //check suspect members if they are still alive,
+        //if not, simply issue the memberDisappeared message
+        MemberImpl[] keys = removeSuspects.keySet().toArray(new MemberImpl[removeSuspects.size()]);
+        for (int i = 0; i < keys.length; i++) {
+            MemberImpl m = keys[i];
+            if (membership.getMember(m) != null && (!memberAlive(m))) {
+                membership.removeMember(m);
+                super.memberDisappeared(m);
+                removeSuspects.remove(m);
+                if(log.isInfoEnabled())
+                    log.info("Suspect member, confirmed dead.["+m+"]");
+            } //end if
+        }
+
+        //check add suspects members if they are alive now,
+        //if they are, simply issue the memberAdded message
+        keys = addSuspects.keySet().toArray(new MemberImpl[addSuspects.size()]);
+        for (int i = 0; i < keys.length; i++) {
+            MemberImpl m = keys[i];
+            if ( membership.getMember(m) == null && (memberAlive(m))) {
+                membership.memberAlive(m);
+                super.memberAdded(m);
+                addSuspects.remove(m);
+                if(log.isInfoEnabled())
+                    log.info("Suspect member, confirmed alive.["+m+"]");
+            } //end if
+        }
+    }
+    
+    protected synchronized void setupMembership() {
+        if ( membership == null ) {
+            membership = new Membership((MemberImpl)super.getLocalMember(true));
+        }
+        
+    }
+    
+    protected boolean memberAlive(Member mbr) {
+        return memberAlive(mbr,TCP_FAIL_DETECT,performSendTest,performReadTest,readTestTimeout,connectTimeout,getOptionFlag());
+    }
+    
+    protected static boolean memberAlive(Member mbr, byte[] msgData, 
+                                         boolean sendTest, boolean readTest,
+                                         long readTimeout, long conTimeout,
+                                         int optionFlag) {
+        //could be a shutdown notification
+        if ( Arrays.equals(mbr.getCommand(),Member.SHUTDOWN_PAYLOAD) ) return false;
+        
+        Socket socket = new Socket();        
+        try {
+            InetAddress ia = InetAddress.getByAddress(mbr.getHost());
+            InetSocketAddress addr = new InetSocketAddress(ia, mbr.getPort());
+            socket.setSoTimeout((int)readTimeout);
+            socket.connect(addr, (int) conTimeout);
+            if ( sendTest ) {
+                ChannelData data = new ChannelData(true);
+                data.setAddress(mbr);
+                data.setMessage(new XByteBuffer(msgData,false));
+                data.setTimestamp(System.currentTimeMillis());
+                int options = optionFlag | Channel.SEND_OPTIONS_BYTE_MESSAGE;
+                if ( readTest ) options = (options | Channel.SEND_OPTIONS_USE_ACK);
+                else options = (options & (~Channel.SEND_OPTIONS_USE_ACK));
+                data.setOptions(options);
+                byte[] message = XByteBuffer.createDataPackage(data);
+                socket.getOutputStream().write(message);
+                if ( readTest ) {
+                    int length = socket.getInputStream().read(message);
+                    return length > 0;
+                }
+            }//end if
+            return true;
+        } catch ( SocketTimeoutException sx) {
+            //do nothing, we couldn't connect
+        } catch ( ConnectException cx) {
+            //do nothing, we couldn't connect
+        }catch (Exception x ) {
+            log.error("Unable to perform failure detection check, assuming member down.",x);
+        } finally {
+            try {socket.close(); } catch ( Exception ignore ){}
+        }
+        return false;
+    }
+
+    public boolean getPerformConnectTest() {
+        return performConnectTest;
+    }
+
+    public long getReadTestTimeout() {
+        return readTestTimeout;
+    }
+
+    public boolean getPerformSendTest() {
+        return performSendTest;
+    }
+
+    public boolean getPerformReadTest() {
+        return performReadTest;
+    }
+
+    public long getConnectTimeout() {
+        return connectTimeout;
+    }
+
+    public void setPerformConnectTest(boolean performConnectTest) {
+        this.performConnectTest = performConnectTest;
+    }
+
+    public void setPerformReadTest(boolean performReadTest) {
+        this.performReadTest = performReadTest;
+    }
+
+    public void setPerformSendTest(boolean performSendTest) {
+        this.performSendTest = performSendTest;
+    }
+
+    public void setReadTestTimeout(long readTestTimeout) {
+        this.readTestTimeout = readTestTimeout;
+    }
+
+    public void setConnectTimeout(long connectTimeout) {
+        this.connectTimeout = connectTimeout;
+    }
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/TcpPingInterceptor.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/TcpPingInterceptor.java
new file mode 100644
index 0000000..26d6762
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/TcpPingInterceptor.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.group.interceptors;
+
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicInteger;
+
+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.catalina.tribes.group.ChannelInterceptorBase;
+import org.apache.catalina.tribes.io.ChannelData;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * 
+ * Sends a ping to all members.
+ * Configure this interceptor with the TcpFailureDetector below it,
+ * and the TcpFailureDetector will act as the membership guide.
+ * @author Filip Hanik
+ * @version 1.0
+ */
+
+public class TcpPingInterceptor extends ChannelInterceptorBase {
+    
+    private static final Log log = LogFactory.getLog(TcpPingInterceptor.class);
+    
+    protected static byte[] TCP_PING_DATA = new byte[] {
+        79, -89, 115, 72, 121, -33, 67, -55, -97, 111, -119, -128, -95, 91, 7, 20,
+        125, -39, 82, 91, -21, -33, 67, -102, -73, 126, -66, -113, -127, 103, 30, -74,
+        55, 21, -66, -121, 69, 33, 76, -88, -65, 10, 77, 19, 83, 56, 21, 50,
+        85, -10, -108, -73, 58, -33, 33, 120, -111, 4, 125, -41, 114, -124, -64, -43};  
+
+    protected long interval = 1000; //1 second
+
+    protected boolean useThread = false;
+    protected boolean staticOnly = false;
+    protected volatile boolean running = true;
+    protected PingThread thread = null;
+    protected static AtomicInteger cnt = new AtomicInteger(0);
+    
+    WeakReference<TcpFailureDetector> failureDetector = null;
+    WeakReference<StaticMembershipInterceptor> staticMembers = null;
+    
+    @Override
+    public synchronized void start(int svc) throws ChannelException {
+        super.start(svc);
+        running = true;
+        if ( thread == null ) {
+            thread = new PingThread();
+            thread.setDaemon(true);
+            thread.setName("TcpPingInterceptor.PingThread-"+cnt.addAndGet(1));
+            thread.start();
+        }
+        
+        //acquire the interceptors to invoke on send ping events
+        ChannelInterceptor next = getNext();
+        while ( next != null ) {
+            if ( next instanceof TcpFailureDetector ) 
+                failureDetector = new WeakReference<TcpFailureDetector>((TcpFailureDetector)next);
+            if ( next instanceof StaticMembershipInterceptor ) 
+                staticMembers = new WeakReference<StaticMembershipInterceptor>((StaticMembershipInterceptor)next);
+            next = next.getNext();
+        }
+        
+    }
+    
+    @Override
+    public void stop(int svc) throws ChannelException {
+        running = false;
+        if ( thread != null ) thread.interrupt();
+        thread = null;
+        super.stop(svc);
+    }
+    
+    @Override
+    public void heartbeat() {
+        super.heartbeat();
+        if (!getUseThread()) sendPing();
+    }
+
+    public long getInterval() {
+        return interval;
+    }
+
+    public void setInterval(long interval) {
+        this.interval = interval;
+    }
+
+    public void setUseThread(boolean useThread) {
+        this.useThread = useThread;
+    }
+
+    public void setStaticOnly(boolean staticOnly) {
+        this.staticOnly = staticOnly;
+    }
+
+    public boolean getUseThread() {
+        return useThread;
+    }
+
+    public boolean getStaticOnly() {
+        return staticOnly;
+    }
+
+    protected void sendPing() {
+        if (failureDetector.get()!=null) {
+            //we have a reference to the failure detector
+            //piggy back on that dude
+            failureDetector.get().checkMembers(true);
+        }else {
+            if (staticOnly && staticMembers.get()!=null) {
+                sendPingMessage(staticMembers.get().getMembers());
+            } else {
+                sendPingMessage(getMembers());
+            }
+        }
+    }
+
+    protected void sendPingMessage(Member[] members) {
+        if ( members == null || members.length == 0 ) return;
+        ChannelData data = new ChannelData(true);//generates a unique Id
+        data.setAddress(getLocalMember(false));
+        data.setTimestamp(System.currentTimeMillis());
+        data.setOptions(getOptionFlag());
+        try {
+            super.sendMessage(members, data, null);
+        }catch (ChannelException x) {
+            log.warn("Unable to send TCP ping.",x);
+        }
+    }
+    
+    @Override
+    public void messageReceived(ChannelMessage msg) {
+        //catch incoming 
+        boolean process = true;
+        if ( okToProcess(msg.getOptions()) ) {
+            //check to see if it is a ping message, if so, process = false
+            process = ( (msg.getMessage().getLength() != TCP_PING_DATA.length) ||
+                        (!Arrays.equals(TCP_PING_DATA,msg.getMessage().getBytes()) ) );
+        }//end if
+
+        //ignore the message, it doesnt have the flag set
+        if ( process ) super.messageReceived(msg);
+        else if ( log.isDebugEnabled() ) log.debug("Received a TCP ping packet:"+msg);
+    }//messageReceived
+    
+    protected class PingThread extends Thread {
+        @Override
+        public void run() {
+            while (running) {
+                try {
+                    sleep(interval);
+                    sendPing();
+                }catch ( InterruptedException ix ) {
+                    interrupted();
+                }catch ( Exception x )  {
+                    log.warn("Unable to send ping from TCP ping thread.",x);
+                }
+            }
+        }
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/ThroughputInterceptor.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/ThroughputInterceptor.java
new file mode 100644
index 0000000..c1bb4c7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/ThroughputInterceptor.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.interceptors;
+
+import java.text.DecimalFormat;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+import org.apache.catalina.tribes.group.InterceptorPayload;
+import org.apache.catalina.tribes.io.ChannelData;
+import org.apache.catalina.tribes.io.XByteBuffer;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+
+
+/**
+ *
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ */
+public class ThroughputInterceptor extends ChannelInterceptorBase {
+    private static final Log log = LogFactory.getLog(ThroughputInterceptor.class);
+
+    double mbTx = 0;
+    double mbAppTx = 0;
+    double mbRx = 0;
+    double timeTx = 0;
+    double lastCnt = 0;
+    AtomicLong msgTxCnt = new AtomicLong(1);
+    AtomicLong msgRxCnt = new AtomicLong(0);
+    AtomicLong msgTxErr = new AtomicLong(0);
+    int interval = 10000;
+    AtomicInteger access = new AtomicInteger(0);
+    long txStart = 0;
+    long rxStart = 0;
+    DecimalFormat df = new DecimalFormat("#0.00");
+
+
+    @Override
+    public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+        if ( access.addAndGet(1) == 1 ) txStart = System.currentTimeMillis();
+        long bytes = XByteBuffer.getDataPackageLength(((ChannelData)msg).getDataPackageLength());
+        try {
+            super.sendMessage(destination, msg, payload);
+        }catch ( ChannelException x ) {
+            msgTxErr.addAndGet(1);
+            if ( access.get() == 1 ) access.addAndGet(-1);
+            throw x;
+        } 
+        mbTx += (bytes*destination.length)/(1024d*1024d);
+        mbAppTx += bytes/(1024d*1024d);
+        if ( access.addAndGet(-1) == 0 ) {
+            long stop = System.currentTimeMillis();
+            timeTx += (stop - txStart) / 1000d;
+            if ((msgTxCnt.get() / interval) >= lastCnt) {
+                lastCnt++;
+                report(timeTx);
+            }
+        }
+        msgTxCnt.addAndGet(1);
+    }
+
+    @Override
+    public void messageReceived(ChannelMessage msg) {
+        if ( rxStart == 0 ) rxStart = System.currentTimeMillis();
+        long bytes = XByteBuffer.getDataPackageLength(((ChannelData)msg).getDataPackageLength());
+        mbRx += bytes/(1024d*1024d);
+        msgRxCnt.addAndGet(1);
+        if ( msgRxCnt.get() % interval == 0 ) report(timeTx);
+        super.messageReceived(msg);
+        
+    }
+    
+    public void report(double timeTx) {
+        StringBuilder buf = new StringBuilder("ThroughputInterceptor Report[\n\tTx Msg:");
+        buf.append(msgTxCnt).append(" messages\n\tSent:");
+        buf.append(df.format(mbTx));
+        buf.append(" MB (total)\n\tSent:");
+        buf.append(df.format(mbAppTx));
+        buf.append(" MB (application)\n\tTime:");
+        buf.append(df.format(timeTx));
+        buf.append(" seconds\n\tTx Speed:");
+        buf.append(df.format(mbTx/timeTx));
+        buf.append(" MB/sec (total)\n\tTxSpeed:");
+        buf.append(df.format(mbAppTx/timeTx));
+        buf.append(" MB/sec (application)\n\tError Msg:");
+        buf.append(msgTxErr).append("\n\tRx Msg:");
+        buf.append(msgRxCnt);
+        buf.append(" messages\n\tRx Speed:");
+        buf.append(df.format(mbRx/((System.currentTimeMillis()-rxStart)/1000)));
+        buf.append(" MB/sec (since 1st msg)\n\tReceived:");
+        buf.append(df.format(mbRx)).append(" MB]\n");
+        if ( log.isInfoEnabled() ) log.info(buf);
+    }
+    
+    public void setInterval(int interval) {
+        this.interval = interval;
+    }
+
+    public int getInterval() {
+        return interval;
+    }
+
+    public double getLastCnt() {
+        return lastCnt;
+    }
+
+    public double getMbAppTx() {
+        return mbAppTx;
+    }
+
+    public double getMbRx() {
+        return mbRx;
+    }
+
+    public double getMbTx() {
+        return mbTx;
+    }
+
+    public AtomicLong getMsgRxCnt() {
+        return msgRxCnt;
+    }
+
+    public AtomicLong getMsgTxCnt() {
+        return msgTxCnt;
+    }
+
+    public AtomicLong getMsgTxErr() {
+        return msgTxErr;
+    }
+
+    public long getRxStart() {
+        return rxStart;
+    }
+
+    public double getTimeTx() {
+        return timeTx;
+    }
+
+    public long getTxStart() {
+        return txStart;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/TwoPhaseCommitInterceptor.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/TwoPhaseCommitInterceptor.java
new file mode 100644
index 0000000..7c6b584
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/group/interceptors/TwoPhaseCommitInterceptor.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.interceptors;
+
+import java.util.HashMap;
+import java.util.Map;
+
+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.group.ChannelInterceptorBase;
+import org.apache.catalina.tribes.group.InterceptorPayload;
+import org.apache.catalina.tribes.util.Arrays;
+import org.apache.catalina.tribes.util.UUIDGenerator;
+
+/**
+ * <p>Title: </p>
+ *
+ * <p>Description: </p>
+ *
+ * <p>Company: </p>
+ *
+ * @author not attributable
+ * @version 1.0
+ */
+public class TwoPhaseCommitInterceptor extends ChannelInterceptorBase {
+
+    private static final byte[] START_DATA = new byte[] {113, 1, -58, 2, -34, -60, 75, -78, -101, -12, 32, -29, 32, 111, -40, 4};
+    private static final byte[] END_DATA = new byte[] {54, -13, 90, 110, 47, -31, 75, -24, -81, -29, 36, 52, -58, 77, -110, 56};
+    private static final org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(TwoPhaseCommitInterceptor.class);
+
+    protected HashMap<UniqueId, MapEntry> messages = new HashMap<UniqueId, MapEntry>();
+    protected long expire = 1000 * 60; //one minute expiration
+    protected boolean deepclone = true;
+
+    @Override
+    public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws
+        ChannelException {
+        //todo, optimize, if destination.length==1, then we can do
+        //msg.setOptions(msg.getOptions() & (~getOptionFlag())
+        //and just send one message
+        if (okToProcess(msg.getOptions()) ) {
+            super.sendMessage(destination, msg, null);
+            ChannelMessage confirmation = null;
+            if ( deepclone ) confirmation = (ChannelMessage)msg.deepclone();
+            else confirmation = (ChannelMessage)msg.clone();
+            confirmation.getMessage().reset();
+            UUIDGenerator.randomUUID(false,confirmation.getUniqueId(),0);
+            confirmation.getMessage().append(START_DATA,0,START_DATA.length);
+            confirmation.getMessage().append(msg.getUniqueId(),0,msg.getUniqueId().length);
+            confirmation.getMessage().append(END_DATA,0,END_DATA.length);
+            super.sendMessage(destination,confirmation,payload);
+        } else {
+            //turn off two phase commit
+            //this wont work if the interceptor has 0 as a flag
+            //since there is no flag to turn off
+            //msg.setOptions(msg.getOptions() & (~getOptionFlag()));
+            super.sendMessage(destination, msg, payload);
+        }
+    }
+
+    @Override
+    public void messageReceived(ChannelMessage msg) {
+        if (okToProcess(msg.getOptions())) {
+            if ( msg.getMessage().getLength() == (START_DATA.length+msg.getUniqueId().length+END_DATA.length) &&
+                 Arrays.contains(msg.getMessage().getBytesDirect(),0,START_DATA,0,START_DATA.length) &&
+                 Arrays.contains(msg.getMessage().getBytesDirect(),START_DATA.length+msg.getUniqueId().length,END_DATA,0,END_DATA.length) ) {
+                UniqueId id = new UniqueId(msg.getMessage().getBytesDirect(),START_DATA.length,msg.getUniqueId().length);
+                MapEntry original = messages.get(id);
+                if ( original != null ) {
+                    super.messageReceived(original.msg);
+                    messages.remove(id);
+                } else log.warn("Received a confirmation, but original message is missing. Id:"+Arrays.toString(id.getBytes()));
+            } else {
+                UniqueId id = new UniqueId(msg.getUniqueId());
+                MapEntry entry = new MapEntry((ChannelMessage)msg.deepclone(),id,System.currentTimeMillis());
+                messages.put(id,entry);
+            }
+        } else {
+            super.messageReceived(msg);
+        }
+    }
+
+    public boolean getDeepclone() {
+        return deepclone;
+    }
+
+    public long getExpire() {
+        return expire;
+    }
+
+    public void setDeepclone(boolean deepclone) {
+        this.deepclone = deepclone;
+    }
+
+    public void setExpire(long expire) {
+        this.expire = expire;
+    }
+    
+    @Override
+    public void heartbeat() {
+        try {
+            long now = System.currentTimeMillis();
+            Map.Entry<UniqueId,MapEntry>[] entries = messages.entrySet().toArray(new Map.Entry[messages.size()]);
+            for (int i=0; i<entries.length; i++ ) {
+                MapEntry entry = entries[i].getValue();
+                if ( entry.expired(now,expire) ) {
+                    if(log.isInfoEnabled())
+                        log.info("Message ["+entry.id+"] has expired. Removing.");
+                    messages.remove(entry.id);
+                }//end if
+            }
+        } catch ( Exception x ) {
+            log.warn("Unable to perform heartbeat on the TwoPhaseCommit interceptor.",x);
+        } finally {
+            super.heartbeat();
+        }
+    }
+    
+    public static class MapEntry {
+        public ChannelMessage msg;
+        public UniqueId id;
+        public long timestamp;
+        
+        public MapEntry(ChannelMessage msg, UniqueId id, long timestamp) {
+            this.msg = msg;
+            this.id = id;
+            this.timestamp = timestamp;
+        }
+        public boolean expired(long now, long expiration) {
+            return (now - timestamp ) > expiration;
+        }
+
+    }
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/io/BufferPool.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/io/BufferPool.java
new file mode 100644
index 0000000..6254d66
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/io/BufferPool.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.io;
+
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ *
+ * @author Filip Hanik
+ * 
+ * @version 1.0
+ */
+public class BufferPool {
+    private static final Log log = LogFactory.getLog(BufferPool.class);
+
+    public static final int DEFAULT_POOL_SIZE = 100*1024*1024; //100MB
+
+
+
+    protected static volatile BufferPool instance = null;
+    protected BufferPoolAPI pool = null;
+
+    private BufferPool(BufferPoolAPI pool) {
+        this.pool = pool;
+    }
+
+    public XByteBuffer getBuffer(int minSize, boolean discard) {
+        if ( pool != null ) return pool.getBuffer(minSize, discard);
+        else return new XByteBuffer(minSize,discard);
+    }
+
+    public void returnBuffer(XByteBuffer buffer) {
+        if ( pool != null ) pool.returnBuffer(buffer);
+    }
+
+    public void clear() {
+        if ( pool != null ) pool.clear();
+    }
+
+
+    public static BufferPool getBufferPool() {
+        if (  (instance == null) ) {
+            synchronized (BufferPool.class) {
+                if ( instance == null ) {
+                   BufferPoolAPI pool = null;
+                   Class<?> clazz = null;
+                   try {
+                       // TODO Is this approach still required?
+                       clazz = Class.forName("org.apache.catalina.tribes.io.BufferPool15Impl");
+                       pool = (BufferPoolAPI)clazz.newInstance();
+                   } catch ( Throwable x ) {
+                       log.warn("Unable to initilize BufferPool, not pooling XByteBuffer objects:"+x.getMessage());
+                       if ( log.isDebugEnabled() ) log.debug("Unable to initilize BufferPool, not pooling XByteBuffer objects:",x);
+                   }
+                   if (pool != null) {
+                       pool.setMaxSize(DEFAULT_POOL_SIZE);
+                       log.info("Created a buffer pool with max size:"+DEFAULT_POOL_SIZE+" bytes of type:"+(clazz!=null?clazz.getName():"null"));
+                       instance = new BufferPool(pool);
+                   }
+                }//end if
+            }//sync
+        }//end if
+        return instance;
+    }
+
+
+    public static interface BufferPoolAPI {
+        public void setMaxSize(int bytes);
+
+        public XByteBuffer getBuffer(int minSize, boolean discard);
+
+        public void returnBuffer(XByteBuffer buffer);
+
+        public void clear();
+    }    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/io/BufferPool15Impl.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/io/BufferPool15Impl.java
new file mode 100644
index 0000000..3cc2b15
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/io/BufferPool15Impl.java
@@ -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.
+ */
+package org.apache.catalina.tribes.io;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ *
+ * @author Filip Hanik
+ * @version 1.0
+ */
+class BufferPool15Impl implements BufferPool.BufferPoolAPI {
+    protected int maxSize;
+    protected AtomicInteger size = new AtomicInteger(0);
+    protected ConcurrentLinkedQueue<XByteBuffer> queue = new ConcurrentLinkedQueue<XByteBuffer>();
+
+    public void setMaxSize(int bytes) {
+        this.maxSize = bytes;
+    }
+
+
+    public XByteBuffer getBuffer(int minSize, boolean discard) {
+        XByteBuffer buffer = queue.poll();
+        if ( buffer != null ) size.addAndGet(-buffer.getCapacity());
+        if ( buffer == null ) buffer = new XByteBuffer(minSize,discard);
+        else if ( buffer.getCapacity() <= minSize ) buffer.expand(minSize);
+        buffer.setDiscard(discard);
+        buffer.reset();
+        return buffer;
+    }
+
+    public void returnBuffer(XByteBuffer buffer) {
+        if ( (size.get() + buffer.getCapacity()) <= maxSize ) {
+            size.addAndGet(buffer.getCapacity());
+            queue.offer(buffer);
+        }
+    }
+
+    public void clear() {
+        queue.clear();
+        size.set(0);
+    }
+
+    public int getMaxSize() {
+        return maxSize;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/io/ChannelData.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/io/ChannelData.java
new file mode 100644
index 0000000..b400702
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/io/ChannelData.java
@@ -0,0 +1,366 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.io;
+
+import java.sql.Timestamp;
+import java.util.Arrays;
+
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.membership.MemberImpl;
+import org.apache.catalina.tribes.util.UUIDGenerator;
+
+/**
+ * The <code>ChannelData</code> object is used to transfer a message through the 
+ * channel interceptor stack and eventually out on a transport to be sent 
+ * to another node. While the message is being processed by the different 
+ * interceptors, the message data can be manipulated as each interceptor seems appropriate.
+ * @author Peter Rossbach
+ * @author Filip Hanik
+ * @version $Id: ChannelData.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ * 
+ */
+public class ChannelData implements ChannelMessage {
+    private static final long serialVersionUID = 1L;
+
+    public static final ChannelData[] EMPTY_DATA_ARRAY = new ChannelData[0];
+    
+    public static volatile boolean USE_SECURE_RANDOM_FOR_UUID = false;
+    
+    /**
+     * The options this message was sent with
+     */
+    private int options = 0 ;
+    /**
+     * The message data, stored in a dynamic buffer
+     */
+    private XByteBuffer message ;
+    /**
+     * The timestamp that goes with this message
+     */
+    private long timestamp ;
+    /**
+     * A unique message id
+     */
+    private byte[] uniqueId ;
+    /**
+     * The source or reply-to address for this message
+     */
+    private Member address;
+
+    /**
+     * Creates an empty channel data with a new unique Id
+     * @see #ChannelData(boolean)
+     */
+    public ChannelData() {
+        this(true);
+    }
+    
+    /**
+     * Create an empty channel data object
+     * @param generateUUID boolean - if true, a unique Id will be generated
+     */
+    public ChannelData(boolean generateUUID) {
+        if ( generateUUID ) generateUUID();
+    }
+
+
+    /**
+     * Creates a new channel data object with data
+     * @param uniqueId - unique message id
+     * @param message - message data
+     * @param timestamp - message timestamp
+     */
+    public ChannelData(byte[] uniqueId, XByteBuffer message, long timestamp) {
+        this.uniqueId = uniqueId;
+        this.message = message;
+        this.timestamp = timestamp;
+    }
+    
+    /**
+     * @return Returns the message byte buffer
+     */
+    public XByteBuffer getMessage() {
+        return message;
+    }
+    /**
+     * @param message The message to send.
+     */
+    public void setMessage(XByteBuffer message) {
+        this.message = message;
+    }
+    /**
+     * @return Returns the timestamp.
+     */
+    public long getTimestamp() {
+        return timestamp;
+    }
+    /**
+     * @param timestamp The timestamp to send
+     */
+    public void setTimestamp(long timestamp) {
+        this.timestamp = timestamp;
+    }
+    /**
+     * @return Returns the uniqueId.
+     */
+    public byte[] getUniqueId() {
+        return uniqueId;
+    }
+    /**
+     * @param uniqueId The uniqueId to send.
+     */
+    public void setUniqueId(byte[] uniqueId) {
+        this.uniqueId = uniqueId;
+    }
+    /**
+     * @return returns the message options 
+     * see org.apache.catalina.tribes.Channel#sendMessage(org.apache.catalina.tribes.Member[], java.io.Serializable, int)
+     *                                                 
+     */
+    public int getOptions() {
+        return options;
+    }
+    /**
+     * Sets the message options.
+     * 
+     * @param options the message options
+     */
+    public void setOptions(int options) {
+        this.options = options;
+    }
+    
+    /**
+     * Returns the source or reply-to address
+     * @return Member
+     */
+    public Member getAddress() {
+        return address;
+    }
+
+    /**
+     * Sets the source or reply-to address
+     * @param address Member
+     */
+    public void setAddress(Member address) {
+        this.address = address;
+    }
+    
+    /**
+     * Generates a UUID and invokes setUniqueId
+     */
+    public void generateUUID() {
+        byte[] data = new byte[16];
+        UUIDGenerator.randomUUID(USE_SECURE_RANDOM_FOR_UUID,data,0);
+        setUniqueId(data);
+    }
+
+    public int getDataPackageLength() {
+        int length = 
+            4 + //options
+            8 + //timestamp  off=4
+            4 + //unique id length off=12
+            uniqueId.length+ //id data off=12+uniqueId.length
+            4 + //addr length off=12+uniqueId.length+4
+            ((MemberImpl)address).getDataLength()+ //member data off=12+uniqueId.length+4+add.length
+            4 + //message length off=12+uniqueId.length+4+add.length+4
+            message.getLength();
+        return length;
+
+    }
+    
+    /**
+     * Serializes the ChannelData object into a byte[] array
+     * @return byte[]
+     */
+    public byte[] getDataPackage()  {
+        int length = getDataPackageLength();
+        byte[] data = new byte[length];
+        int offset = 0;
+        return getDataPackage(data,offset);
+    }
+
+    public byte[] getDataPackage(byte[] data, int offset)  {
+        byte[] addr = ((MemberImpl)address).getData(false);
+        XByteBuffer.toBytes(options,data,offset);
+        offset += 4; //options
+        XByteBuffer.toBytes(timestamp,data,offset);
+        offset += 8; //timestamp
+        XByteBuffer.toBytes(uniqueId.length,data,offset);
+        offset += 4; //uniqueId.length
+        System.arraycopy(uniqueId,0,data,offset,uniqueId.length);
+        offset += uniqueId.length; //uniqueId data
+        XByteBuffer.toBytes(addr.length,data,offset);
+        offset += 4; //addr.length
+        System.arraycopy(addr,0,data,offset,addr.length);
+        offset += addr.length; //addr data
+        XByteBuffer.toBytes(message.getLength(),data,offset);
+        offset += 4; //message.length
+        System.arraycopy(message.getBytesDirect(),0,data,offset,message.getLength());
+        offset += message.getLength(); //message data
+        return data;
+    }
+    
+    /**
+     * Deserializes a ChannelData object from a byte array
+     * @param xbuf byte[]
+     * @return ChannelData
+     */
+    public static ChannelData getDataFromPackage(XByteBuffer xbuf)  {
+        ChannelData data = new ChannelData(false);
+        int offset = 0;
+        data.setOptions(XByteBuffer.toInt(xbuf.getBytesDirect(),offset));
+        offset += 4; //options
+        data.setTimestamp(XByteBuffer.toLong(xbuf.getBytesDirect(),offset));
+        offset += 8; //timestamp
+        data.uniqueId = new byte[XByteBuffer.toInt(xbuf.getBytesDirect(),offset)];
+        offset += 4; //uniqueId length
+        System.arraycopy(xbuf.getBytesDirect(),offset,data.uniqueId,0,data.uniqueId.length);
+        offset += data.uniqueId.length; //uniqueId data
+        //byte[] addr = new byte[XByteBuffer.toInt(xbuf.getBytesDirect(),offset)];
+        int addrlen = XByteBuffer.toInt(xbuf.getBytesDirect(),offset);
+        offset += 4; //addr length
+        //System.arraycopy(xbuf.getBytesDirect(),offset,addr,0,addr.length);
+        data.setAddress(MemberImpl.getMember(xbuf.getBytesDirect(),offset,addrlen));
+        //offset += addr.length; //addr data
+        offset += addrlen;
+        int xsize = XByteBuffer.toInt(xbuf.getBytesDirect(),offset);
+        offset += 4; //xsize length
+        System.arraycopy(xbuf.getBytesDirect(),offset,xbuf.getBytesDirect(),0,xsize);
+        xbuf.setLength(xsize);
+        data.message = xbuf;
+        return data;
+
+    }
+
+    public static ChannelData getDataFromPackage(byte[] b)  {
+        ChannelData data = new ChannelData(false);
+        int offset = 0;
+        data.setOptions(XByteBuffer.toInt(b,offset));
+        offset += 4; //options
+        data.setTimestamp(XByteBuffer.toLong(b,offset));
+        offset += 8; //timestamp
+        data.uniqueId = new byte[XByteBuffer.toInt(b,offset)];
+        offset += 4; //uniqueId length
+        System.arraycopy(b,offset,data.uniqueId,0,data.uniqueId.length);
+        offset += data.uniqueId.length; //uniqueId data
+        byte[] addr = new byte[XByteBuffer.toInt(b,offset)];
+        offset += 4; //addr length
+        System.arraycopy(b,offset,addr,0,addr.length);
+        data.setAddress(MemberImpl.getMember(addr));
+        offset += addr.length; //addr data
+        int xsize = XByteBuffer.toInt(b,offset);
+        //data.message = new XByteBuffer(new byte[xsize],false);
+        data.message = BufferPool.getBufferPool().getBuffer(xsize,false);
+        offset += 4; //message length
+        System.arraycopy(b,offset,data.message.getBytesDirect(),0,xsize);
+        data.message.append(b,offset,xsize);
+        offset += xsize; //message data
+        return data;
+    }
+    
+    @Override
+    public int hashCode() {
+        return XByteBuffer.toInt(getUniqueId(),0);
+    }
+    
+    /**
+     * Compares to ChannelData objects, only compares on getUniqueId().equals(o.getUniqueId())
+     * @param o Object
+     * @return boolean
+     */
+    @Override
+    public boolean equals(Object o) {
+        if ( o instanceof ChannelData ) {
+            return Arrays.equals(getUniqueId(),((ChannelData)o).getUniqueId());
+        } else return false;
+    }
+    
+    /**
+     * Create a shallow clone, only the data gets recreated
+     * @return ClusterData
+     */
+    @Override
+    public Object clone() {
+//        byte[] d = this.getDataPackage();
+//        return ClusterData.getDataFromPackage(d);
+        ChannelData clone = new ChannelData(false);
+        clone.options = this.options;
+        clone.message = new XByteBuffer(this.message.getBytesDirect(),false);
+        clone.timestamp = this.timestamp;
+        clone.uniqueId = this.uniqueId;
+        clone.address = this.address;
+        return clone;
+    }
+    
+    /**
+     * Complete clone
+     * @return ClusterData
+     */
+    public Object deepclone() {
+        byte[] d = this.getDataPackage();
+        return ChannelData.getDataFromPackage(d);
+    }
+    
+    /**
+     * Utility method, returns true if the options flag indicates that an ack
+     * is to be sent after the message has been received and processed
+     * @param options int - the options for the message
+     * @return boolean 
+     * @see org.apache.catalina.tribes.Channel#SEND_OPTIONS_USE_ACK
+     * @see org.apache.catalina.tribes.Channel#SEND_OPTIONS_SYNCHRONIZED_ACK
+     */
+    public static boolean sendAckSync(int options) {
+        return ( (Channel.SEND_OPTIONS_USE_ACK & options) == Channel.SEND_OPTIONS_USE_ACK) &&
+            ( (Channel.SEND_OPTIONS_SYNCHRONIZED_ACK & options) == Channel.SEND_OPTIONS_SYNCHRONIZED_ACK);
+    }
+
+
+    /**
+     * Utility method, returns true if the options flag indicates that an ack
+     * is to be sent after the message has been received but not yet processed
+     * @param options int - the options for the message
+     * @return boolean 
+     * @see org.apache.catalina.tribes.Channel#SEND_OPTIONS_USE_ACK
+     * @see org.apache.catalina.tribes.Channel#SEND_OPTIONS_SYNCHRONIZED_ACK
+     */
+    public static boolean sendAckAsync(int options) {
+        return ( (Channel.SEND_OPTIONS_USE_ACK & options) == Channel.SEND_OPTIONS_USE_ACK) &&
+            ( (Channel.SEND_OPTIONS_SYNCHRONIZED_ACK & options) != Channel.SEND_OPTIONS_SYNCHRONIZED_ACK);
+    }
+    
+    @Override
+    public String toString() {
+        StringBuilder buf = new StringBuilder();
+        buf.append("ClusterData[src=");
+        buf.append(getAddress()).append("; id=");
+        buf.append(bToS(getUniqueId())).append("; sent=");
+        buf.append(new Timestamp(this.getTimestamp()).toString()).append("]");
+        return buf.toString();
+    }
+    
+    public 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();
+    }
+
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/io/DirectByteArrayOutputStream.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/io/DirectByteArrayOutputStream.java
new file mode 100644
index 0000000..c487d7e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/io/DirectByteArrayOutputStream.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.tribes.io;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Byte array output stream that exposes the byte array directly
+ *
+ * @author not attributable
+ * @version 1.0
+ */
+public class DirectByteArrayOutputStream extends OutputStream {
+    
+    private XByteBuffer buffer;
+    
+    public DirectByteArrayOutputStream(int size) {
+        buffer = new XByteBuffer(size,false);
+    }
+
+    /**
+     * Writes the specified byte to this output stream.
+     *
+     * @param b the <code>byte</code>.
+     * @throws IOException if an I/O error occurs. In particular, an
+     *   <code>IOException</code> may be thrown if the output stream has
+     *   been closed.
+     * TODO Implement this java.io.OutputStream method
+     */
+    @Override
+    public void write(int b) throws IOException {
+        buffer.append((byte)b);
+    }
+    
+    public int size() {
+        return buffer.getLength();
+    }
+    
+    public byte[] getArrayDirect() {
+        return buffer.getBytesDirect();
+    }
+    
+    public byte[] getArray() {
+        return buffer.getBytes();
+    }
+
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/io/ListenCallback.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/io/ListenCallback.java
new file mode 100644
index 0000000..70109ac
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/io/ListenCallback.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.io;
+
+import org.apache.catalina.tribes.ChannelMessage;
+
+
+
+/**
+ * Internal interface, similar to the MessageListener but used 
+ * at the IO base
+ * The listen callback interface is used by the replication system
+ * when data has been received. The interface does not care about
+ * objects and marshalling and just passes the bytes straight through.
+ * @author Filip Hanik
+ * @version $Id: ListenCallback.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ */
+public interface ListenCallback
+{
+    /**
+     * This method is invoked on the callback object to notify it that new data has
+     * been received from one of the cluster nodes.
+     * @param data - the message bytes received from the cluster/replication system
+     */
+     public void messageDataReceived(ChannelMessage data);
+     
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/io/ObjectReader.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/io/ObjectReader.java
new file mode 100644
index 0000000..9dec59a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/io/ObjectReader.java
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.io;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+
+
+/**
+ * The object reader object is an object used in conjunction with
+ * java.nio TCP messages. This object stores the message bytes in a
+ * <code>XByteBuffer</code> until a full package has been received.
+ * This object uses an XByteBuffer which is an extendable object buffer that also allows
+ * for message encoding and decoding.
+ *
+ * @author Filip Hanik
+ * @version $Id: ObjectReader.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ */
+public class ObjectReader {
+
+    private static final Log log = LogFactory.getLog(ObjectReader.class);
+
+    private XByteBuffer buffer;
+
+    protected long lastAccess = System.currentTimeMillis();
+
+    protected boolean accessed = false;
+    private boolean cancelled;
+
+    public ObjectReader(int packetSize) {
+        this.buffer = new XByteBuffer(packetSize, true);
+    }
+    /**
+     * Creates an <code>ObjectReader</code> for a TCP NIO socket channel
+     * @param channel - the channel to be read.
+     */
+    public ObjectReader(SocketChannel channel) {
+        this(channel.socket());
+    }
+
+    /**
+     * Creates an <code>ObjectReader</code> for a TCP socket
+     * @param socket Socket
+     */
+    public ObjectReader(Socket socket) {
+        try{
+            this.buffer = new XByteBuffer(socket.getReceiveBufferSize(), true);
+        }catch ( IOException x ) {
+            //unable to get buffer size
+            log.warn("Unable to retrieve the socket receiver buffer size, setting to default 43800 bytes.");
+            this.buffer = new XByteBuffer(43800,true);
+        }
+    }
+
+    public synchronized void access() {
+        this.accessed = true;
+        this.lastAccess = System.currentTimeMillis();
+    }
+
+    public synchronized void finish() {
+        this.accessed = false;
+        this.lastAccess = System.currentTimeMillis();
+    }
+
+    public boolean isAccessed() {
+        return this.accessed;
+    }
+
+    /**
+     * Append new bytes to buffer.
+     * @see XByteBuffer#countPackages()
+     * @param data new transfer buffer
+     * @param len length in buffer
+     * @param count whether to return the count
+     * @return number of messages that was sent to callback (or -1 if count == false)
+     * @throws java.io.IOException
+     */
+    public int append(ByteBuffer data, int len, boolean count) throws java.io.IOException {
+       buffer.append(data,len);
+       int pkgCnt = -1;
+       if ( count ) pkgCnt = buffer.countPackages();
+       return pkgCnt;
+   }
+
+     public int append(byte[] data,int off,int len, boolean count) throws java.io.IOException {
+        buffer.append(data,off,len);
+        int pkgCnt = -1;
+        if ( count ) pkgCnt = buffer.countPackages();
+        return pkgCnt;
+    }
+
+    /**
+     * Send buffer to cluster listener (callback).
+     * Is message complete receiver send message to callback?
+     *
+     * @see org.apache.catalina.tribes.transport.ReceiverBase#messageDataReceived(ChannelMessage)
+     * @see XByteBuffer#doesPackageExist()
+     * @see XByteBuffer#extractPackage(boolean)
+     *
+     * @return number of received packages/messages
+     * @throws java.io.IOException
+     */
+    public ChannelMessage[] execute() throws java.io.IOException {
+        int pkgCnt = buffer.countPackages();
+        ChannelMessage[] result = new ChannelMessage[pkgCnt];
+        for (int i=0; i<pkgCnt; i++)  {
+            ChannelMessage data = buffer.extractPackage(true);
+            result[i] = data;
+        }
+        return result;
+    }
+
+    public int bufferSize() {
+        return buffer.getLength();
+    }
+
+
+    public boolean hasPackage() {
+        return buffer.countPackages(true)>0;
+    }
+    /**
+     * Returns the number of packages that the reader has read
+     * @return int
+     */
+    public int count() {
+        return buffer.countPackages();
+    }
+
+    public void close() {
+        this.buffer = null;
+    }
+
+    public long getLastAccess() {
+        return lastAccess;
+    }
+
+    public boolean isCancelled() {
+        return cancelled;
+    }
+
+    public void setLastAccess(long lastAccess) {
+        this.lastAccess = lastAccess;
+    }
+
+    public void setCancelled(boolean cancelled) {
+        this.cancelled = cancelled;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/io/ReplicationStream.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/io/ReplicationStream.java
new file mode 100644
index 0000000..ffc6d14
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/io/ReplicationStream.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
+
+/**
+ * Custom subclass of <code>ObjectInputStream</code> that loads from the
+ * class loader for this web application.  This allows classes defined only
+ * with the web application to be found correctly.
+ *
+ * @author Craig R. McClanahan
+ * @author Bip Thelin
+ * @author Filip Hanik
+ * @version $Id: ReplicationStream.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ */
+
+public final class ReplicationStream extends ObjectInputStream {
+
+    
+    /**
+     * The class loader we will use to resolve classes.
+     */
+    private ClassLoader[] classLoaders = null;
+    
+    /**
+     * Construct a new instance of CustomObjectInputStream
+     *
+     * @param stream The input stream we will read from
+     * @param classLoaders The class loader array used to instantiate objects
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public ReplicationStream(InputStream stream,
+                             ClassLoader[] classLoaders)
+        throws IOException {
+
+        super(stream);
+        this.classLoaders = classLoaders;
+    }
+
+    /**
+     * Load the local class equivalent of the specified stream class
+     * description, by using the class loader assigned to this Context.
+     *
+     * @param classDesc Class description from the input stream
+     *
+     * @exception ClassNotFoundException if this class cannot be found
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public Class<?> resolveClass(ObjectStreamClass classDesc)
+        throws ClassNotFoundException, IOException {
+        String name = classDesc.getName();
+        try {
+            return resolveClass(name);
+        } catch (ClassNotFoundException e) {
+            return super.resolveClass(classDesc);
+        }
+    }
+    
+    public Class<?> resolveClass(String name)
+        throws ClassNotFoundException, IOException {
+
+        boolean tryRepFirst = name.startsWith("org.apache.catalina.tribes");
+            try {
+            if (tryRepFirst)
+                return findReplicationClass(name);
+            else
+                return findExternalClass(name);
+        } catch (Exception x) {
+            if (tryRepFirst)
+                return findExternalClass(name);
+            else
+                return findReplicationClass(name);
+        }
+    }
+    
+    /**
+     * ObjectInputStream.resolveProxyClass has some funky way of using 
+     * the incorrect class loader to resolve proxy classes, let's do it our way instead
+     */
+    @Override
+    protected Class<?> resolveProxyClass(String[] interfaces)
+            throws IOException, ClassNotFoundException {
+        
+        ClassLoader latestLoader = (classLoaders!=null && classLoaders.length==0)?null:classLoaders[0];
+        ClassLoader nonPublicLoader = null;
+        boolean hasNonPublicInterface = false;
+
+        // define proxy in class loader of non-public interface(s), if any
+        Class<?>[] classObjs = new Class[interfaces.length];
+        for (int i = 0; i < interfaces.length; i++) {
+            Class<?> cl = this.resolveClass(interfaces[i]);
+            if (latestLoader==null) latestLoader = cl.getClassLoader();
+            if ((cl.getModifiers() & Modifier.PUBLIC) == 0) {
+                if (hasNonPublicInterface) {
+                    if (nonPublicLoader != cl.getClassLoader()) {
+                        throw new IllegalAccessError(
+                                "conflicting non-public interface class loaders");
+                    }
+                } else {
+                    nonPublicLoader = cl.getClassLoader();
+                    hasNonPublicInterface = true;
+                }
+            }
+            classObjs[i] = cl;
+        }
+        try {
+            return Proxy.getProxyClass(hasNonPublicInterface ? nonPublicLoader
+                    : latestLoader, classObjs);
+        } catch (IllegalArgumentException e) {
+            throw new ClassNotFoundException(null, e);
+        }
+    }
+
+    
+    public Class<?> findReplicationClass(String name)
+        throws ClassNotFoundException, IOException {
+        Class<?> clazz = Class.forName(name, false, getClass().getClassLoader());
+        return clazz;
+    }
+
+    public Class<?> findExternalClass(String name) throws ClassNotFoundException  {
+        ClassNotFoundException cnfe = null;
+        for (int i=0; i<classLoaders.length; i++ ) {
+            try {
+                Class<?> clazz = Class.forName(name, false, classLoaders[i]);
+                return clazz;
+            } catch ( ClassNotFoundException x ) {
+                cnfe = x;
+            } 
+        }
+        if ( cnfe != null ) throw cnfe;
+        else throw new ClassNotFoundException(name);
+    }
+    
+    @Override
+    public void close() throws IOException  {
+        this.classLoaders = null;
+        super.close();
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/io/XByteBuffer.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/io/XByteBuffer.java
new file mode 100644
index 0000000..77cee1e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/io/XByteBuffer.java
@@ -0,0 +1,604 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.io;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.nio.ByteBuffer;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * The XByteBuffer provides a dual functionality.
+ * One, it stores message bytes and automatically extends the byte buffer if needed.<BR>
+ * Two, it can encode and decode packages so that they can be defined and identified
+ * as they come in on a socket.
+ * <br>
+ * <b>THIS CLASS IS NOT THREAD SAFE</B><BR>
+ * <br/>
+ * Transfer package:
+ * <ul>
+ * <li><b>START_DATA/b> - 7 bytes - <i>FLT2002</i></li>
+ * <li><b>SIZE</b>      - 4 bytes - size of the data package</li>
+ * <li><b>DATA</b>      - should be as many bytes as the prev SIZE</li>
+ * <li><b>END_DATA</b>  - 7 bytes - <i>TLF2003</i></lI>
+ * </ul>
+ * @author Filip Hanik
+ * @version $Id: XByteBuffer.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ */
+public class XByteBuffer
+{
+    
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog( XByteBuffer.class );
+    
+    /**
+     * This is a package header, 7 bytes (FLT2002)
+     */
+    private static final byte[] START_DATA = {70,76,84,50,48,48,50};
+    
+    /**
+     * This is the package footer, 7 bytes (TLF2003)
+     */
+    private static final byte[] END_DATA = {84,76,70,50,48,48,51};
+ 
+    /**
+     * Variable to hold the data
+     */
+    protected byte[] buf = null;
+    
+    /**
+     * Current length of data in the buffer
+     */
+    protected int bufSize = 0;
+    
+    /**
+     * Flag for discarding invalid packages
+     * If this flag is set to true, and append(byte[],...) is called,
+     * the data added will be inspected, and if it doesn't start with 
+     * <code>START_DATA</code> it will be thrown away.
+     * 
+     */
+    protected boolean discard = true;
+    
+    /**
+     * Constructs a new XByteBuffer
+     * @param size - the initial size of the byte buffer
+     * TODO use a pool of byte[] for performance
+     */
+    public XByteBuffer(int size, boolean discard) {
+        buf = new byte[size];
+        this.discard = discard;
+    }
+    
+    public XByteBuffer(byte[] data,boolean discard) {
+        this(data,data.length+128,discard);
+    }
+    
+    public XByteBuffer(byte[] data, int size,boolean discard) {
+        int length = Math.max(data.length,size);
+        buf = new byte[length];
+        System.arraycopy(data,0,buf,0,data.length);
+        bufSize = data.length;
+        this.discard = discard;
+    }
+    
+    public int getLength() {
+        return bufSize;
+    }
+
+    public void setLength(int size) {
+        if ( size > buf.length ) throw new ArrayIndexOutOfBoundsException("Size is larger than existing buffer.");
+        bufSize = size;
+    }
+    
+    public void trim(int length) {
+        if ( (bufSize - length) < 0 ) 
+            throw new ArrayIndexOutOfBoundsException("Can't trim more bytes than are available. length:"+bufSize+" trim:"+length);
+        bufSize -= length;
+    }
+    
+    public void reset() {
+        bufSize = 0;
+    }
+            
+    public byte[] getBytesDirect() {
+        return this.buf;
+    }
+
+    /**
+     * Returns the bytes in the buffer, in its exact length
+     */
+    public byte[] getBytes() {
+        byte[] b = new byte[bufSize];
+        System.arraycopy(buf,0,b,0,bufSize);
+        return b;
+    }
+
+    /**
+     * Resets the buffer
+     */
+    public void clear() {
+        bufSize = 0;
+    }
+
+    /**
+     * Appends the data to the buffer. If the data is incorrectly formatted, ie, the data should always start with the
+     * header, false will be returned and the data will be discarded.
+     * @param b - bytes to be appended
+     * @param len - the number of bytes to append.
+     * @return true if the data was appended correctly. Returns false if the package is incorrect, ie missing header or something, or the length of data is 0
+     */
+    public boolean append(ByteBuffer b, int len) {
+        int newcount = bufSize + len;
+        if (newcount > buf.length) {
+            expand(newcount);
+        }
+        b.get(buf,bufSize,len);
+        
+        bufSize = newcount;
+        
+        if ( discard ) {
+            if (bufSize > START_DATA.length && (firstIndexOf(buf, 0, START_DATA) == -1)) {
+                bufSize = 0;
+                log.error("Discarded the package, invalid header");
+                return false;
+            }
+        }
+        return true;
+
+    }
+    
+    public boolean append(byte i) {
+        int newcount = bufSize + 1;
+        if (newcount > buf.length) {
+            expand(newcount);
+        }
+        buf[bufSize] = i;
+        bufSize = newcount;
+        return true;
+    }
+
+
+    public boolean append(boolean i) {
+        int newcount = bufSize + 1;
+        if (newcount > buf.length) {
+            expand(newcount);
+        }
+        XByteBuffer.toBytes(i,buf,bufSize);
+        bufSize = newcount;
+        return true;
+    }
+
+    public boolean append(long i) {
+        int newcount = bufSize + 8;
+        if (newcount > buf.length) {
+            expand(newcount);
+        }
+        XByteBuffer.toBytes(i,buf,bufSize);
+        bufSize = newcount;
+        return true;
+    }
+    
+    public boolean append(int i) {
+        int newcount = bufSize + 4;
+        if (newcount > buf.length) {
+            expand(newcount);
+        }
+        XByteBuffer.toBytes(i,buf,bufSize);
+        bufSize = newcount;
+        return true;
+    }
+
+    public boolean append(byte[] b, int off, int len) {
+        if ((off < 0) || (off > b.length) || (len < 0) ||
+            ((off + len) > b.length) || ((off + len) < 0))  {
+            throw new IndexOutOfBoundsException();
+        } else if (len == 0) {
+            return false;
+        }
+
+        int newcount = bufSize + len;
+        if (newcount > buf.length) {
+            expand(newcount);
+        }
+        System.arraycopy(b, off, buf, bufSize, len);
+        bufSize = newcount;
+
+        if ( discard ) {
+            if (bufSize > START_DATA.length && (firstIndexOf(buf, 0, START_DATA) == -1)) {
+                bufSize = 0;
+                log.error("Discarded the package, invalid header");
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public void expand(int newcount) {
+        //don't change the allocation strategy
+        byte newbuf[] = new byte[Math.max(buf.length << 1, newcount)];
+        System.arraycopy(buf, 0, newbuf, 0, bufSize);
+        buf = newbuf;
+    }
+    
+    public int getCapacity() {
+        return buf.length;
+    }
+
+
+    /**
+     * Internal mechanism to make a check if a complete package exists
+     * within the buffer
+     * @return - true if a complete package (header,compress,size,data,footer) exists within the buffer
+     */
+    public int countPackages() {
+        return countPackages(false);
+    }
+
+    public int countPackages(boolean first)
+    {
+        int cnt = 0;
+        int pos = START_DATA.length;
+        int start = 0;
+
+        while ( start < bufSize ) {
+            //first check start header
+            int index = XByteBuffer.firstIndexOf(buf,start,START_DATA);
+            //if the header (START_DATA) isn't the first thing or
+            //the buffer isn't even 14 bytes
+            if ( index != start || ((bufSize-start)<14) ) break;
+            //next 4 bytes are compress flag not needed for count packages
+            //then get the size 4 bytes
+            int size = toInt(buf, pos);
+            //now the total buffer has to be long enough to hold
+            //START_DATA.length+4+size+END_DATA.length
+            pos = start + START_DATA.length + 4 + size;
+            if ( (pos + END_DATA.length) > bufSize) break;
+            //and finally check the footer of the package END_DATA
+            int newpos = firstIndexOf(buf, pos, END_DATA);
+            //mismatch, there is no package
+            if (newpos != pos) break;
+            //increase the packet count
+            cnt++;
+            //reset the values
+            start = pos + END_DATA.length;
+            pos = start + START_DATA.length;
+            //we only want to verify that we have at least one package
+            if ( first ) break;
+        }
+        return cnt;
+    }
+
+    /**
+     * Method to check if a package exists in this byte buffer.
+     * @return - true if a complete package (header,options,size,data,footer) exists within the buffer
+     */
+    public boolean doesPackageExist()  {
+        return (countPackages(true)>0);
+    }
+
+    /**
+     * Extracts the message bytes from a package.
+     * If no package exists, a IllegalStateException will be thrown.
+     * @param clearFromBuffer - if true, the package will be removed from the byte buffer
+     * @return - returns the actual message bytes (header, compress,size and footer not included).
+     */
+    public XByteBuffer extractDataPackage(boolean clearFromBuffer) {
+        int psize = countPackages(true);
+        if (psize == 0) {
+            throw new java.lang.IllegalStateException("No package exists in XByteBuffer");
+        }
+        int size = toInt(buf, START_DATA.length);
+        XByteBuffer xbuf = BufferPool.getBufferPool().getBuffer(size,false);
+        xbuf.setLength(size);
+        System.arraycopy(buf, START_DATA.length + 4, xbuf.getBytesDirect(), 0, size);
+        if (clearFromBuffer) {
+            int totalsize = START_DATA.length + 4 + size + END_DATA.length;
+            bufSize = bufSize - totalsize;
+            System.arraycopy(buf, totalsize, buf, 0, bufSize);
+        }
+        return xbuf;
+
+    }
+    
+    public ChannelData extractPackage(boolean clearFromBuffer) throws java.io.IOException {
+        XByteBuffer xbuf = extractDataPackage(clearFromBuffer);
+        ChannelData cdata = ChannelData.getDataFromPackage(xbuf);
+        return cdata;
+    }
+    
+    /**
+     * Creates a complete data package
+     * @param cdata - the message data to be contained within the package
+     * @return - a full package (header,size,data,footer)
+     */
+    public static byte[] createDataPackage(ChannelData cdata) {
+//        return createDataPackage(cdata.getDataPackage());
+        //avoid one extra byte array creation
+        int dlength = cdata.getDataPackageLength();
+        int length = getDataPackageLength(dlength);
+        byte[] data = new byte[length];
+        int offset = 0;
+        System.arraycopy(START_DATA, 0, data, offset, START_DATA.length);
+        offset += START_DATA.length;
+        toBytes(dlength,data, START_DATA.length);
+        offset += 4;
+        cdata.getDataPackage(data,offset);
+        offset += dlength;
+        System.arraycopy(END_DATA, 0, data, offset, END_DATA.length);
+        offset += END_DATA.length;
+        return data;
+    }
+    
+    public static byte[] createDataPackage(byte[] data, int doff, int dlength, byte[] buffer, int bufoff) {
+        if ( (buffer.length-bufoff) > getDataPackageLength(dlength) ) {
+            throw new ArrayIndexOutOfBoundsException("Unable to create data package, buffer is too small.");
+        }
+        System.arraycopy(START_DATA, 0, buffer, bufoff, START_DATA.length);
+        toBytes(data.length,buffer, bufoff+START_DATA.length);
+        System.arraycopy(data, doff, buffer, bufoff+START_DATA.length + 4, dlength);
+        System.arraycopy(END_DATA, 0, buffer, bufoff+START_DATA.length + 4 + data.length, END_DATA.length);
+        return buffer;
+    }
+
+    
+    public static int getDataPackageLength(int datalength) {
+        int length = 
+            START_DATA.length + //header length
+            4 + //data length indicator
+            datalength + //actual data length
+            END_DATA.length; //footer length
+        return length;
+
+    }
+    
+    public static byte[] createDataPackage(byte[] data) {
+        int length = getDataPackageLength(data.length);
+        byte[] result = new byte[length];
+        return createDataPackage(data,0,data.length,result,0);
+    }
+
+
+//    public static void fillDataPackage(byte[] data, int doff, int dlength, XByteBuffer buf) {
+//        int pkglen = getDataPackageLength(dlength);
+//        if ( buf.getCapacity() <  pkglen ) buf.expand(pkglen);
+//        createDataPackage(data,doff,dlength,buf.getBytesDirect(),buf.getLength());
+//    }
+
+    /**
+     * Convert four bytes to an int
+     * @param b - the byte array containing the four bytes
+     * @param off - the offset
+     * @return the integer value constructed from the four bytes
+     * @exception java.lang.ArrayIndexOutOfBoundsException
+     */
+    public static int toInt(byte[] b,int off){
+        return ( ( b[off+3]) & 0xFF) +
+            ( ( ( b[off+2]) & 0xFF) << 8) +
+            ( ( ( b[off+1]) & 0xFF) << 16) +
+            ( ( ( b[off+0]) & 0xFF) << 24);
+    }
+
+    /**
+     * Convert eight bytes to a long
+     * @param b - the byte array containing the four bytes
+     * @param off - the offset
+     * @return the long value constructed from the eight bytes
+     * @exception java.lang.ArrayIndexOutOfBoundsException
+     */
+    public static long toLong(byte[] b,int off){
+        return ( ( (long) b[off+7]) & 0xFF) +
+            ( ( ( (long) b[off+6]) & 0xFF) << 8) +
+            ( ( ( (long) b[off+5]) & 0xFF) << 16) +
+            ( ( ( (long) b[off+4]) & 0xFF) << 24) +
+            ( ( ( (long) b[off+3]) & 0xFF) << 32) +
+            ( ( ( (long) b[off+2]) & 0xFF) << 40) +
+            ( ( ( (long) b[off+1]) & 0xFF) << 48) +
+            ( ( ( (long) b[off+0]) & 0xFF) << 56);
+    }
+
+    
+    /**
+     * Converts a boolean to a 1-byte array
+     * @param bool - the integer
+     * @return - 1-byte array
+     * @deprecated use toBytes(boolean,byte[],int)
+     */
+    @Deprecated
+    public static byte[] toBytes(boolean bool) {
+        byte[] b = new byte[1] ;
+        return toBytes(bool,b,0);
+
+    }
+    
+    public static byte[] toBytes(boolean bool, byte[] data, int offset) {
+        data[offset] = (byte)(bool?1:0);
+        return data;
+    }
+    
+    /**
+     * Converts a byte array entry to boolean
+     * @param b byte array
+     * @param offset within byte array
+     * @return true if byte array entry is non-zero, false otherwise
+     */
+    public static boolean toBoolean(byte[] b, int offset) {
+        return b[offset] != 0;
+    }
+
+    
+    /**
+     * Converts an integer to four bytes
+     * @param n - the integer
+     * @return - four bytes in an array
+     * @deprecated use toBytes(int,byte[],int)
+     */
+    @Deprecated
+    public static byte[] toBytes(int n) {
+        return toBytes(n,new byte[4],0);
+    }
+
+    public static byte[] toBytes(int n,byte[] b, int offset) {
+        b[offset+3] = (byte) (n);
+        n >>>= 8;
+        b[offset+2] = (byte) (n);
+        n >>>= 8;
+        b[offset+1] = (byte) (n);
+        n >>>= 8;
+        b[offset+0] = (byte) (n);
+        return b;
+    }
+
+    /**
+     * Converts an long to eight bytes
+     * @param n - the long
+     * @return - eight bytes in an array
+     * @deprecated use toBytes(long,byte[],int)
+     */
+    @Deprecated
+    public static byte[] toBytes(long n) {
+        return toBytes(n,new byte[8],0);
+    }
+    public static byte[] toBytes(long n, byte[] b, int offset) {
+        b[offset+7] = (byte) (n);
+        n >>>= 8;
+        b[offset+6] = (byte) (n);
+        n >>>= 8;
+        b[offset+5] = (byte) (n);
+        n >>>= 8;
+        b[offset+4] = (byte) (n);
+        n >>>= 8;
+        b[offset+3] = (byte) (n);
+        n >>>= 8;
+        b[offset+2] = (byte) (n);
+        n >>>= 8;
+        b[offset+1] = (byte) (n);
+        n >>>= 8;
+        b[offset+0] = (byte) (n);
+        return b;
+    }
+
+    /**
+     * Similar to a String.IndexOf, but uses pure bytes
+     * @param src - the source bytes to be searched
+     * @param srcOff - offset on the source buffer
+     * @param find - the string to be found within src
+     * @return - the index of the first matching byte. -1 if the find array is not found
+     */
+    public static int firstIndexOf(byte[] src, int srcOff, byte[] find){
+        int result = -1;
+        if (find.length > src.length) return result;
+        if (find.length == 0 || src.length == 0) return result;
+        if (srcOff >= src.length ) throw new java.lang.ArrayIndexOutOfBoundsException();
+        boolean found = false;
+        int srclen = src.length;
+        int findlen = find.length;
+        byte first = find[0];
+        int pos = srcOff;
+        while (!found) {
+            //find the first byte
+            while (pos < srclen){
+                if (first == src[pos])
+                    break;
+                pos++;
+            }
+            if (pos >= srclen)
+                return -1;
+
+            //we found the first character
+            //match the rest of the bytes - they have to match
+            if ( (srclen - pos) < findlen)
+                return -1;
+            //assume it does exist
+            found = true;
+            for (int i = 1; ( (i < findlen) && found); i++)
+                found = found && (find[i] == src[pos + i]);
+            if (found)
+                result = pos;
+            else if ( (srclen - pos) < findlen)
+                return -1; //no more matches possible
+            else
+                pos++;
+        }
+        return result;
+    }
+
+    
+    public static Serializable deserialize(byte[] data) 
+        throws IOException, ClassNotFoundException, ClassCastException {
+        return deserialize(data,0,data.length);
+    }
+    
+    public static Serializable deserialize(byte[] data, int offset, int length)  
+        throws IOException, ClassNotFoundException, ClassCastException {
+        return deserialize(data,offset,length,null);     
+    }
+    
+    private static AtomicInteger invokecount = new AtomicInteger(0);
+    
+    public static Serializable deserialize(byte[] data, int offset, int length, ClassLoader[] cls) 
+        throws IOException, ClassNotFoundException, ClassCastException {
+        invokecount.addAndGet(1);
+        Object message = null;
+        if ( cls == null ) cls = new ClassLoader[0];
+        if (data != null) {
+            InputStream  instream = new ByteArrayInputStream(data,offset,length);
+            ObjectInputStream stream = null;
+            stream = (cls.length>0)? new ReplicationStream(instream,cls):new ObjectInputStream(instream);
+            message = stream.readObject();
+            instream.close();
+            stream.close();
+        }
+        if ( message == null ) {
+            return null;
+        } else if (message instanceof Serializable)
+            return (Serializable) message;
+        else {
+            throw new ClassCastException("Message has the wrong class. It should implement Serializable, instead it is:"+message.getClass().getName());
+        }
+    }
+
+    /**
+     * Serializes a message into cluster data
+     * @param msg ClusterMessage
+     * @return serialized content as byte[] array 
+     * @throws IOException
+     */
+    public static byte[] serialize(Serializable msg) throws IOException {
+        ByteArrayOutputStream outs = new ByteArrayOutputStream();
+        ObjectOutputStream out = new ObjectOutputStream(outs);
+        out.writeObject(msg);
+        out.flush();
+        byte[] data = outs.toByteArray();
+        return data;
+    }
+
+    public void setDiscard(boolean discard) {
+        this.discard = discard;
+    }
+
+    public boolean getDiscard() {
+        return discard;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/AbstractRxTask.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/AbstractRxTask.java
new file mode 100644
index 0000000..281b55c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/AbstractRxTask.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.tribes.transport;
+
+import org.apache.catalina.tribes.io.ListenCallback;
+
+
+/**
+ * @author Filip Hanik
+ * @version $Id: AbstractRxTask.java,v 1.1 2011/06/28 21:08:25 rherrmann Exp $
+ */
+public abstract class AbstractRxTask implements Runnable
+{ 
+    
+    public static final int OPTION_DIRECT_BUFFER = ReceiverBase.OPTION_DIRECT_BUFFER;
+    
+    private ListenCallback callback;
+    private RxTaskPool pool;
+    private boolean doRun = true;
+    private int options;
+    protected boolean useBufferPool = true;
+
+    public AbstractRxTask(ListenCallback callback) {
+        this.callback = callback;
+    }
+
+    public void setTaskPool(RxTaskPool pool) {
+        this.pool = pool;
+    }
+
+    public void setOptions(int options) {
+        this.options = options;
+    }
+
+    public void setCallback(ListenCallback callback) {
+        this.callback = callback;
+    }
+
+    public void setDoRun(boolean doRun) {
+        this.doRun = doRun;
+    }
+
+    public RxTaskPool getTaskPool() {
+        return pool;
+    }
+
+    public int getOptions() {
+        return options;
+    }
+
+    public ListenCallback getCallback() {
+        return callback;
+    }
+
+    public boolean isDoRun() {
+        return doRun;
+    }
+
+    public void close()
+    {
+        doRun = false;
+        notify();
+    }
+    
+    public void setUseBufferPool(boolean usebufpool) {
+        useBufferPool = usebufpool;
+    }
+    
+    public boolean getUseBufferPool() {
+        return useBufferPool;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/AbstractSender.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/AbstractSender.java
new file mode 100644
index 0000000..5424550
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/AbstractSender.java
@@ -0,0 +1,357 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import org.apache.catalina.tribes.Member;
+
+/**
+ * <p>Title: </p>
+ *
+ * <p>Description: </p>
+ *
+ * <p>Company: </p>
+ *
+ * @author not attributable
+ * @version 1.0
+ */
+public abstract class AbstractSender implements DataSender {
+
+    private boolean connected = false;
+    private int rxBufSize = 25188;
+    private int txBufSize = 43800;
+    private int udpRxBufSize = 25188;
+    private int udpTxBufSize = 43800;
+    private boolean directBuffer = false;
+    private int keepAliveCount = -1;
+    private int requestCount = 0;
+    private long connectTime;
+    private long keepAliveTime = -1;
+    private long timeout = 3000;
+    private Member destination;
+    private InetAddress address;
+    private int port;
+    private int maxRetryAttempts = 1;//1 resends
+    private int attempt;
+    private boolean tcpNoDelay = true;
+    private boolean soKeepAlive = false;
+    private boolean ooBInline = true;
+    private boolean soReuseAddress = true;
+    private boolean soLingerOn = false;
+    private int soLingerTime = 3;
+    private int soTrafficClass = 0x04 | 0x08 | 0x010;
+    private boolean throwOnFailedAck = true;
+    private boolean udpBased = false;
+    private int udpPort = -1;
+
+    /**
+     * transfers sender properties from one sender to another
+     * @param from AbstractSender
+     * @param to AbstractSender
+     */
+    public static void transferProperties(AbstractSender from, AbstractSender to) {
+        to.rxBufSize = from.rxBufSize;
+        to.txBufSize = from.txBufSize;
+        to.directBuffer = from.directBuffer;
+        to.keepAliveCount = from.keepAliveCount;
+        to.keepAliveTime = from.keepAliveTime;
+        to.timeout = from.timeout;
+        to.destination = from.destination;
+        to.address = from.address;
+        to.port = from.port;
+        to.maxRetryAttempts = from.maxRetryAttempts;
+        to.tcpNoDelay = from.tcpNoDelay;
+        to.soKeepAlive = from.soKeepAlive;
+        to.ooBInline = from.ooBInline;
+        to.soReuseAddress = from.soReuseAddress;
+        to.soLingerOn = from.soLingerOn;
+        to.soLingerTime = from.soLingerTime;
+        to.soTrafficClass = from.soTrafficClass;
+        to.throwOnFailedAck = from.throwOnFailedAck;
+        to.udpBased = from.udpBased;
+        to.udpPort = from.udpPort;
+    }
+
+
+    public AbstractSender() {
+
+    }
+
+    /**
+     * connect
+     *
+     * @throws IOException
+     * TODO Implement this org.apache.catalina.tribes.transport.DataSender method
+     */
+    public abstract void connect() throws IOException;
+
+    /**
+     * disconnect
+     *
+     * TODO Implement this org.apache.catalina.tribes.transport.DataSender method
+     */
+    public abstract void disconnect();
+
+    /**
+     * keepalive
+     *
+     * @return boolean
+     * TODO Implement this org.apache.catalina.tribes.transport.DataSender method
+     */
+    public boolean keepalive() {
+        boolean disconnect = false;
+        if (isUdpBased()) disconnect = true; //always disconnect UDP, TODO optimize the keepalive handling
+        else if ( keepAliveCount >= 0 && requestCount>keepAliveCount ) disconnect = true;
+        else if ( keepAliveTime >= 0 && (System.currentTimeMillis()-connectTime)>keepAliveTime ) disconnect = true;
+        if ( disconnect ) disconnect();
+        return disconnect;
+    }
+
+    protected void setConnected(boolean connected){
+        this.connected = connected;
+    }
+
+    public boolean isConnected() {
+        return connected;
+    }
+
+    public long getConnectTime() {
+        return connectTime;
+    }
+
+    public Member getDestination() {
+        return destination;
+    }
+
+
+    public int getKeepAliveCount() {
+        return keepAliveCount;
+    }
+
+    public long getKeepAliveTime() {
+        return keepAliveTime;
+    }
+
+    public int getRequestCount() {
+        return requestCount;
+    }
+
+    public int getRxBufSize() {
+        return rxBufSize;
+    }
+
+    public long getTimeout() {
+        return timeout;
+    }
+
+    public int getTxBufSize() {
+        return txBufSize;
+    }
+
+    public InetAddress getAddress() {
+        return address;
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    public int getMaxRetryAttempts() {
+        return maxRetryAttempts;
+    }
+
+    public void setDirect(boolean direct) {
+        setDirectBuffer(direct);
+    }
+
+    public void setDirectBuffer(boolean directBuffer) {
+        this.directBuffer = directBuffer;
+    }
+
+    public boolean getDirect() {
+        return getDirectBuffer();
+    }
+
+    public boolean getDirectBuffer() {
+        return this.directBuffer;
+    }
+
+    public int getAttempt() {
+        return attempt;
+    }
+
+    public boolean getTcpNoDelay() {
+        return tcpNoDelay;
+    }
+
+    public boolean getSoKeepAlive() {
+        return soKeepAlive;
+    }
+
+    public boolean getOoBInline() {
+        return ooBInline;
+    }
+
+    public boolean getSoReuseAddress() {
+        return soReuseAddress;
+    }
+
+    public boolean getSoLingerOn() {
+        return soLingerOn;
+    }
+
+    public int getSoLingerTime() {
+        return soLingerTime;
+    }
+
+    public int getSoTrafficClass() {
+        return soTrafficClass;
+    }
+
+    public boolean getThrowOnFailedAck() {
+        return throwOnFailedAck;
+    }
+
+    public void setKeepAliveCount(int keepAliveCount) {
+        this.keepAliveCount = keepAliveCount;
+    }
+
+    public void setKeepAliveTime(long keepAliveTime) {
+        this.keepAliveTime = keepAliveTime;
+    }
+
+    public void setRequestCount(int requestCount) {
+        this.requestCount = requestCount;
+    }
+
+    public void setRxBufSize(int rxBufSize) {
+        this.rxBufSize = rxBufSize;
+    }
+
+    public void setTimeout(long timeout) {
+        this.timeout = timeout;
+    }
+
+    public void setTxBufSize(int txBufSize) {
+        this.txBufSize = txBufSize;
+    }
+
+    public void setConnectTime(long connectTime) {
+        this.connectTime = connectTime;
+    }
+
+    public void setMaxRetryAttempts(int maxRetryAttempts) {
+        this.maxRetryAttempts = maxRetryAttempts;
+    }
+
+    public void setAttempt(int attempt) {
+        this.attempt = attempt;
+    }
+
+    public void setTcpNoDelay(boolean tcpNoDelay) {
+        this.tcpNoDelay = tcpNoDelay;
+    }
+
+    public void setSoKeepAlive(boolean soKeepAlive) {
+        this.soKeepAlive = soKeepAlive;
+    }
+
+    public void setOoBInline(boolean ooBInline) {
+        this.ooBInline = ooBInline;
+    }
+
+    public void setSoReuseAddress(boolean soReuseAddress) {
+        this.soReuseAddress = soReuseAddress;
+    }
+
+    public void setSoLingerOn(boolean soLingerOn) {
+        this.soLingerOn = soLingerOn;
+    }
+
+    public void setSoLingerTime(int soLingerTime) {
+        this.soLingerTime = soLingerTime;
+    }
+
+    public void setSoTrafficClass(int soTrafficClass) {
+        this.soTrafficClass = soTrafficClass;
+    }
+
+    public void setThrowOnFailedAck(boolean throwOnFailedAck) {
+        this.throwOnFailedAck = throwOnFailedAck;
+    }
+
+    public void setDestination(Member destination) throws UnknownHostException {
+        this.destination = destination;
+        this.address = InetAddress.getByAddress(destination.getHost());
+        this.port = destination.getPort();
+        this.udpPort = destination.getUdpPort();
+
+    }
+
+    public void setPort(int port) {
+        this.port = port;
+    }
+
+    public void setAddress(InetAddress address) {
+        this.address = address;
+    }
+
+
+    public boolean isUdpBased() {
+        return udpBased;
+    }
+
+
+    public void setUdpBased(boolean udpBased) {
+        this.udpBased = udpBased;
+    }
+
+
+    public int getUdpPort() {
+        return udpPort;
+    }
+
+
+    public void setUdpPort(int udpPort) {
+        this.udpPort = udpPort;
+    }
+
+
+    public int getUdpRxBufSize() {
+        return udpRxBufSize;
+    }
+
+
+    public void setUdpRxBufSize(int udpRxBufSize) {
+        this.udpRxBufSize = udpRxBufSize;
+    }
+
+
+    public int getUdpTxBufSize() {
+        return udpTxBufSize;
+    }
+
+
+    public void setUdpTxBufSize(int udpTxBufSize) {
+        this.udpTxBufSize = udpTxBufSize;
+    }
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/Constants.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/Constants.java
new file mode 100644
index 0000000..8ae8921
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/Constants.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.transport;
+
+import org.apache.catalina.tribes.io.XByteBuffer;
+
+/**
+ * Manifest constants for the <code>org.apache.catalina.tribes.transport</code>
+ * package.
+ * @author Filip Hanik
+ * @author Peter Rossbach
+ * @version $Id: Constants.java,v 1.1 2011/06/28 21:08:25 rherrmann Exp $
+ */
+
+public class Constants {
+
+    public static final String Package = "org.apache.catalina.tribes.transport";
+    
+    /*
+     * Do not change any of these values!
+     */
+    public static final byte[] ACK_DATA = new byte[] {6, 2, 3};
+    public static final byte[] FAIL_ACK_DATA = new byte[] {11, 0, 5};
+    public static final byte[] ACK_COMMAND = XByteBuffer.createDataPackage(ACK_DATA);
+    public static final byte[] FAIL_ACK_COMMAND = XByteBuffer.createDataPackage(FAIL_ACK_DATA);
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/DataSender.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/DataSender.java
new file mode 100644
index 0000000..36d1514
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/DataSender.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.transport;
+
+import java.io.IOException;
+
+/**
+ * <p>Title: </p>
+ *
+ * <p>Description: </p>
+ *
+ * <p>Company: </p>
+ *
+ * @author not attributable
+ * @version 1.0
+ */
+public interface DataSender {
+    public void connect() throws IOException;
+    public void disconnect();
+    public boolean isConnected();
+    public void setRxBufSize(int size);
+    public void setTxBufSize(int size);
+    public boolean keepalive();
+    public void setTimeout(long timeout);
+    public void setKeepAliveCount(int maxRequests);
+    public void setKeepAliveTime(long keepAliveTimeInMs);
+    public int getRequestCount();
+    public long getConnectTime();
+    
+    
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/LocalStrings.properties
new file mode 100644
index 0000000..ea2fe5f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/LocalStrings.properties
@@ -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.
+
+IDataSender.ack.eof=EOF reached at local port [{0}:{1,number,integer}]
+IDataSender.ack.receive=Got ACK at local port [{0}:{1,number,integer}]
+IDataSender.ack.missing=Unable to read acknowledgement from [{0}:{1,number,integer}] in {2,number,integer} ms. Disconnecting socket, and trying again.
+IDataSender.ack.read=Read wait ack char '{2}' [{0}:{1,number,integer}]
+IDataSender.ack.start=Waiting for ACK message [{0}:{1,number,integer}]
+IDataSender.ack.wrong=Missing correct ACK after 10 bytes read at local port [{0}:{1,number,integer}]
+IDataSender.closeSocket=Sender close socket to [{0}:{1,number,integer}] (close count {2,number,integer})
+IDataSender.connect=Sender connect to [{0}:{1,number,integer}] (connect count {2,number,integer})
+IDataSender.create=Create sender [{0}:{1,number,integer}]
+IDataSender.disconnect=Sender disconnect from [{0}:{1,number,integer}] (disconnect count {2,number,integer})
+IDataSender.message.disconnect=Message transfered: Sender can't disconnect from [{0}:{1,number,integer}]
+IDataSender.message.create=Message transfered: Sender can't create current socket [{0}:{1,number,integer}]
+IDataSender.openSocket=Sender open socket to [{0}:{1,number,integer}] (open count {2,number,integer})
+IDataSender.openSocket.failure=Open sender socket [{0}:{1,number,integer}] failure! (open failure count {2,number,integer})
+IDataSender.send.again=Send data again to [{0}:{1,number,integer}]
+IDataSender.send.crash=Send message crashed [{0}:{1,number,integer}] type=[{2}], id=[{3}]
+IDataSender.send.message=Send message to [{0}:{1,number,integer}] id=[{2}] size={3,number,integer}
+IDataSender.send.lost=Message lost: [{0}:{1,number,integer}] type=[{2}], id=[{3}]
+IDataSender.senderModes.Configured=Configured a data replication sender for mode {0}
+IDataSender.senderModes.Instantiate=Can't instantiate a data replication sender of class {0}
+IDataSender.senderModes.Missing=Can't configure a data replication sender for mode {0}
+IDataSender.senderModes.Resources=Can't load data replication sender mapping list
+IDataSender.stats=Send stats from [{0}:{1,number,integer}], Nr of bytes sent={2,number,integer} over {3} = {4,number,integer} bytes/request, processing time {5,number,integer} msec, avg processing time {6,number,integer} msec
+PooledSender.senderDisconnectFail=Failed to disconnect sender 
+ReplicationValve.crossContext.add=add Cross Context session replication container to replicationValve threadlocal
+ReplicationValve.crossContext.registerSession=register Cross context session id={0} from context {1}
+ReplicationValve.crossContext.remove=remove Cross Context session replication container from replicationValve threadlocal
+ReplicationValve.crossContext.sendDelta=send Cross Context session delta from context {0}.
+ReplicationValve.filter.loading=Loading request filters={0}
+ReplicationValve.filter.token=Request filter={0}
+ReplicationValve.filter.token.failure=Unable to compile filter={0}
+ReplicationValve.invoke.uri=Invoking replication request on {0}
+ReplicationValve.nocluster=No cluster configured for this request.
+ReplicationValve.resetDeltaRequest=Cluster is standalone: reset Session Request Delta at context {0}
+ReplicationValve.send.failure=Unable to perform replication request.
+ReplicationValve.send.invalid.failure=Unable to send session [id={0}] invalid message over cluster.
+ReplicationValve.session.found=Context {0}: Found session {1} but it isn't a ClusterSession.
+ReplicationValve.session.indicator=Context {0}: Primarity of session {0} in request attribute {1} is {2}.
+ReplicationValve.session.invalid=Context {0}: Requested session {1} is invalid, removed or not replicated at this node.
+ReplicationValve.stats=Average request time= {0} ms for Cluster overhead time={1} ms for {2} requests {3} filter requests {4} send requests {5} cross context requests (Request={6} ms Cluster={7} ms).
+SimpleTcpCluster.event.log=Cluster receive listener event {0} with data {1}
+SimpleTcpCluster.getProperty=get property {0}
+SimpleTcpCluster.setProperty=set property {0}: {1} old value {2}
+SimpleTcpCluster.default.addClusterListener=Add Default ClusterListener at cluster {0}
+SimpleTcpCluster.default.addClusterValves=Add Default ClusterValves at cluster {0}
+SimpleTcpCluster.default.addClusterReceiver=Add Default ClusterReceiver at cluster {0}
+SimpleTcpCluster.default.addClusterSender=Add Default ClusterSender at cluster {0}
+SimpleTcpCluster.default.addMembershipService=Add Default Membership Service at cluster {0}
+SimpleTcpCluster.log.receive=RECEIVE {0,date}:{0,time} {1,number} {2}:{3,number,integer} {4} {5}
+SimpleTcpCluster.log.send=SEND {0,date}:{0,time} {1,number} {2}:{3,number,integer} {4} 
+SimpleTcpCluster.log.send.all=SEND {0,date}:{0,time} {1,number} - {2}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/LocalStrings_es.properties
new file mode 100644
index 0000000..1c49e49
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/LocalStrings_es.properties
@@ -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.
+
+AsyncSocketSender.create.thread = Crear remitente [{0}\:{1,number,integer}] hilo en cola a r\u00E9plica de tcp en segundo plano
+AsyncSocketSender.queue.message = Poner en cola mensaje para [{0}\:{1,number,integer}] id\=[{2}] medida\={3}
+AsyncSocketSender.send.error = No puedo enviar sesi\u00F3n de forma as\u00EDncrona con id\=[{0}] - se ignora el mensaje.
+AsyncSocketSender.queue.empty = \u00A1La cola en remitente [{0}\:{1,number,integer}] devolvi\u00F3 elemento nulo\!
+cluster.mbean.register.already = \u00A1MBean {0} ya registrado\!
+IDataSender.ack.eof = EOF alcanzado en puerto local [{0}\:{1,number,integer}]
+IDataSender.ack.receive = Obtenido ACK en puerto local [{0}\:{1,number,integer}]
+IDataSender.ack.missing = No puedo leer reconocimiento desde [{0}\:{1,number,integer}] en {2,number,integer} ms. Desconectando conector e intentando otra vez.
+IDataSender.ack.read = Car\u00E1cter de espera de lectura ack '{2}' [{0}\:{1,number,integer}]
+IDataSender.ack.start = Esperando por mensaje ACK [{0}\:{1,number,integer}]
+IDataSender.ack.wrong = Falta ACK correcto tras 10 bytes le\u00EDdos en puerto local [{0}\:{1,number,integer}]
+IDataSender.closeSocket = El remitente cerr\u00F3 el conector con [{0}\:{1,number,integer}] (contador de cierre {2,number,integer})
+IDataSender.connect = Remitente conectado con [{0}\:{1,number,integer}] (contador de conexi\u00F3n {2,number,integer})
+IDataSender.create = Crear remitente [{0}\:{1,number,integer}]
+IDataSender.disconnect = Remitente desconectado de [{0}\:{1,number,integer}] (contador de desconexi\u00F3n {2,number,integer})
+IDataSender.message.disconnect = Mensaje transferido\: El remitente no se pude desconectar de [{0}\:{1,number,integer}]
+IDataSender.message.create = Mensaje transferido\: El remitente no puede crear conector en curso [{0}\:{1,number,integer}]
+IDataSender.openSocket = Remitente abri\u00F3 conector con [{0}\:{1,number,integer}] (contador de apertura {2,number,integer})
+IDataSender.openSocket.failure = \u00A1No pude abrir conector de remitente [{0}\:{1,number,integer}]\! (contador de fallo de apertura {2,number,integer})
+IDataSender.send.again = Enviar datos de nuevo a [{0}\:{1,number,integer}]
+IDataSender.send.crash = Enviar mensaje se rompi\u00F3 [{0}\:{1,number,integer}] tipo\=[{2}], id\=[{3}]
+IDataSender.send.message = Enviar mensaje a [{0}\:{1,number,integer}] id\=[{2}] medida\={3,number,integer}
+IDataSender.send.lost = Mensaje perdido\: [{0}\:{1,number,integer}] tipo\=[{2}], id\=[{3}]
+IDataSender.senderModes.Configured = Configurado un remitente de r\u00E9plica de datos para el modo {0}
+IDataSender.senderModes.Instantiate = No puedo instanciar remitente de r\u00E9plica de datos de clase {0}
+IDataSender.senderModes.Missing = No puedo configurar remitente de r\u00E9plica de datos para modo {0}
+IDataSender.senderModes.Resources = No puedo cargar lista de mapeo de remitente de r\u00E9plica de datos de clase {0}
+IDataSender.stats = Estados de Env\u00EDo desde [{0}\:{1,number,integer}], Nr de bytes enviado\={2,number,integer} sobre {3} \= {4,number,integer} bytes/requerimiento, tiempo de proceso {5,number,integer} mseg, tiempo medio de proceso {6,number,integer} mseg
+PoolSocketSender.senderQueue.sender.failed = PoolSocketSender fall\u00F3 el crear nuevo remitente para [{0}\:{1,number,integer}]
+PoolSocketSender.noMoreSender = No hay disponible remitente de conector para cliente [{0}\:{1,number,integer}] \u00BFha desaparecido?
+ReplicationTransmitter.getProperty = obtener propiedad {0}
+ReplicationTransmitter.setProperty = poner propiedad {0}\: {1} valor viejo {2}
+ReplicationTransmitter.started = Iniciar ClusterSender en cl\u00FAster {0} con nombre {1}
+ReplicationTransmitter.stopped = Parado ClusterSender en cl\u00FAster {0} con nombre {1}
+ReplicationValve.crossContext.add = a\u00F1adir contenedor de r\u00E9plica de sesi\u00F3n de Contexto Cruzado con hilo local de replicationValve
+ReplicationValve.crossContext.registerSession = registrado sesi\u00F3n de contexto Cruzado con id\={0} desde contexto {1}
+ReplicationValve.crossContext.remove = quitar contenedor de r\u00E9plica de sesi\u00F3n de Contexto Cruzado con hilo local de replicationValve
+ReplicationValve.crossContext.sendDelta = enviar sesi\u00F3n delta de Contexto Cruzado desde contexto {0}.
+ReplicationValve.filter.loading = Cargando filtros de requerimiento\={0}
+ReplicationValve.filter.token = Filtro de requerimiento\={0}
+ReplicationValve.filter.token.failure = No puedo compilar filtro\={0}
+ReplicationValve.invoke.uri = Invocando requerimiento de r\u00E9plica en {0}
+ReplicationValve.nocluster = No hay cl\u00FAster configurado para este requerimiento.
+ReplicationValve.resetDeltaRequest = El Cl\u00FAster es aut\u00F3nomo\: limpiado Delta de Requerimiento de Sesi\u00F3n en contexto {0}
+ReplicationValve.send.failure = No puedo realizar requerimiento de r\u00E9plica.
+ReplicationValve.send.invalid.failure = El Cl\u00FAster es aut\u00F3nomo\: limpiado Delta de Requerimiento de Sesi\u00F3n en contexto {0}
+ReplicationValve.session.found = Contexto {0}\: Hallada sesi\u00F3n {1} pero no es una ClusterSession.
+ReplicationValve.session.indicator = Contexto {0}\: La primac\u00EDa de la sesi\u00F3n {0} en atributo de requerimiento {1} es {2}.
+ReplicationValve.session.invalid = Contexto {0}\: La sesi\u00F3n requerida {1} es inv\u00E1lida, quitada o no replicada en este nodo.
+ReplicationValve.stats = Tiempo de requerimiento medio\= {0} ms para tiempo de sobrecarga de Cl\u00FAster\={1} ms para {2} requerimientos {3} requerimientos de filtro {4} requerimientos de env\u00EDo {5} requerimientos de contexto cruzado (Requerimiento\={6} ms Cl\u00FAster\={7} ms).
+SimpleTcpCluster.event.log = Cl\u00FAster recibi\u00F3 evento de oyente {0} con datos {1}
+SimpleTcpCluster.getProperty = obtener propiedad {0}
+SimpleTcpCluster.setProperty = poner propiedad {0}\: {1} valor viejo {2}
+SimpleTcpCluster.default.addClusterListener = A\u00F1adir ClusterListener por defecto en cl\u00FAster {0}
+SimpleTcpCluster.default.addClusterValves = A\u00F1adir ClusterValves por defecto en cl\u00FAster {0}
+SimpleTcpCluster.default.addClusterReceiver = A\u00F1adir ClusterReceiver por defecto en cl\u00FAster {0}
+SimpleTcpCluster.default.addClusterSender = A\u00F1adir ClusterSender por defecto en cl\u00FAster {0}
+SimpleTcpCluster.default.addMembershipService = A\u00F1adir Servicio de Miembro por defecto en cl\u00FAster {0}
+SimpleTcpCluster.log.receive = RECIBIDO {0,date}\:{0,time} {1,number} {2}\:{3,number,integer} {4} {5}
+SimpleTcpCluster.log.send = ENVIADO {0,date}\:{0,time} {1,number} {2}\:{3,number,integer} {4} 
+SimpleTcpCluster.log.send.all = ENVIADO {0,date}\:{0,time} {1,number} {2}\:{3,number,integer} {4} 
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/MultiPointSender.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/MultiPointSender.java
new file mode 100644
index 0000000..d2e07f5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/MultiPointSender.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.catalina.tribes.transport;
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+
+/**
+ * @author Filip Hanik
+ * @version $Id: MultiPointSender.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ * @since 5.5.16
+ */
+
+public interface MultiPointSender extends DataSender
+{
+    public void sendMessage(Member[] destination, ChannelMessage data) throws ChannelException;
+    public void setRxBufSize(int size);
+    public void setTxBufSize(int size);
+    public void setMaxRetryAttempts(int attempts);
+    public void setDirectBuffer(boolean directBuf);
+    public void add(Member member);
+    public void remove(Member member);
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/PooledSender.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/PooledSender.java
new file mode 100644
index 0000000..fef6c19
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/PooledSender.java
@@ -0,0 +1,235 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.util.StringManager;
+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 abstract class PooledSender extends AbstractSender implements MultiPointSender {
+    
+    private static final Log log = LogFactory.getLog(PooledSender.class);
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+    
+    private SenderQueue queue = null;
+    private int poolSize = 25;
+    public PooledSender() {
+        queue = new SenderQueue(this,poolSize);
+    }
+    
+    public abstract DataSender getNewDataSender();
+    
+    public DataSender getSender() {
+        return queue.getSender(getTimeout());
+    }
+    
+    public void returnSender(DataSender sender) {
+        sender.keepalive();
+        queue.returnSender(sender);
+    }
+    
+    @Override
+    public synchronized void connect() throws IOException {
+        //do nothing, happens in the socket sender itself
+        queue.open();
+        setConnected(true);
+    }
+    
+    @Override
+    public synchronized void disconnect() {
+        queue.close();
+        setConnected(false);
+    }
+    
+    
+    public int getInPoolSize() {
+        return queue.getInPoolSize();
+    }
+
+    public int getInUsePoolSize() {
+        return queue.getInUsePoolSize();
+    }
+
+
+    public void setPoolSize(int poolSize) {
+        this.poolSize = poolSize;
+        queue.setLimit(poolSize);
+    }
+
+    public int getPoolSize() {
+        return poolSize;
+    }
+
+    @Override
+    public boolean keepalive() {
+        //do nothing, the pool checks on every return
+        return (queue==null)?false:queue.checkIdleKeepAlive();
+    }
+
+    @Override
+    public void add(Member member) {
+        // no op, senders created upon demands
+    }
+
+    @Override
+    public void remove(Member member) {
+        //no op for now, should not cancel out any keys
+        //can create serious sync issues
+        //all TCP connections are cleared out through keepalive
+        //and if remote node disappears
+    }
+    //  ----------------------------------------------------- Inner Class
+
+    private static class SenderQueue {
+        private int limit = 25;
+
+        PooledSender parent = null;
+
+        private List<DataSender> notinuse = null;
+
+        private List<DataSender> inuse = null;
+
+        private boolean isOpen = true;
+
+        public SenderQueue(PooledSender parent, int limit) {
+            this.limit = limit;
+            this.parent = parent;
+            notinuse = new java.util.LinkedList<DataSender>();
+            inuse = new java.util.LinkedList<DataSender>();
+        }
+
+        /**
+         * @return Returns the limit.
+         */
+        public int getLimit() {
+            return limit;
+        }
+        /**
+         * @param limit The limit to set.
+         */
+        public void setLimit(int limit) {
+            this.limit = limit;
+        }
+        /**
+         * @return
+         */
+        public int getInUsePoolSize() {
+            return inuse.size();
+        }
+
+        /**
+         * @return
+         */
+        public int getInPoolSize() {
+            return notinuse.size();
+        }
+        
+        public synchronized boolean checkIdleKeepAlive() {
+            DataSender[] list = new DataSender[notinuse.size()];
+            notinuse.toArray(list);
+            boolean result = false;
+            for (int i=0; i<list.length; i++) {
+                result = result | list[i].keepalive();
+            }
+            return result;
+        }
+
+        public synchronized DataSender getSender(long timeout) {
+            long start = System.currentTimeMillis();
+            while ( true ) {
+                if (!isOpen)throw new IllegalStateException("Queue is closed");
+                DataSender sender = null;
+                if (notinuse.size() == 0 && inuse.size() < limit) {
+                    sender = parent.getNewDataSender();
+                } else if (notinuse.size() > 0) {
+                    sender = notinuse.remove(0);
+                }
+                if (sender != null) {
+                    inuse.add(sender);
+                    return sender;
+                }//end if
+                long delta = System.currentTimeMillis() - start;
+                if ( delta > timeout && timeout>0) return null;
+                else {
+                    try {
+                        wait(Math.max(timeout - delta,1));
+                    }catch (InterruptedException x){}
+                }//end if
+            }
+        }
+
+        public synchronized void returnSender(DataSender sender) {
+            if ( !isOpen) {
+                sender.disconnect();
+                return;
+            }
+            //to do
+            inuse.remove(sender);
+            //just in case the limit has changed
+            if ( notinuse.size() < this.getLimit() ) notinuse.add(sender);
+            else
+                try {
+                    sender.disconnect();
+                } catch (Exception e) {
+                    if (log.isDebugEnabled()) {
+                        log.debug(sm.getString(
+                                "PooledSender.senderDisconnectFail"), e);
+                    }
+                }
+            notify();
+        }
+
+        public synchronized void close() {
+            isOpen = false;
+            Object[] unused = notinuse.toArray();
+            Object[] used = inuse.toArray();
+            for (int i = 0; i < unused.length; i++) {
+                DataSender sender = (DataSender) unused[i];
+                sender.disconnect();
+            }//for
+            for (int i = 0; i < used.length; i++) {
+                DataSender sender = (DataSender) used[i];
+                sender.disconnect();
+            }//for
+            notinuse.clear();
+            inuse.clear();
+            notify();
+
+
+        }
+
+        public synchronized void open() {
+            isOpen = true;
+            notify();
+        }
+    }
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/ReceiverBase.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/ReceiverBase.java
new file mode 100644
index 0000000..5be2921
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/ReceiverBase.java
@@ -0,0 +1,588 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.ChannelReceiver;
+import org.apache.catalina.tribes.MessageListener;
+import org.apache.catalina.tribes.io.ListenCallback;
+import org.apache.catalina.tribes.util.ExecutorFactory;
+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 abstract class ReceiverBase implements ChannelReceiver, ListenCallback, RxTaskPool.TaskCreator {
+
+    public static final int OPTION_DIRECT_BUFFER = 0x0004;
+
+    private static final Log log = LogFactory.getLog(ReceiverBase.class);
+
+    private MessageListener listener;
+    private String host = "auto";
+    private InetAddress bind;
+    private int port  = 4000;
+    private int udpPort = -1;
+    private int securePort = -1;
+    private int rxBufSize = 43800;
+    private int txBufSize = 25188;
+    private int udpRxBufSize = 43800;
+    private int udpTxBufSize = 25188;
+
+    private boolean listen = false;
+    private RxTaskPool pool;
+    private boolean direct = true;
+    private long tcpSelectorTimeout = 5000;
+    //how many times to search for an available socket
+    private int autoBind = 100;
+    private int maxThreads = 15;
+    private int minThreads = 6;
+    private int maxTasks = 100;
+    private int minTasks = 10;
+    private boolean tcpNoDelay = true;
+    private boolean soKeepAlive = false;
+    private boolean ooBInline = true;
+    private boolean soReuseAddress = true;
+    private boolean soLingerOn = true;
+    private int soLingerTime = 3;
+    private int soTrafficClass = 0x04 | 0x08 | 0x010;
+    private int timeout = 3000; //3 seconds
+    private boolean useBufferPool = true;
+    private boolean daemon = true;
+    private long maxIdleTime = 60000;
+    
+    private ExecutorService executor;
+
+
+    public ReceiverBase() {
+    }
+
+    public void start() throws IOException {
+        if ( executor == null ) {
+            //executor = new ThreadPoolExecutor(minThreads,maxThreads,60,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());
+            TaskThreadFactory tf = new TaskThreadFactory("Tribes-Task-Receiver-");
+            executor = ExecutorFactory.newThreadPool(minThreads, maxThreads, maxIdleTime, TimeUnit.MILLISECONDS, tf);
+        }
+    }
+
+    public void stop() {
+        if ( executor != null ) executor.shutdownNow();//ignore left overs
+        executor = null;
+    }
+
+    /**
+     * getMessageListener
+     *
+     * @return MessageListener
+     * TODO Implement this org.apache.catalina.tribes.ChannelReceiver method
+     */
+    public MessageListener getMessageListener() {
+        return listener;
+    }
+
+    /**
+     *
+     * @return The port
+     * TODO Implement this org.apache.catalina.tribes.ChannelReceiver method
+     */
+    public int getPort() {
+        return port;
+    }
+
+    public int getRxBufSize() {
+        return rxBufSize;
+    }
+
+    public int getTxBufSize() {
+        return txBufSize;
+    }
+
+    /**
+     * @deprecated use getMinThreads()/getMaxThreads()
+     * @return int
+     */
+    @Deprecated
+    public int getTcpThreadCount() {
+        return getMaxThreads();
+    }
+
+    /**
+     * setMessageListener
+     *
+     * @param listener MessageListener
+     * TODO Implement this org.apache.catalina.tribes.ChannelReceiver method
+     */
+    public void setMessageListener(MessageListener listener) {
+        this.listener = listener;
+    }
+
+    /**
+     * @deprecated use setPort
+     * @param tcpListenPort int
+     */
+    @Deprecated
+    public void setTcpListenPort(int tcpListenPort) {
+        setPort(tcpListenPort);
+    }
+
+    /**
+     * @deprecated use setAddress
+     * @param tcpListenHost String
+     */
+    @Deprecated
+    public void setTcpListenAddress(String tcpListenHost) {
+        setAddress(tcpListenHost);
+    }
+
+    public void setRxBufSize(int rxBufSize) {
+        this.rxBufSize = rxBufSize;
+    }
+
+    public void setTxBufSize(int txBufSize) {
+        this.txBufSize = txBufSize;
+    }
+
+    /**
+     * @deprecated use setMaxThreads/setMinThreads
+     * @param tcpThreadCount int
+     */
+    @Deprecated
+    public void setTcpThreadCount(int tcpThreadCount) {
+        setMaxThreads(tcpThreadCount);
+        setMinThreads(tcpThreadCount);
+    }
+
+    /**
+     * @return Returns the bind.
+     */
+    public InetAddress getBind() {
+        if (bind == null) {
+            try {
+                if ("auto".equals(host)) {
+                    host = java.net.InetAddress.getLocalHost().getHostAddress();
+                }
+                if (log.isDebugEnabled())
+                    log.debug("Starting replication listener on address:"+ host);
+                bind = java.net.InetAddress.getByName(host);
+            } catch (IOException ioe) {
+                log.error("Failed bind replication listener on address:"+ host, ioe);
+            }
+        }
+        return bind;
+    }
+
+    /**
+     * recursive bind to find the next available port
+     * @param socket ServerSocket
+     * @param portstart int
+     * @param retries int
+     * @return int
+     * @throws IOException
+     */
+    protected int bind(ServerSocket socket, int portstart, int retries) throws IOException {
+        InetSocketAddress addr = null;
+        while ( retries > 0 ) {
+            try {
+                addr = new InetSocketAddress(getBind(), portstart);
+                socket.bind(addr);
+                setPort(portstart);
+                log.info("Receiver Server Socket bound to:"+addr);
+                return 0;
+            }catch ( IOException x) {
+                retries--;
+                if ( retries <= 0 ) {
+                    log.info("Unable to bind server socket to:"+addr+" throwing error.");
+                    throw x;
+                }
+                portstart++;
+                try {Thread.sleep(25);}catch( InterruptedException ti){Thread.interrupted();}
+                retries = bind(socket,portstart,retries);
+            }
+        }
+        return retries;
+    }
+
+    /**
+     * Same as bind() except it does it for the UDP port
+     * @param socket
+     * @param portstart
+     * @param retries
+     * @return int
+     * @throws IOException
+     */
+    protected int bindUdp(DatagramSocket socket, int portstart, int retries) throws IOException {
+        InetSocketAddress addr = null;
+        while ( retries > 0 ) {
+            try {
+                addr = new InetSocketAddress(getBind(), portstart);
+                socket.bind(addr);
+                setUdpPort(portstart);
+                log.info("UDP Receiver Server Socket bound to:"+addr);
+                return 0;
+            }catch ( IOException x) {
+                retries--;
+                if ( retries <= 0 ) {
+                    log.info("Unable to bind UDP socket to:"+addr+" throwing error.");
+                    throw x;
+                }
+                portstart++;
+                try {Thread.sleep(25);}catch( InterruptedException ti){Thread.interrupted();}
+                retries = bindUdp(socket,portstart,retries);
+            }
+        }
+        return retries;
+    }
+
+
+    public void messageDataReceived(ChannelMessage data) {
+        if ( this.listener != null ) {
+            if ( listener.accept(data) ) listener.messageReceived(data);
+        }
+    }
+
+    public int getWorkerThreadOptions() {
+        int options = 0;
+        if ( getDirect() ) options = options | OPTION_DIRECT_BUFFER;
+        return options;
+    }
+
+
+    /**
+     * @param bind The bind to set.
+     */
+    public void setBind(java.net.InetAddress bind) {
+        this.bind = bind;
+    }
+
+    /**
+     * @deprecated use getPort
+     * @return int
+     */
+    @Deprecated
+    public int getTcpListenPort() {
+        return getPort();
+    }
+
+
+    public boolean getDirect() {
+        return direct;
+    }
+
+
+
+    public void setDirect(boolean direct) {
+        this.direct = direct;
+    }
+
+
+    public String getAddress() {
+        getBind();
+        return this.host;
+    }
+
+    public String getHost() {
+        return getAddress();
+    }
+
+    public long getSelectorTimeout() {
+        return tcpSelectorTimeout;
+    }
+    /**
+     * @deprecated use getSelectorTimeout
+     * @return long
+     */
+    @Deprecated
+    public long getTcpSelectorTimeout() {
+        return getSelectorTimeout();
+    }
+
+    public boolean doListen() {
+        return listen;
+    }
+
+    public MessageListener getListener() {
+        return listener;
+    }
+
+    public RxTaskPool getTaskPool() {
+        return pool;
+    }
+
+    /**
+     * @deprecated use getAddress
+     * @return String
+     */
+    @Deprecated
+    public String getTcpListenAddress() {
+        return getAddress();
+    }
+
+    public int getAutoBind() {
+        return autoBind;
+    }
+
+    public int getMaxThreads() {
+        return maxThreads;
+    }
+
+    public int getMinThreads() {
+        return minThreads;
+    }
+
+    public boolean getTcpNoDelay() {
+        return tcpNoDelay;
+    }
+
+    public boolean getSoKeepAlive() {
+        return soKeepAlive;
+    }
+
+    public boolean getOoBInline() {
+        return ooBInline;
+    }
+
+
+    public boolean getSoLingerOn() {
+        return soLingerOn;
+    }
+
+    public int getSoLingerTime() {
+        return soLingerTime;
+    }
+
+    public boolean getSoReuseAddress() {
+        return soReuseAddress;
+    }
+
+    public int getSoTrafficClass() {
+        return soTrafficClass;
+    }
+
+    public int getTimeout() {
+        return timeout;
+    }
+
+    public boolean getUseBufferPool() {
+        return useBufferPool;
+    }
+
+    public int getSecurePort() {
+        return securePort;
+    }
+
+    public int getMinTasks() {
+        return minTasks;
+    }
+
+    public int getMaxTasks() {
+        return maxTasks;
+    }
+
+    public ExecutorService getExecutor() {
+        return executor;
+    }
+
+    public boolean isListening() {
+        return listen;
+    }
+
+    /**
+     * @deprecated use setSelectorTimeout
+     * @param selTimeout long
+     */
+    @Deprecated
+    public void setTcpSelectorTimeout(long selTimeout) {
+        setSelectorTimeout(selTimeout);
+    }
+
+    public void setSelectorTimeout(long selTimeout) {
+        tcpSelectorTimeout = selTimeout;
+    }
+
+    public void setListen(boolean doListen) {
+        this.listen = doListen;
+    }
+
+
+    public void setAddress(String host) {
+        this.host = host;
+    }
+    public void setHost(String host) {
+        setAddress(host);
+    }
+
+    public void setListener(MessageListener listener) {
+        this.listener = listener;
+    }
+
+    public void setPool(RxTaskPool pool) {
+        this.pool = pool;
+    }
+
+    public void setPort(int port) {
+        this.port = port;
+    }
+
+    public void setAutoBind(int autoBind) {
+        this.autoBind = autoBind;
+        if ( this.autoBind <= 0 ) this.autoBind = 1;
+    }
+
+    public void setMaxThreads(int maxThreads) {
+        this.maxThreads = maxThreads;
+    }
+
+    public void setMinThreads(int minThreads) {
+        this.minThreads = minThreads;
+    }
+
+    public void setTcpNoDelay(boolean tcpNoDelay) {
+        this.tcpNoDelay = tcpNoDelay;
+    }
+
+    public void setSoKeepAlive(boolean soKeepAlive) {
+        this.soKeepAlive = soKeepAlive;
+    }
+
+    public void setOoBInline(boolean ooBInline) {
+        this.ooBInline = ooBInline;
+    }
+
+
+    public void setSoLingerOn(boolean soLingerOn) {
+        this.soLingerOn = soLingerOn;
+    }
+
+    public void setSoLingerTime(int soLingerTime) {
+        this.soLingerTime = soLingerTime;
+    }
+
+    public void setSoReuseAddress(boolean soReuseAddress) {
+        this.soReuseAddress = soReuseAddress;
+    }
+
+    public void setSoTrafficClass(int soTrafficClass) {
+        this.soTrafficClass = soTrafficClass;
+    }
+
+    public void setTimeout(int timeout) {
+        this.timeout = timeout;
+    }
+
+    public void setUseBufferPool(boolean useBufferPool) {
+        this.useBufferPool = useBufferPool;
+    }
+
+    public void setSecurePort(int securePort) {
+        this.securePort = securePort;
+    }
+
+    public void setMinTasks(int minTasks) {
+        this.minTasks = minTasks;
+    }
+
+    public void setMaxTasks(int maxTasks) {
+        this.maxTasks = maxTasks;
+    }
+
+    public void setExecutor(ExecutorService executor) {
+        this.executor = executor;
+    }
+
+    public void heartbeat() {
+        //empty operation
+    }
+
+    public int getUdpPort() {
+        return udpPort;
+    }
+
+    public void setUdpPort(int udpPort) {
+        this.udpPort = udpPort;
+    }
+
+    public int getUdpRxBufSize() {
+        return udpRxBufSize;
+    }
+
+    public void setUdpRxBufSize(int udpRxBufSize) {
+        this.udpRxBufSize = udpRxBufSize;
+    }
+
+    public int getUdpTxBufSize() {
+        return udpTxBufSize;
+    }
+
+    public void setUdpTxBufSize(int udpTxBufSize) {
+        this.udpTxBufSize = udpTxBufSize;
+    }
+
+    // ---------------------------------------------- ThreadFactory Inner Class
+    class TaskThreadFactory implements ThreadFactory {
+        final ThreadGroup group;
+        final AtomicInteger threadNumber = new AtomicInteger(1);
+        final String namePrefix;
+
+        TaskThreadFactory(String namePrefix) {
+            SecurityManager s = System.getSecurityManager();
+            group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
+            this.namePrefix = namePrefix;
+        }
+
+        public Thread newThread(Runnable r) {
+            Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement());
+            t.setDaemon(daemon);
+            t.setPriority(Thread.NORM_PRIORITY);
+            return t;
+        }
+    }
+
+    public boolean isDaemon() {
+        return daemon;
+    }
+
+    public long getMaxIdleTime() {
+        return maxIdleTime;
+    }
+
+    public void setDaemon(boolean daemon) {
+        this.daemon = daemon;
+    }
+
+    public void setMaxIdleTime(long maxIdleTime) {
+        this.maxIdleTime = maxIdleTime;
+    }    
+    
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/ReplicationTransmitter.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/ReplicationTransmitter.java
new file mode 100644
index 0000000..abe73a2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/ReplicationTransmitter.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.tribes.transport;
+
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.ChannelSender;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.transport.nio.PooledParallelSender;
+import org.apache.catalina.tribes.util.StringManager;
+
+/**
+ * Transmit message to other cluster members
+ * Actual senders are created based on the replicationMode
+ * type 
+ * 
+ * @author Filip Hanik
+ * @version $Id: ReplicationTransmitter.java,v 1.1 2011/06/28 21:08:25 rherrmann Exp $
+ */
+public class ReplicationTransmitter implements ChannelSender {
+
+    /**
+     * The descriptive information about this implementation.
+     */
+    private static final String info = "ReplicationTransmitter/3.0";
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm = StringManager.getManager(Constants.Package);
+
+
+    public ReplicationTransmitter() {
+    }
+
+    private MultiPointSender transport = new PooledParallelSender();
+
+    /**
+     * 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 MultiPointSender getTransport() {
+        return transport;
+    }
+
+    public void setTransport(MultiPointSender transport) {
+        this.transport = transport;
+    }
+    
+    // ------------------------------------------------------------- public
+    
+    /**
+     * Send data to one member
+     * @see org.apache.catalina.tribes.ChannelSender#sendMessage(org.apache.catalina.tribes.ChannelMessage, org.apache.catalina.tribes.Member[])
+     */
+    public void sendMessage(ChannelMessage message, Member[] destination) throws ChannelException {
+        MultiPointSender sender = getTransport();
+        sender.sendMessage(destination,message);
+    }
+    
+    
+    /**
+     * start the sender and register transmitter mbean
+     * 
+     * @see org.apache.catalina.tribes.ChannelSender#start()
+     */
+    public void start() throws java.io.IOException {
+        getTransport().connect();
+    }
+
+    /**
+     * stop the sender and deregister mbeans (transmitter, senders)
+     * 
+     * @see org.apache.catalina.tribes.ChannelSender#stop()
+     */
+    public synchronized void stop() {
+        getTransport().disconnect();
+    }
+
+    /**
+     * Call transmitter to check for sender socket status
+     * 
+     * @see org.apache.catalina.ha.tcp.SimpleTcpCluster#backgroundProcess()
+     */
+    @Override
+    public void heartbeat() {
+        if (getTransport()!=null) getTransport().keepalive();
+    }
+
+    /**
+     * add new cluster member and create sender ( s. replicationMode) transfer
+     * current properties to sender
+     * 
+     * @see org.apache.catalina.tribes.ChannelSender#add(org.apache.catalina.tribes.Member)
+     */
+    public synchronized void add(Member member) {
+        getTransport().add(member);
+    }
+
+    /**
+     * remove sender from transmitter. ( deregister mbean and disconnect sender )
+     * 
+     * @see org.apache.catalina.tribes.ChannelSender#remove(org.apache.catalina.tribes.Member)
+     */
+    public synchronized void remove(Member member) {
+        getTransport().remove(member);
+    }
+
+    // ------------------------------------------------------------- protected
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/RxTaskPool.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/RxTaskPool.java
new file mode 100644
index 0000000..d3639cf
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/RxTaskPool.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.catalina.tribes.transport;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author not attributable
+ * @version 1.0
+ */
+
+public class RxTaskPool
+{
+    /**
+     * A very simple thread pool class.  The pool size is set at
+     * construction time and remains fixed.  Threads are cycled
+     * through a FIFO idle queue.
+     */
+
+    List<AbstractRxTask> idle = new LinkedList<AbstractRxTask>();
+    List<AbstractRxTask> used = new LinkedList<AbstractRxTask>();
+    
+    Object mutex = new Object();
+    boolean running = true;
+    
+    private int maxTasks;
+    private int minTasks;
+    
+    private TaskCreator creator = null;
+
+    
+    public RxTaskPool (int maxTasks, int minTasks, TaskCreator creator) throws Exception {
+        // fill up the pool with worker threads
+        this.maxTasks = maxTasks;
+        this.minTasks = minTasks;
+        this.creator = creator;
+    }
+    
+    protected void configureTask(AbstractRxTask task) {
+        synchronized (task) {
+            task.setTaskPool(this);
+//            task.setName(task.getClass().getName() + "[" + inc() + "]");
+//            task.setDaemon(true);
+//            task.setPriority(Thread.MAX_PRIORITY);
+//            task.start();
+        }
+    }
+
+    /**
+     * Find an idle worker thread, if any.  Could return null.
+     */
+    public AbstractRxTask getRxTask()
+    {
+        AbstractRxTask worker = null;
+        synchronized (mutex) {
+            while ( worker == null && running ) {
+                if (idle.size() > 0) {
+                    try {
+                        worker = idle.remove(0);
+                    } catch (java.util.NoSuchElementException x) {
+                        //this means that there are no available workers
+                        worker = null;
+                    }
+                } else if ( used.size() < this.maxTasks && creator != null) {
+                    worker = creator.createRxTask();
+                    configureTask(worker);
+                } else {
+                    try { mutex.wait(); } catch ( java.lang.InterruptedException x ) {Thread.interrupted();}
+                }
+            }//while
+            if ( worker != null ) used.add(worker);
+        }
+        return (worker);
+    }
+    
+    public int available() {
+        return idle.size();
+    }
+
+    /**
+     * Called by the worker thread to return itself to the
+     * idle pool.
+     */
+    public void returnWorker (AbstractRxTask worker) {
+        if ( running ) {
+            synchronized (mutex) {
+                used.remove(worker);
+                //if ( idle.size() < minThreads && !idle.contains(worker)) idle.add(worker);
+                if ( idle.size() < maxTasks && !idle.contains(worker)) idle.add(worker); //let max be the upper limit
+                else {
+                    worker.setDoRun(false);
+                    synchronized (worker){worker.notify();}
+                }
+                mutex.notify();
+            }
+        }else {
+            worker.setDoRun(false);
+            synchronized (worker){worker.notify();}
+        }
+    }
+
+    public int getMaxThreads() {
+        return maxTasks;
+    }
+
+    public int getMinThreads() {
+        return minTasks;
+    }
+
+    public void stop() {
+        running = false;
+        synchronized (mutex) {
+            Iterator<AbstractRxTask> i = idle.iterator();
+            while ( i.hasNext() ) {
+                AbstractRxTask worker = i.next();
+                returnWorker(worker);
+                i.remove();
+            }
+        }
+    }
+
+    public void setMaxTasks(int maxThreads) {
+        this.maxTasks = maxThreads;
+    }
+
+    public void setMinTasks(int minThreads) {
+        this.minTasks = minThreads;
+    }
+
+    public TaskCreator getTaskCreator() {
+        return this.creator;
+    }
+    
+    public static interface TaskCreator  {
+        public AbstractRxTask createRxTask();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/SenderState.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/SenderState.java
new file mode 100644
index 0000000..6c74e40
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/SenderState.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.tribes.transport;
+
+import java.util.HashMap;
+
+import org.apache.catalina.tribes.Member;
+
+
+/**
+ * 
+ * @author Filip Hanik
+ * @version 1.0
+ * @since 5.5.16
+ */
+
+public class SenderState {
+    
+    public static final int READY = 0;
+    public static final int SUSPECT = 1;
+    public static final int FAILING = 2;    
+    
+    protected static HashMap<Member, SenderState> memberStates = new HashMap<Member, SenderState>();
+    
+    public static SenderState getSenderState(Member member) {
+        return getSenderState(member,true);
+    }
+
+    public static SenderState getSenderState(Member member, boolean create) {
+        SenderState state = memberStates.get(member);
+        if ( state == null && create) {
+            synchronized ( memberStates ) {
+                state = memberStates.get(member);
+                if ( state == null ) {
+                    state = new SenderState();
+                    memberStates.put(member,state);
+                }
+            }
+        }
+        return state;
+    }
+    
+    public static void removeSenderState(Member member) {
+        synchronized ( memberStates ) {
+            memberStates.remove(member);
+        }
+    }
+    
+
+    // ----------------------------------------------------- Instance Variables
+
+    private int state = READY;
+
+    //  ----------------------------------------------------- Constructor
+
+    
+    private SenderState() {
+        this(READY);
+    }
+
+    private SenderState(int state) {
+        this.state = state;
+    }
+    
+    /**
+     * 
+     * @return boolean
+     */
+    public boolean isSuspect() {
+        return (state == SUSPECT) || (state == FAILING);
+    }
+
+    public void setSuspect() {
+        state = SUSPECT;
+    }
+    
+    public boolean isReady() {
+        return state == READY;
+    }
+    
+    public void setReady() {
+        state = READY;
+    }
+    
+    public boolean isFailing() {
+        return state == FAILING;
+    }
+    
+    public void setFailing() {
+        state = FAILING;
+    }
+    
+
+    //  ----------------------------------------------------- Public Properties
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/bio/BioReceiver.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/bio/BioReceiver.java
new file mode 100644
index 0000000..c8faace
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/bio/BioReceiver.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.transport.bio;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import org.apache.catalina.tribes.io.ObjectReader;
+import org.apache.catalina.tribes.transport.AbstractRxTask;
+import org.apache.catalina.tribes.transport.ReceiverBase;
+import org.apache.catalina.tribes.transport.RxTaskPool;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ *
+ * @author Filip Hanik
+ * @version $Id: BioReceiver.java,v 1.1 2011/06/28 21:08:17 rherrmann Exp $
+ */
+public class BioReceiver extends ReceiverBase implements Runnable {
+
+    private static final Log log = LogFactory.getLog(BioReceiver.class);
+
+    protected ServerSocket serverSocket;
+
+    public BioReceiver() {
+        // NO-OP
+    }
+
+    @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, "BioReceiver");
+            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());
+        }
+    }
+    
+    @Override
+    public AbstractRxTask createRxTask() {
+        return getReplicationThread();
+    }
+    
+    protected BioReplicationTask getReplicationThread() {
+        BioReplicationTask result = new BioReplicationTask(this);
+        result.setOptions(getWorkerThreadOptions());
+        result.setUseBufferPool(this.getUseBufferPool());
+        return result;
+    }
+
+    @Override
+    public void stop() {
+        setListen(false);
+        try {
+            this.serverSocket.close();
+        } catch (Exception x) {
+            if (log.isDebugEnabled()) {
+                log.debug("Failed to close socket", x);
+            }
+        }
+        super.stop();
+    }
+
+
+    protected void bind() throws IOException {
+        // allocate an unbound server socket channel
+        serverSocket = new ServerSocket();
+        // set the port the server channel will listen to
+        //serverSocket.bind(new InetSocketAddress(getBind(), getTcpListenPort()));
+        bind(serverSocket,getPort(),getAutoBind());
+    }
+
+
+    @Override
+    public void run() {
+        try {
+            listen();
+        } catch (Exception x) {
+            log.error("Unable to run replication listener.", x);
+        }
+    }
+    
+    public void listen() throws Exception {
+        if (doListen()) {
+            log.warn("ServerSocket already started");
+            return;
+        }
+        setListen(true);
+
+        while ( doListen() ) {
+            Socket socket = null;
+            if ( getTaskPool().available() < 1 ) {
+                if ( log.isWarnEnabled() )
+                    log.warn("All BIO server replication threads are busy, unable to handle more requests until a thread is freed up.");
+            }
+            BioReplicationTask task = (BioReplicationTask)getTaskPool().getRxTask();
+            if ( task == null ) continue; //should never happen
+            try {
+                socket = serverSocket.accept();
+            }catch ( Exception x ) {
+                if ( doListen() ) throw x;
+            }
+            if ( !doListen() ) {
+                task.setDoRun(false);
+                task.serviceSocket(null,null);
+                getExecutor().execute(task);
+                break; //regular shutdown
+            }
+            if ( socket == null ) continue;
+            socket.setReceiveBufferSize(getRxBufSize());
+            socket.setSendBufferSize(getTxBufSize());
+            socket.setTcpNoDelay(getTcpNoDelay());
+            socket.setKeepAlive(getSoKeepAlive());
+            socket.setOOBInline(getOoBInline());
+            socket.setReuseAddress(getSoReuseAddress());
+            socket.setSoLinger(getSoLingerOn(),getSoLingerTime());
+            socket.setTrafficClass(getSoTrafficClass());
+            socket.setSoTimeout(getTimeout());
+            ObjectReader reader = new ObjectReader(socket);
+            task.serviceSocket(socket,reader);
+            getExecutor().execute(task);
+        }//while
+    }
+    
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/bio/BioReplicationTask.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/bio/BioReplicationTask.java
new file mode 100644
index 0000000..53e9504
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/bio/BioReplicationTask.java
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+
+import org.apache.catalina.tribes.ChannelMessage;
+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.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * 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: BioReplicationTask.java,v 1.1 2011/06/28 21:08:17 rherrmann Exp $
+ */
+public class BioReplicationTask extends AbstractRxTask {
+
+
+    private static final Log log = LogFactory.getLog( BioReplicationTask.class );
+    
+    protected Socket socket;
+    protected ObjectReader reader;
+    
+    public BioReplicationTask (ListenCallback callback) {
+        super(callback);
+    }
+
+    // loop forever waiting for work to do
+    @Override
+    public synchronized void run()
+    {
+        if ( socket == null ) return;
+        try {
+            drainSocket();
+        } catch ( Exception x ) {
+            log.error("Unable to service bio socket", x);
+        }finally {
+            try {
+                socket.close();
+            }catch (Exception e) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Failed to close socket", e);
+                }
+            }
+            try {
+                reader.close();
+            }catch (Exception e) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Failed to close reader", e);
+                }
+            }
+            reader = null;
+            socket = null;
+        }
+        // done, ready for more, return to pool
+        if ( getTaskPool() != null ) getTaskPool().returnWorker (this);
+    }
+
+    
+    public synchronized void serviceSocket(Socket socket, ObjectReader reader) {
+        this.socket = socket;
+        this.reader = reader;
+    }
+    
+    protected void execute(ObjectReader reader) throws Exception{
+        int pkgcnt = reader.count();
+
+        if ( pkgcnt > 0 ) {
+            ChannelMessage[] msgs = reader.execute();
+            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(Constants.ACK_COMMAND);
+                try {
+                    //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(Constants.ACK_COMMAND);
+                }catch  ( Exception x ) {
+                    if (ChannelData.sendAckSync(msgs[i].getOptions())) sendAck(Constants.FAIL_ACK_COMMAND);
+                    log.error("Error thrown from messageDataReceived.",x);
+                }
+                if ( getUseBufferPool() ) {
+                    BufferPool.getBufferPool().returnBuffer(msgs[i].getMessage());
+                    msgs[i].setMessage(null);
+                }
+            }                       
+        }
+
+       
+    }
+
+    /**
+     * 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 drainSocket () throws Exception {
+        InputStream in = socket.getInputStream();
+        // loop while data available, channel is non-blocking
+        byte[] buf = new byte[1024];
+        int length = in.read(buf);
+        while ( length >= 0 ) {
+            int count = reader.append(buf,0,length,true);
+            if ( count > 0 ) execute(reader);
+            length = in.read(buf);
+        }
+    }
+
+
+    /**
+     * send a reply-acknowledgment (6,2,3)
+     * @param command
+     */
+    protected void sendAck(byte[] command) {
+        try {
+            OutputStream out = socket.getOutputStream();
+            out.write(command);
+            out.flush();
+            if (log.isTraceEnabled()) {
+                log.trace("ACK sent to " + socket.getPort());
+            }
+        } catch ( java.io.IOException x ) {
+            log.warn("Unable to send ACK back through channel, channel disconnected?: "+x.getMessage());
+        }
+    }
+    
+    @Override
+    public void close() {
+        setDoRun(false);
+        try {
+            socket.close();
+        }catch (Exception e) {
+            if (log.isDebugEnabled()) {
+                log.debug("Failed to close socket", e);
+            }
+        }
+        try {
+            reader.close();
+        }catch (Exception e) {
+            if (log.isDebugEnabled()) {
+                log.debug("Failed to close reader", e);
+            }
+        }
+        reader = null;
+        socket = null;
+        super.close();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/bio/BioSender.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/bio/BioSender.java
new file mode 100644
index 0000000..9bcf506
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/bio/BioSender.java
@@ -0,0 +1,295 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+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.catalina.tribes.transport.Constants;
+import org.apache.catalina.tribes.transport.SenderState;
+import org.apache.catalina.tribes.util.StringManager;
+
+/**
+ * Send cluster messages with only one socket. Ack and keep Alive Handling is
+ * supported
+ * 
+ * @author Peter Rossbach
+ * @author Filip Hanik
+ * @version $Id: BioSender.java,v 1.1 2011/06/28 21:08:17 rherrmann Exp $
+ * @since 5.5.16
+ */
+public class BioSender extends AbstractSender {
+
+    private static final org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(BioSender.class);
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm = StringManager.getManager(Constants.Package);
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * The descriptive information about this implementation.
+     */
+    private static final String info = "DataSender/3.0";
+
+    
+    /**
+     * current sender socket
+     */
+    private Socket socket = null;
+    private OutputStream soOut = null;
+    private InputStream soIn = null;
+    
+    protected XByteBuffer ackbuf = new XByteBuffer(Constants.ACK_COMMAND.length,true);
+
+
+    // ------------------------------------------------------------- Constructor
+    
+    public BioSender()  {
+        // NO-OP
+    }
+
+
+    // ------------------------------------------------------------- Properties
+
+    /**
+     * 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 Methods
+
+    /**
+     * Connect other cluster member receiver 
+     * @see org.apache.catalina.tribes.transport.DataSender#connect()
+     */
+    @Override
+    public  void connect() throws IOException {
+        openSocket();
+   }
+
+ 
+    /**
+     * disconnect and close socket
+     * 
+     * @see org.apache.catalina.tribes.transport.DataSender#disconnect()
+     */
+    @Override
+    public  void disconnect() {
+        boolean connect = isConnected();
+        closeSocket();
+        if (connect) {
+            if (log.isDebugEnabled())
+                log.debug(sm.getString("IDataSender.disconnect", getAddress().getHostAddress(), new Integer(getPort()), new Long(0)));
+        }
+        
+    }
+
+    /**
+     * Send message.
+     */
+    public  void sendMessage(byte[] data, boolean waitForAck) throws IOException {
+        IOException exception = null;
+        setAttempt(0);
+        try {
+             // first try with existing connection
+             pushMessage(data,false,waitForAck);
+        } catch (IOException x) {
+            SenderState.getSenderState(getDestination()).setSuspect();
+            exception = x;
+            if (log.isTraceEnabled()) log.trace(sm.getString("IDataSender.send.again", getAddress().getHostAddress(),new Integer(getPort())),x);
+            while ( getAttempt()<getMaxRetryAttempts() ) {
+                try {
+                    setAttempt(getAttempt()+1);
+                    // second try with fresh connection
+                    pushMessage(data, true,waitForAck);
+                    exception = null;
+                } catch (IOException xx) {
+                    exception = xx;
+                    closeSocket();
+                }
+            }
+        } finally {
+            setRequestCount(getRequestCount()+1);
+            keepalive();
+            if ( exception != null ) throw exception;
+        }
+    }
+
+    
+    /**
+     * Name of this SockerSender
+     */
+    @Override
+    public String toString() {
+        StringBuilder buf = new StringBuilder("DataSender[(");
+        buf.append(super.toString()).append(")");
+        buf.append(getAddress()).append(":").append(getPort()).append("]");
+        return buf.toString();
+    }
+
+    // --------------------------------------------------------- Protected Methods
+ 
+    /**
+     * open real socket and set time out when waitForAck is enabled
+     * is socket open return directly
+     */
+    protected void openSocket() throws IOException {
+       if(isConnected()) return ;
+       try {
+           socket = new Socket();
+           InetSocketAddress sockaddr = new InetSocketAddress(getAddress(), getPort());
+           socket.connect(sockaddr,(int)getTimeout());
+           socket.setSendBufferSize(getTxBufSize());
+           socket.setReceiveBufferSize(getRxBufSize());
+           socket.setSoTimeout( (int) getTimeout());
+           socket.setTcpNoDelay(getTcpNoDelay());
+           socket.setKeepAlive(getSoKeepAlive());
+           socket.setReuseAddress(getSoReuseAddress());
+           socket.setOOBInline(getOoBInline());
+           socket.setSoLinger(getSoLingerOn(),getSoLingerTime());
+           socket.setTrafficClass(getSoTrafficClass());
+           setConnected(true);
+           soOut = socket.getOutputStream();
+           soIn  = socket.getInputStream();
+           setRequestCount(0);
+           setConnectTime(System.currentTimeMillis());
+           if (log.isDebugEnabled())
+               log.debug(sm.getString("IDataSender.openSocket", getAddress().getHostAddress(), new Integer(getPort()), new Long(0)));
+      } catch (IOException ex1) {
+          SenderState.getSenderState(getDestination()).setSuspect();
+          if (log.isDebugEnabled())
+              log.debug(sm.getString("IDataSender.openSocket.failure",getAddress().getHostAddress(), new Integer(getPort()),new Long(0)), ex1);
+          throw (ex1);
+        }
+        
+     }
+
+    /**
+     * Close socket.
+     * 
+     * @see #disconnect()
+     */
+    protected void closeSocket() {
+        if(isConnected()) {
+             if (socket != null) {
+                try {
+                    socket.close();
+                } catch (IOException x) {
+                    // Ignore
+                } finally {
+                    socket = null;
+                    soOut = null;
+                    soIn = null;
+                }
+            }
+            setRequestCount(0);
+            setConnected(false);
+            if (log.isDebugEnabled())
+                log.debug(sm.getString("IDataSender.closeSocket",getAddress().getHostAddress(), new Integer(getPort()),new Long(0)));
+       }
+    }
+
+    /**
+     * Push messages with only one socket at a time
+     * Wait for ack is needed and make auto retry when write message is failed.
+     * After sending error close and reopen socket again.
+     * 
+     * After successful sending update stats
+     * 
+     * WARNING: Subclasses must be very careful that only one thread call this pushMessage at once!!!
+     * 
+     * @see #closeSocket()
+     * @see #openSocket()
+     * @see #sendMessage(byte[], boolean)
+     * 
+     * @param data
+     *            data to send
+     * @since 5.5.10
+     */
+    
+    protected void pushMessage(byte[] data, boolean reconnect, boolean waitForAck) throws IOException {
+        keepalive();
+        if ( reconnect ) closeSocket();
+        if (!isConnected()) openSocket();
+        soOut.write(data);
+        soOut.flush();
+        if (waitForAck) waitForAck();
+        SenderState.getSenderState(getDestination()).setReady();
+
+    }
+    
+    /**
+     * Wait for Acknowledgement from other server.
+     * FIXME Please, not wait only for three characters, better control that the wait ack message is correct.
+     * @throws java.io.IOException
+     * @throws java.net.SocketTimeoutException
+     */
+    protected void waitForAck() throws java.io.IOException {
+        try {
+            boolean ackReceived = false;
+            boolean failAckReceived = false;
+            ackbuf.clear();
+            int bytesRead = 0;
+            int i = soIn.read();
+            while ((i != -1) && (bytesRead < Constants.ACK_COMMAND.length)) {
+                bytesRead++;
+                byte d = (byte)i;
+                ackbuf.append(d);
+                if (ackbuf.doesPackageExist() ) {
+                    byte[] ackcmd = ackbuf.extractDataPackage(true).getBytes();
+                    ackReceived = Arrays.equals(ackcmd,org.apache.catalina.tribes.transport.Constants.ACK_DATA);
+                    failAckReceived = Arrays.equals(ackcmd,org.apache.catalina.tribes.transport.Constants.FAIL_ACK_DATA);
+                    ackReceived = ackReceived || failAckReceived;
+                    break;
+                }
+                i = soIn.read();
+            }
+            if (!ackReceived) {
+                if (i == -1) throw new IOException(sm.getString("IDataSender.ack.eof",getAddress(), new Integer(socket.getLocalPort())));
+                else throw new IOException(sm.getString("IDataSender.ack.wrong",getAddress(), new Integer(socket.getLocalPort())));
+            } else if ( failAckReceived && getThrowOnFailedAck()) {
+                throw new RemoteProcessException("Received a failed ack:org.apache.catalina.tribes.transport.Constants.FAIL_ACK_DATA");
+            }
+        } catch (IOException x) {
+            String errmsg = sm.getString("IDataSender.ack.missing", getAddress(),new Integer(socket.getLocalPort()), new Long(getTimeout()));
+            if ( SenderState.getSenderState(getDestination()).isReady() ) {
+                SenderState.getSenderState(getDestination()).setSuspect();
+                if ( log.isWarnEnabled() ) log.warn(errmsg, x);
+            } else {
+                if ( log.isDebugEnabled() )log.debug(errmsg, x);
+            }
+            throw x;
+        } finally {
+            ackbuf.clear();
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/bio/MultipointBioSender.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/bio/MultipointBioSender.java
new file mode 100644
index 0000000..ea0db03
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/bio/MultipointBioSender.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.catalina.tribes.transport.bio;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+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.io.ChannelData;
+import org.apache.catalina.tribes.io.XByteBuffer;
+import org.apache.catalina.tribes.transport.AbstractSender;
+import org.apache.catalina.tribes.transport.MultiPointSender;
+
+/**
+ *
+ * @author Filip Hanik
+ * @version $Id: MultipointBioSender.java,v 1.1 2011/06/28 21:08:17 rherrmann Exp $
+ *
+ */
+public class MultipointBioSender extends AbstractSender implements MultiPointSender {
+    public MultipointBioSender() {
+        // NO-OP
+    }
+    
+    protected long selectTimeout = 1000; 
+    protected HashMap<Member, BioSender> bioSenders =
+        new HashMap<Member, BioSender>();
+
+    @Override
+    public synchronized void sendMessage(Member[] destination, ChannelMessage msg) throws ChannelException {
+        byte[] data = XByteBuffer.createDataPackage((ChannelData)msg);
+        BioSender[] senders = setupForSend(destination);
+        ChannelException cx = null;
+        for ( int i=0; i<senders.length; i++ ) {
+            try {
+                senders[i].sendMessage(data,(msg.getOptions()&Channel.SEND_OPTIONS_USE_ACK)==Channel.SEND_OPTIONS_USE_ACK);
+            } catch (Exception x) {
+                if (cx == null) cx = new ChannelException(x);
+                cx.addFaultyMember(destination[i],x);
+            }
+        }
+        if (cx!=null ) throw cx;
+    }
+
+
+
+    protected BioSender[] setupForSend(Member[] destination) throws ChannelException {
+        ChannelException cx = null;
+        BioSender[] result = new BioSender[destination.length];
+        for ( int i=0; i<destination.length; i++ ) {
+            try {
+                BioSender sender = bioSenders.get(destination[i]);
+                if (sender == null) {
+                    sender = new BioSender();
+                    AbstractSender.transferProperties(this,sender);
+                    sender.setDestination(destination[i]);
+                    bioSenders.put(destination[i], sender);
+                }
+                result[i] = sender;
+                if (!result[i].isConnected() ) result[i].connect();
+                result[i].keepalive();
+            }catch (Exception x ) {
+                if ( cx== null ) cx = new ChannelException(x);
+                cx.addFaultyMember(destination[i],x);
+            }
+        }
+        if ( cx!=null ) throw cx;
+        else return result;
+    }
+
+    @Override
+    public void connect() throws IOException {
+        //do nothing, we connect on demand
+        setConnected(true);
+    }
+
+
+    private synchronized void close() throws ChannelException  {
+        ChannelException x = null;
+        Object[] members = bioSenders.keySet().toArray();
+        for (int i=0; i<members.length; i++ ) {
+            Member mbr = (Member)members[i];
+            try {
+                BioSender sender = bioSenders.get(mbr);
+                sender.disconnect();
+            }catch ( Exception e ) {
+                if ( x == null ) x = new ChannelException(e);
+                x.addFaultyMember(mbr,e);
+            }
+            bioSenders.remove(mbr);
+        }
+        if ( x != null ) throw x;
+    }
+
+    @Override
+    public void add(Member member) {
+        // NO-OP
+        // Members are defined by the array of members specified in the call to
+        // sendMessage()
+    }
+
+    @Override
+    public void remove(Member member) {
+        //disconnect senders
+        BioSender sender = bioSenders.remove(member);
+        if ( sender != null ) sender.disconnect();
+    }
+
+
+    @Override
+    public synchronized void disconnect() {
+        try {close(); }catch (Exception x){/* Ignore */}
+        setConnected(false);
+    }
+
+    @Override
+    public void finalize() {
+        try {disconnect(); }catch ( Exception e){/* Ignore */}
+    }
+
+
+    @Override
+    public boolean keepalive() {
+        //throw new UnsupportedOperationException("Method ParallelBioSender.checkKeepAlive() not implemented");
+        boolean result = false;
+        @SuppressWarnings("unchecked") // bioSenders is of type HashMap<Member, BioSender>
+        Map.Entry<Member,BioSender>[] entries = bioSenders.entrySet().toArray(new Map.Entry[bioSenders.size()]);
+        for ( int i=0; i<entries.length; i++ ) {
+            BioSender sender = entries[i].getValue();
+            if ( sender.keepalive() ) {
+                bioSenders.remove(entries[i].getKey());
+            }
+        }
+        return result;
+    }
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/bio/PooledMultiSender.java b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/bio/PooledMultiSender.java
new file mode 100644
index 0000000..8f96d06
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/tribes/transport/bio/PooledMultiSender.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.tribes.transport.bio;
+
+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.MultiPointSender;
+import org.apache.catalina.tribes.transport.PooledSender;
+
+/**
+ * <p>Title: </p>
+ *
+ * <p>Description: </p>
+ *
+ * <p>Company: </p>
+ *
+ * @author not attributable
+ * @version 1.0
+ */
+public class PooledMultiSender extends PooledSender {
+    
+
+    public PooledMultiSender() {
+        // NO-OP
+    }
+    
+    @Override
+    public void sendMessage(Member[] destination, ChannelMessage msg) throws ChannelException {
+        MultiPointSender sender = null;
+        try {
+            sender = (MultiPointSender)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 {
+                sender.sendMessage(destination, msg);
+            }
+            sender.keepalive();
+        }finally {
+            if ( sender != null ) returnSender(sender);
+        }
+    }
+
+    /**
+     * getNewDataSender
+     *
+     * @return DataSender
+     * TODO Implement this org.apache.catalina.tribes.transport.PooledSender
+     *   method
+     */
+    @Override
+    public DataSender getNewDataSender() {
+        MultipointBioSender sender = new MultipointBioSender();
+        AbstractSender.transferProperties(this,sender);
+        return sender;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/users/AbstractGroup.java b/bundles/org.apache.tomcat/src/org/apache/catalina/users/AbstractGroup.java
new file mode 100644
index 0000000..43b6860
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/users/AbstractGroup.java
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.users;
+
+
+import java.util.Iterator;
+
+import org.apache.catalina.Group;
+import org.apache.catalina.Role;
+import org.apache.catalina.User;
+import org.apache.catalina.UserDatabase;
+
+
+/**
+ * <p>Convenience base class for {@link Group} implementations.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: AbstractGroup.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ * @since 4.1
+ */
+
+public abstract class AbstractGroup implements Group {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The description of this group.
+     */
+    protected String description = null;
+
+
+    /**
+     * The group name of this group.
+     */
+    protected String groupname = null;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the description of this group.
+     */
+    public String getDescription() {
+
+        return (this.description);
+
+    }
+
+
+    /**
+     * Set the description of this group.
+     *
+     * @param description The new description
+     */
+    public void setDescription(String description) {
+
+        this.description = description;
+
+    }
+
+
+    /**
+     * Return the group name of this group, which must be unique
+     * within the scope of a {@link UserDatabase}.
+     */
+    public String getGroupname() {
+
+        return (this.groupname);
+
+    }
+
+
+    /**
+     * Set the group name of this group, which must be unique
+     * within the scope of a {@link UserDatabase}.
+     *
+     * @param groupname The new group name
+     */
+    public void setGroupname(String groupname) {
+
+        this.groupname = groupname;
+
+    }
+
+
+    /**
+     * Return the set of {@link Role}s assigned specifically to this group.
+     */
+    public abstract Iterator<Role> getRoles();
+
+
+    /**
+     * Return the {@link UserDatabase} within which this Group is defined.
+     */
+    public abstract UserDatabase getUserDatabase();
+
+
+    /**
+     * Return an Iterator over the set of {@link org.apache.catalina.User}s that 
+     * are members of this group.
+     */
+    public abstract Iterator<User> getUsers();
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add a new {@link Role} to those assigned specifically to this group.
+     *
+     * @param role The new role
+     */
+    public abstract void addRole(Role role);
+
+
+    /**
+     * Is this group specifically assigned the specified {@link Role}?
+     *
+     * @param role The role to check
+     */
+    public abstract boolean isInRole(Role role);
+
+
+    /**
+     * Remove a {@link Role} from those assigned to this group.
+     *
+     * @param role The old role
+     */
+    public abstract void removeRole(Role role);
+
+
+    /**
+     * Remove all {@link Role}s from those assigned to this group.
+     */
+    public abstract void removeRoles();
+
+
+    // ------------------------------------------------------ Principal Methods
+
+
+    /**
+     * Make the principal name the same as the group name.
+     */
+    public String getName() {
+
+        return (getGroupname());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/users/AbstractRole.java b/bundles/org.apache.tomcat/src/org/apache/catalina/users/AbstractRole.java
new file mode 100644
index 0000000..912633f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/users/AbstractRole.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.users;
+
+
+import org.apache.catalina.Role;
+import org.apache.catalina.UserDatabase;
+
+
+/**
+ * <p>Convenience base class for {@link Role} implementations.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: AbstractRole.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ * @since 4.1
+ */
+
+public abstract class AbstractRole implements Role {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The description of this Role.
+     */
+    protected String description = null;
+
+
+    /**
+     * The role name of this Role.
+     */
+    protected String rolename = null;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the description of this role.
+     */
+    public String getDescription() {
+
+        return (this.description);
+
+    }
+
+
+    /**
+     * Set the description of this role.
+     *
+     * @param description The new description
+     */
+    public void setDescription(String description) {
+
+        this.description = description;
+
+    }
+
+
+    /**
+     * Return the role name of this role, which must be unique
+     * within the scope of a {@link UserDatabase}.
+     */
+    public String getRolename() {
+
+        return (this.rolename);
+
+    }
+
+
+    /**
+     * Set the role name of this role, which must be unique
+     * within the scope of a {@link UserDatabase}.
+     *
+     * @param rolename The new role name
+     */
+    public void setRolename(String rolename) {
+
+        this.rolename = rolename;
+
+    }
+
+
+    /**
+     * Return the {@link UserDatabase} within which this Role is defined.
+     */
+    public abstract UserDatabase getUserDatabase();
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    // ------------------------------------------------------ Principal Methods
+
+
+    /**
+     * Make the principal name the same as the role name.
+     */
+    public String getName() {
+
+        return (getRolename());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/users/AbstractUser.java b/bundles/org.apache.tomcat/src/org/apache/catalina/users/AbstractUser.java
new file mode 100644
index 0000000..6596eb7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/users/AbstractUser.java
@@ -0,0 +1,226 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.users;
+
+
+import java.util.Iterator;
+
+import org.apache.catalina.Group;
+import org.apache.catalina.Role;
+import org.apache.catalina.User;
+
+
+/**
+ * <p>Convenience base class for {@link User} implementations.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: AbstractUser.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ * @since 4.1
+ */
+
+public abstract class AbstractUser implements User {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The full name of this user.
+     */
+    protected String fullName = null;
+
+
+    /**
+     * The logon password of this user.
+     */
+    protected String password = null;
+
+
+    /**
+     * The logon username of this user.
+     */
+    protected String username = null;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the full name of this user.
+     */
+    public String getFullName() {
+
+        return (this.fullName);
+
+    }
+
+
+    /**
+     * Set the full name of this user.
+     *
+     * @param fullName The new full name
+     */
+    public void setFullName(String fullName) {
+
+        this.fullName = fullName;
+
+    }
+
+
+    /**
+     * Return the set of {@link Group}s to which this user belongs.
+     */
+    public abstract Iterator<Group> getGroups();
+
+
+    /**
+     * Return the logon password of this user, optionally prefixed with the
+     * identifier of an encoding scheme surrounded by curly braces, such as
+     * <code>{md5}xxxxx</code>.
+     */
+    public String getPassword() {
+
+        return (this.password);
+
+    }
+
+
+    /**
+     * Set the logon password of this user, optionally prefixed with the
+     * identifier of an encoding scheme surrounded by curly braces, such as
+     * <code>{md5}xxxxx</code>.
+     *
+     * @param password The new logon password
+     */
+    public void setPassword(String password) {
+
+        this.password = password;
+
+    }
+
+
+    /**
+     * Return the set of {@link Role}s assigned specifically to this user.
+     */
+    public abstract Iterator<Role> getRoles();
+
+
+    /**
+     * Return the logon username of this user, which must be unique
+     * within the scope of a {@link org.apache.catalina.UserDatabase}.
+     */
+    public String getUsername() {
+
+        return (this.username);
+
+    }
+
+
+    /**
+     * Set the logon username of this user, which must be unique within
+     * the scope of a {@link org.apache.catalina.UserDatabase}.
+     *
+     * @param username The new logon username
+     */
+    public void setUsername(String username) {
+
+        this.username = username;
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add a new {@link Group} to those this user belongs to.
+     *
+     * @param group The new group
+     */
+    public abstract void addGroup(Group group);
+
+
+    /**
+     * Add a new {@link Role} to those assigned specifically to this user.
+     *
+     * @param role The new role
+     */
+    public abstract void addRole(Role role);
+
+
+    /**
+     * Is this user in the specified {@link Group}?
+     *
+     * @param group The group to check
+     */
+    public abstract boolean isInGroup(Group group);
+
+
+    /**
+     * Is this user specifically assigned the specified {@link Role}?  This
+     * method does <strong>NOT</strong> check for roles inherited based on
+     * {@link Group} membership.
+     *
+     * @param role The role to check
+     */
+    public abstract boolean isInRole(Role role);
+
+
+    /**
+     * Remove a {@link Group} from those this user belongs to.
+     *
+     * @param group The old group
+     */
+    public abstract void removeGroup(Group group);
+
+
+    /**
+     * Remove all {@link Group}s from those this user belongs to.
+     */
+    public abstract void removeGroups();
+
+
+    /**
+     * Remove a {@link Role} from those assigned to this user.
+     *
+     * @param role The old role
+     */
+    public abstract void removeRole(Role role);
+
+
+    /**
+     * Remove all {@link Role}s from those assigned to this user.
+     */
+    public abstract void removeRoles();
+
+
+    // ------------------------------------------------------ Principal Methods
+
+
+    /**
+     * Make the principal name the same as the group name.
+     */
+    public String getName() {
+
+        return (getUsername());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/users/Constants.java b/bundles/org.apache.tomcat/src/org/apache/catalina/users/Constants.java
new file mode 100644
index 0000000..e6d3108
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/users/Constants.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.users;
+
+
+/**
+ * Manifest constants for this Java package.
+ *
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: Constants.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ * @since 4.1
+ */
+
+public final class Constants {
+
+    public static final String Package = "org.apache.catalina.users";
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/users/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/users/LocalStrings.properties
new file mode 100644
index 0000000..885b07f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/users/LocalStrings.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.
+
+memoryUserDatabase.invalidGroup=Invalid group name {0}
+memoryUserDatabase.notPersistable=User database is not persistable - no write permissions on directory
+memoryUserDatabase.nullGroup=Null or zero length group name specified. The group will be ignored.
+memoryUserDatabase.nullRole=Null or zero length role name specified. The role will be ignored.
+memoryUserDatabase.nullUser=Null or zero length user name specified. The user will be ignored.
+memoryUserDatabase.readOnly=User database has been configured to be read only. Changes cannot be saved
+memoryUserDatabase.renameOld=Cannot rename original file to {0}
+memoryUserDatabase.renameNew=Cannot rename new file to {0}
+memoryUserDatabase.writeException=IOException writing to {0}
+memoryUserDatabase.xmlFeatureEncoding=Exception configuring digester to permit java encoding names in XML files. Only IANA encoding names will be supported.
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/users/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/users/LocalStrings_es.properties
new file mode 100644
index 0000000..43be40c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/users/LocalStrings_es.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+memoryUserDatabase.invalidGroup = Nombre de grupo {0} inv\u00E1lido
+memoryUserDatabase.renameOld = Imposible de renombrar el archivo original a {0}
+memoryUserDatabase.renameNew = Imposible de renombrar el archivo nuevo a {0}
+memoryUserDatabase.writeException = IOException durante la escritura hacia {0}
+memoryUserDatabase.notPersistable = La base de datos de usuario no es persistible - no hay permisos de grabaci\u00F3n sobre el directorio
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/users/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/users/LocalStrings_fr.properties
new file mode 100644
index 0000000..11bf591
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/users/LocalStrings_fr.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+memoryUserDatabase.invalidGroup=Nom de groupe invalide {0}
+memoryUserDatabase.renameOld=Impossible de renommer le fichier original en {0}
+memoryUserDatabase.renameNew=Impossible de renommer le nouveau fichier en {0}
+memoryUserDatabase.writeException=IOException lors de l''\u00e9criture vers {0}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/users/LocalStrings_ja.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/users/LocalStrings_ja.properties
new file mode 100644
index 0000000..a4b2de6
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/users/LocalStrings_ja.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+memoryUserDatabase.invalidGroup=\u7121\u52b9\u306a\u30b0\u30eb\u30fc\u30d7\u540d {0}
+memoryUserDatabase.renameOld=\u5143\u306e\u30d5\u30a1\u30a4\u30eb\u540d\u3092 {0} \u306b\u5909\u66f4\u3067\u304d\u307e\u305b\u3093
+memoryUserDatabase.renameNew=\u65b0\u3057\u3044\u30d5\u30a1\u30a4\u30eb\u540d\u3092 {0} \u306b\u5909\u66f4\u3067\u304d\u307e\u305b\u3093
+memoryUserDatabase.writeException={0} \u306b\u66f8\u304d\u8fbc\u307f\u4e2d\u306eIOException\u3067\u3059
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/users/MemoryGroup.java b/bundles/org.apache.tomcat/src/org/apache/catalina/users/MemoryGroup.java
new file mode 100644
index 0000000..ed13638
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/users/MemoryGroup.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.catalina.users;
+
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.apache.catalina.Role;
+import org.apache.catalina.User;
+import org.apache.catalina.UserDatabase;
+
+
+/**
+ * <p>Concrete implementation of {@link org.apache.catalina.Group} for the
+ * {@link MemoryUserDatabase} implementation of {@link UserDatabase}.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: MemoryGroup.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ * @since 4.1
+ */
+
+public class MemoryGroup extends AbstractGroup {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Package-private constructor used by the factory method in
+     * {@link MemoryUserDatabase}.
+     *
+     * @param database The {@link MemoryUserDatabase} that owns this group
+     * @param groupname Group name of this group
+     * @param description Description of this group
+     */
+    MemoryGroup(MemoryUserDatabase database,
+                String groupname, String description) {
+
+        super();
+        this.database = database;
+        setGroupname(groupname);
+        setDescription(description);
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The {@link MemoryUserDatabase} that owns this group.
+     */
+    protected MemoryUserDatabase database = null;
+
+
+    /**
+     * The set of {@link Role}s associated with this group.
+     */
+    protected ArrayList<Role> roles = new ArrayList<Role>();
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the set of {@link Role}s assigned specifically to this group.
+     */
+    @Override
+    public Iterator<Role> getRoles() {
+
+        synchronized (roles) {
+            return (roles.iterator());
+        }
+
+    }
+
+
+    /**
+     * Return the {@link UserDatabase} within which this Group is defined.
+     */
+    @Override
+    public UserDatabase getUserDatabase() {
+
+        return (this.database);
+
+    }
+
+
+    /**
+     * Return the set of {@link org.apache.catalina.User}s that are members of this group.
+     */
+    @Override
+    public Iterator<User> getUsers() {
+
+        ArrayList<User> results = new ArrayList<User>();
+        Iterator<User> users = database.getUsers();
+        while (users.hasNext()) {
+            User user = users.next();
+            if (user.isInGroup(this)) {
+                results.add(user);
+            }
+        }
+        return (results.iterator());
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add a new {@link Role} to those assigned specifically to this group.
+     *
+     * @param role The new role
+     */
+    @Override
+    public void addRole(Role role) {
+
+        synchronized (roles) {
+            if (!roles.contains(role)) {
+                roles.add(role);
+            }
+        }
+
+    }
+
+
+    /**
+     * Is this group specifically assigned the specified {@link Role}?
+     *
+     * @param role The role to check
+     */
+    @Override
+    public boolean isInRole(Role role) {
+
+        synchronized (roles) {
+            return (roles.contains(role));
+        }
+
+    }
+
+
+    /**
+     * Remove a {@link Role} from those assigned to this group.
+     *
+     * @param role The old role
+     */
+    @Override
+    public void removeRole(Role role) {
+
+        synchronized (roles) {
+            roles.remove(role);
+        }
+
+    }
+
+
+    /**
+     * Remove all {@link Role}s from those assigned to this group.
+     */
+    @Override
+    public void removeRoles() {
+
+        synchronized (roles) {
+            roles.clear();
+        }
+
+    }
+
+
+    /**
+     * <p>Return a String representation of this group in XML format.</p>
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("<group groupname=\"");
+        sb.append(groupname);
+        sb.append("\"");
+        if (description != null) {
+            sb.append(" description=\"");
+            sb.append(description);
+            sb.append("\"");
+        }
+        synchronized (roles) {
+            if (roles.size() > 0) {
+                sb.append(" roles=\"");
+                int n = 0;
+                Iterator<Role> values = roles.iterator();
+                while (values.hasNext()) {
+                    if (n > 0) {
+                        sb.append(',');
+                    }
+                    n++;
+                    sb.append((values.next()).getRolename());
+                }
+                sb.append("\"");
+            }
+        }
+        sb.append("/>");
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/users/MemoryRole.java b/bundles/org.apache.tomcat/src/org/apache/catalina/users/MemoryRole.java
new file mode 100644
index 0000000..7d9ca26
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/users/MemoryRole.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.catalina.users;
+
+
+import org.apache.catalina.UserDatabase;
+
+
+/**
+ * <p>Concrete implementation of {@link org.apache.catalina.Role} for the
+ * {@link MemoryUserDatabase} implementation of {@link UserDatabase}.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: MemoryRole.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ * @since 4.1
+ */
+
+public class MemoryRole extends AbstractRole {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Package-private constructor used by the factory method in
+     * {@link MemoryUserDatabase}.
+     *
+     * @param database The {@link MemoryUserDatabase} that owns this role
+     * @param rolename Role name of this role
+     * @param description Description of this role
+     */
+    MemoryRole(MemoryUserDatabase database,
+               String rolename, String description) {
+
+        super();
+        this.database = database;
+        setRolename(rolename);
+        setDescription(description);
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The {@link MemoryUserDatabase} that owns this role.
+     */
+    protected MemoryUserDatabase database = null;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the {@link UserDatabase} within which this role is defined.
+     */
+    @Override
+    public UserDatabase getUserDatabase() {
+
+        return (this.database);
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * <p>Return a String representation of this role in XML format.</p>
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("<role rolename=\"");
+        sb.append(rolename);
+        sb.append("\"");
+        if (description != null) {
+            sb.append(" description=\"");
+            sb.append(description);
+            sb.append("\"");
+        }
+        sb.append("/>");
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/users/MemoryUser.java b/bundles/org.apache.tomcat/src/org/apache/catalina/users/MemoryUser.java
new file mode 100644
index 0000000..8886122
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/users/MemoryUser.java
@@ -0,0 +1,308 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.users;
+
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.apache.catalina.Group;
+import org.apache.catalina.Role;
+import org.apache.catalina.UserDatabase;
+import org.apache.catalina.util.RequestUtil;
+
+/**
+ * <p>Concrete implementation of {@link org.apache.catalina.User} for the
+ * {@link MemoryUserDatabase} implementation of {@link UserDatabase}.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: MemoryUser.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ * @since 4.1
+ */
+
+public class MemoryUser extends AbstractUser {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Package-private constructor used by the factory method in
+     * {@link MemoryUserDatabase}.
+     *
+     * @param database The {@link MemoryUserDatabase} that owns this user
+     * @param username Logon username of the new user
+     * @param password Logon password of the new user
+     * @param fullName Full name of the new user
+     */
+    MemoryUser(MemoryUserDatabase database, String username,
+               String password, String fullName) {
+
+        super();
+        this.database = database;
+        setUsername(username);
+        setPassword(password);
+        setFullName(fullName);
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The {@link MemoryUserDatabase} that owns this user.
+     */
+    protected MemoryUserDatabase database = null;
+
+
+    /**
+     * The set of {@link Group}s that this user is a member of.
+     */
+    protected ArrayList<Group> groups = new ArrayList<Group>();
+
+
+    /**
+     * The set of {@link Role}s associated with this user.
+     */
+    protected ArrayList<Role> roles = new ArrayList<Role>();
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the set of {@link Group}s to which this user belongs.
+     */
+    @Override
+    public Iterator<Group> getGroups() {
+
+        synchronized (groups) {
+            return (groups.iterator());
+        }
+
+    }
+
+
+    /**
+     * Return the set of {@link Role}s assigned specifically to this user.
+     */
+    @Override
+    public Iterator<Role> getRoles() {
+
+        synchronized (roles) {
+            return (roles.iterator());
+        }
+
+    }
+
+
+    /**
+     * Return the {@link UserDatabase} within which this User is defined.
+     */
+    public UserDatabase getUserDatabase() {
+
+        return (this.database);
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add a new {@link Group} to those this user belongs to.
+     *
+     * @param group The new group
+     */
+    @Override
+    public void addGroup(Group group) {
+
+        synchronized (groups) {
+            if (!groups.contains(group)) {
+                groups.add(group);
+            }
+        }
+
+    }
+
+
+    /**
+     * Add a new {@link Role} to those assigned specifically to this user.
+     *
+     * @param role The new role
+     */
+    @Override
+    public void addRole(Role role) {
+
+        synchronized (roles) {
+            if (!roles.contains(role)) {
+                roles.add(role);
+            }
+        }
+
+    }
+
+
+    /**
+     * Is this user in the specified group?
+     *
+     * @param group The group to check
+     */
+    @Override
+    public boolean isInGroup(Group group) {
+
+        synchronized (groups) {
+            return (groups.contains(group));
+        }
+
+    }
+
+
+    /**
+     * Is this user specifically assigned the specified {@link Role}?  This
+     * method does <strong>NOT</strong> check for roles inherited based on
+     * {@link Group} membership.
+     *
+     * @param role The role to check
+     */
+    @Override
+    public boolean isInRole(Role role) {
+
+        synchronized (roles) {
+            return (roles.contains(role));
+        }
+
+    }
+
+
+    /**
+     * Remove a {@link Group} from those this user belongs to.
+     *
+     * @param group The old group
+     */
+    @Override
+    public void removeGroup(Group group) {
+
+        synchronized (groups) {
+            groups.remove(group);
+        }
+
+    }
+
+
+    /**
+     * Remove all {@link Group}s from those this user belongs to.
+     */
+    @Override
+    public void removeGroups() {
+
+        synchronized (groups) {
+            groups.clear();
+        }
+
+    }
+
+
+    /**
+     * Remove a {@link Role} from those assigned to this user.
+     *
+     * @param role The old role
+     */
+    @Override
+    public void removeRole(Role role) {
+
+        synchronized (roles) {
+            roles.remove(role);
+        }
+
+    }
+
+
+    /**
+     * Remove all {@link Role}s from those assigned to this user.
+     */
+    @Override
+    public void removeRoles() {
+
+        synchronized (roles) {
+            roles.clear();
+        }
+
+    }
+
+
+    /**
+     * <p>Return a String representation of this user in XML format.</p>
+     *
+     * <p><strong>IMPLEMENTATION NOTE</strong> - For backwards compatibility,
+     * the reader that processes this entry will accept either
+     * <code>username</code> or </code>name</code> for the username
+     * property.</p>
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("<user username=\"");
+        sb.append(RequestUtil.filter(username));
+        sb.append("\" password=\"");
+        sb.append(RequestUtil.filter(password));
+        sb.append("\"");
+        if (fullName != null) {
+            sb.append(" fullName=\"");
+            sb.append(RequestUtil.filter(fullName));
+            sb.append("\"");
+        }
+        synchronized (groups) {
+            if (groups.size() > 0) {
+                sb.append(" groups=\"");
+                int n = 0;
+                Iterator<Group> values = groups.iterator();
+                while (values.hasNext()) {
+                    if (n > 0) {
+                        sb.append(',');
+                    }
+                    n++;
+                    sb.append(RequestUtil.filter(values.next().getGroupname()));
+                }
+                sb.append("\"");
+            }
+        }
+        synchronized (roles) {
+            if (roles.size() > 0) {
+                sb.append(" roles=\"");
+                int n = 0;
+                Iterator<Role> values = roles.iterator();
+                while (values.hasNext()) {
+                    if (n > 0) {
+                        sb.append(',');
+                    }
+                    n++;
+                    sb.append(RequestUtil.filter(values.next().getRolename()));
+                }
+                sb.append("\"");
+            }
+        }
+        sb.append("/>");
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/users/MemoryUserDatabase.java b/bundles/org.apache.tomcat/src/org/apache/catalina/users/MemoryUserDatabase.java
new file mode 100644
index 0000000..c24ae99
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/users/MemoryUserDatabase.java
@@ -0,0 +1,833 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.users;
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import org.apache.catalina.Globals;
+import org.apache.catalina.Group;
+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.ExceptionUtils;
+import org.apache.tomcat.util.digester.Digester;
+import org.apache.tomcat.util.digester.ObjectCreationFactory;
+import org.apache.tomcat.util.res.StringManager;
+import org.xml.sax.Attributes;
+
+
+/**
+ * <p>Concrete implementation of {@link UserDatabase} that loads all
+ * defined users, groups, and roles into an in-memory data structure,
+ * and uses a specified XML file for its persistent storage.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: MemoryUserDatabase.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ * @since 4.1
+ */
+
+public class MemoryUserDatabase implements UserDatabase {
+
+
+    private static final Log log = LogFactory.getLog(MemoryUserDatabase.class);
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Create a new instance with default values.
+     */
+    public MemoryUserDatabase() {
+
+        super();
+
+    }
+
+
+    /**
+     * Create a new instance with the specified values.
+     *
+     * @param id Unique global identifier of this user database
+     */
+    public MemoryUserDatabase(String id) {
+
+        super();
+        this.id = id;
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The set of {@link Group}s defined in this database, keyed by
+     * group name.
+     */
+    protected HashMap<String,Group> groups = new HashMap<String,Group>();
+
+
+    /**
+     * The unique global identifier of this user database.
+     */
+    protected String id = null;
+
+
+    /**
+     * The relative (to <code>catalina.base</code>) or absolute pathname to
+     * the XML file in which we will save our persistent information.
+     */
+    protected String pathname = "conf/tomcat-users.xml";
+
+
+    /**
+     * The relative or absolute pathname to the file in which our old
+     * information is stored while renaming is in progress.
+     */
+    protected String pathnameOld = pathname + ".old";
+
+
+    /**
+     * The relative or absolute pathname of the file in which we write
+     * our new information prior to renaming.
+     */
+    protected String pathnameNew = pathname + ".new";
+
+
+    /**
+     * A flag, indicating if the user database is read only.
+     */
+    protected boolean readonly = true;
+
+    /**
+     * The set of {@link Role}s defined in this database, keyed by
+     * role name.
+     */
+    protected HashMap<String,Role> roles = new HashMap<String,Role>();
+
+
+    /**
+     * The string manager for this package.
+     */
+    private static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
+     * The set of {@link User}s defined in this database, keyed by
+     * user name.
+     */
+    protected HashMap<String,User> users = new HashMap<String,User>();
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the set of {@link Group}s defined in this user database.
+     */
+    public Iterator<Group> getGroups() {
+
+        synchronized (groups) {
+            return (groups.values().iterator());
+        }
+
+    }
+
+
+    /**
+     * Return the unique global identifier of this user database.
+     */
+    public String getId() {
+
+        return (this.id);
+
+    }
+
+
+    /**
+     * Return the relative or absolute pathname to the persistent storage file.
+     */
+    public String getPathname() {
+
+        return (this.pathname);
+
+    }
+
+
+    /**
+     * Set the relative or absolute pathname to the persistent storage file.
+     *
+     * @param pathname The new pathname
+     */
+    public void setPathname(String pathname) {
+
+        this.pathname = pathname;
+        this.pathnameOld = pathname + ".old";
+        this.pathnameNew = pathname + ".new";
+
+    }
+
+
+    /**
+     * Returning the readonly status of the user database
+     */
+    public boolean getReadonly() {
+
+        return (this.readonly);
+
+    }
+
+
+    /**
+     * Setting the readonly status of the user database
+     *
+     * @param readonly the new status
+     */
+    public void setReadonly(boolean readonly) {
+
+        this.readonly = readonly;
+
+    }
+
+
+    /**
+     * Return the set of {@link Role}s defined in this user database.
+     */
+    public Iterator<Role> getRoles() {
+
+        synchronized (roles) {
+            return (roles.values().iterator());
+        }
+
+    }
+
+
+    /**
+     * Return the set of {@link User}s defined in this user database.
+     */
+    public Iterator<User> getUsers() {
+
+        synchronized (users) {
+            return (users.values().iterator());
+        }
+
+    }
+
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Finalize access to this user database.
+     *
+     * @exception Exception if any exception is thrown during closing
+     */
+    public void close() throws Exception {
+
+        save();
+
+        synchronized (groups) {
+            synchronized (users) {
+                users.clear();
+                groups.clear();
+            }
+        }
+
+    }
+
+
+    /**
+     * Create and return a new {@link Group} defined in this user database.
+     *
+     * @param groupname The group name of the new group (must be unique)
+     * @param description The description of this group
+     */
+    public Group createGroup(String groupname, String description) {
+
+        if (groupname == null || groupname.length() == 0) {
+            String msg = sm.getString("memoryUserDatabase.nullGroup");
+            log.warn(msg);
+            throw new IllegalArgumentException(msg);
+        }
+
+        MemoryGroup group = new MemoryGroup(this, groupname, description);
+        synchronized (groups) {
+            groups.put(group.getGroupname(), group);
+        }
+        return (group);
+
+    }
+
+
+    /**
+     * Create and return a new {@link Role} defined in this user database.
+     *
+     * @param rolename The role name of the new group (must be unique)
+     * @param description The description of this group
+     */
+    public Role createRole(String rolename, String description) {
+
+        if (rolename == null || rolename.length() == 0) {
+            String msg = sm.getString("memoryUserDatabase.nullRole");
+            log.warn(msg);
+            throw new IllegalArgumentException(msg);
+        }
+
+        MemoryRole role = new MemoryRole(this, rolename, description);
+        synchronized (roles) {
+            roles.put(role.getRolename(), role);
+        }
+        return (role);
+
+    }
+
+
+    /**
+     * Create and return a new {@link User} defined in this user database.
+     *
+     * @param username The logon username of the new user (must be unique)
+     * @param password The logon password of the new user
+     * @param fullName The full name of the new user
+     */
+    public User createUser(String username, String password,
+                           String fullName) {
+
+        if (username == null || username.length() == 0) {
+            String msg = sm.getString("memoryUserDatabase.nullUser");
+            log.warn(msg);
+            throw new IllegalArgumentException(msg);
+        }
+
+        MemoryUser user = new MemoryUser(this, username, password, fullName);
+        synchronized (users) {
+            users.put(user.getUsername(), user);
+        }
+        return (user);
+    }
+
+
+    /**
+     * Return the {@link Group} with the specified group name, if any;
+     * otherwise return <code>null</code>.
+     *
+     * @param groupname Name of the group to return
+     */
+    public Group findGroup(String groupname) {
+
+        synchronized (groups) {
+            return groups.get(groupname);
+        }
+
+    }
+
+
+    /**
+     * Return the {@link Role} with the specified role name, if any;
+     * otherwise return <code>null</code>.
+     *
+     * @param rolename Name of the role to return
+     */
+    public Role findRole(String rolename) {
+
+        synchronized (roles) {
+            return roles.get(rolename);
+        }
+
+    }
+
+
+    /**
+     * Return the {@link User} with the specified user name, if any;
+     * otherwise return <code>null</code>.
+     *
+     * @param username Name of the user to return
+     */
+    public User findUser(String username) {
+
+        synchronized (users) {
+            return users.get(username);
+        }
+
+    }
+
+
+    /**
+     * Initialize access to this user database.
+     *
+     * @exception Exception if any exception is thrown during opening
+     */
+    public void open() throws Exception {
+
+        synchronized (groups) {
+            synchronized (users) {
+
+                // Erase any previous groups and users
+                users.clear();
+                groups.clear();
+                roles.clear();
+
+                // Construct a reader for the XML input file (if it exists)
+                File file = new File(pathname);
+                if (!file.isAbsolute()) {
+                    file = new File(System.getProperty(Globals.CATALINA_BASE_PROP),
+                                    pathname);
+                }
+                if (!file.exists()) {
+                    return;
+                }
+                FileInputStream fis = new FileInputStream(file);
+
+                // Construct a digester to read the XML input file
+                Digester digester = new Digester();
+                try {
+                    digester.setFeature(
+                            "http://apache.org/xml/features/allow-java-encodings",
+                            true);
+                } catch (Exception e) {
+                    log.warn(sm.getString("memoryUserDatabase.xmlFeatureEncoding"), e);
+                }
+                digester.addFactoryCreate
+                    ("tomcat-users/group",
+                     new MemoryGroupCreationFactory(this), true);
+                digester.addFactoryCreate
+                    ("tomcat-users/role",
+                     new MemoryRoleCreationFactory(this), true);
+                digester.addFactoryCreate
+                    ("tomcat-users/user",
+                     new MemoryUserCreationFactory(this), true);
+
+                // Parse the XML input file to load this database
+                try {
+                    digester.parse(fis);
+                    fis.close();
+                } catch (Exception e) {
+                    try {
+                        fis.close();
+                    } catch (Throwable t) {
+                        ExceptionUtils.handleThrowable(t);
+                    }
+                    throw e;
+                }
+
+            }
+        }
+
+    }
+
+
+    /**
+     * Remove the specified {@link Group} from this user database.
+     *
+     * @param group The group to be removed
+     */
+    public void removeGroup(Group group) {
+
+        synchronized (groups) {
+            Iterator<User> users = getUsers();
+            while (users.hasNext()) {
+                User user = users.next();
+                user.removeGroup(group);
+            }
+            groups.remove(group.getGroupname());
+        }
+
+    }
+
+
+    /**
+     * Remove the specified {@link Role} from this user database.
+     *
+     * @param role The role to be removed
+     */
+    public void removeRole(Role role) {
+
+        synchronized (roles) {
+            Iterator<Group> groups = getGroups();
+            while (groups.hasNext()) {
+                Group group = groups.next();
+                group.removeRole(role);
+            }
+            Iterator<User> users = getUsers();
+            while (users.hasNext()) {
+                User user = users.next();
+                user.removeRole(role);
+            }
+            roles.remove(role.getRolename());
+        }
+
+    }
+
+
+    /**
+     * Remove the specified {@link User} from this user database.
+     *
+     * @param user The user to be removed
+     */
+    public void removeUser(User user) {
+
+        synchronized (users) {
+            users.remove(user.getUsername());
+        }
+
+    }
+
+
+    /**
+     * Check for permissions to save this user database
+     * to persistent storage location
+     *
+     */
+    public boolean isWriteable() {
+
+        File file = new File(pathname);
+        if (!file.isAbsolute()) {
+            file = new File(System.getProperty(Globals.CATALINA_BASE_PROP),
+                            pathname);
+        }
+        File dir = file.getParentFile();
+        return dir.exists() && dir.isDirectory() && dir.canWrite();
+
+    }
+
+
+    /**
+     * Save any updated information to the persistent storage location for
+     * this user database.
+     *
+     * @exception Exception if any exception is thrown during saving
+     */
+    public void save() throws Exception {
+
+        if (getReadonly()) {
+            log.error(sm.getString("memoryUserDatabase.readOnly"));
+            return;
+        }
+
+        if (!isWriteable()) {
+            log.warn(sm.getString("memoryUserDatabase.notPersistable"));
+            return;
+        }
+
+        // Write out contents to a temporary file
+        File fileNew = new File(pathnameNew);
+        if (!fileNew.isAbsolute()) {
+            fileNew =
+                new File(System.getProperty(Globals.CATALINA_BASE_PROP), pathnameNew);
+        }
+        PrintWriter writer = null;
+        try {
+
+            // Configure our PrintWriter
+            FileOutputStream fos = new FileOutputStream(fileNew);
+            OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF8");
+            writer = new PrintWriter(osw);
+
+            // Print the file prolog
+            writer.println("<?xml version='1.0' encoding='utf-8'?>");
+            writer.println("<tomcat-users>");
+
+            // Print entries for each defined role, group, and user
+            Iterator<?> values = null;
+            values = getRoles();
+            while (values.hasNext()) {
+                writer.print("  ");
+                writer.println(values.next());
+            }
+            values = getGroups();
+            while (values.hasNext()) {
+                writer.print("  ");
+                writer.println(values.next());
+            }
+            values = getUsers();
+            while (values.hasNext()) {
+                writer.print("  ");
+                writer.println(values.next());
+            }
+
+            // Print the file epilog
+            writer.println("</tomcat-users>");
+
+            // Check for errors that occurred while printing
+            if (writer.checkError()) {
+                writer.close();
+                fileNew.delete();
+                throw new IOException
+                    (sm.getString("memoryUserDatabase.writeException",
+                                  fileNew.getAbsolutePath()));
+            }
+            writer.close();
+        } catch (IOException e) {
+            if (writer != null) {
+                writer.close();
+            }
+            fileNew.delete();
+            throw e;
+        }
+
+        // Perform the required renames to permanently save this file
+        File fileOld = new File(pathnameOld);
+        if (!fileOld.isAbsolute()) {
+            fileOld =
+                new File(System.getProperty(Globals.CATALINA_BASE_PROP), pathnameOld);
+        }
+        fileOld.delete();
+        File fileOrig = new File(pathname);
+        if (!fileOrig.isAbsolute()) {
+            fileOrig =
+                new File(System.getProperty(Globals.CATALINA_BASE_PROP), pathname);
+        }
+        if (fileOrig.exists()) {
+            fileOld.delete();
+            if (!fileOrig.renameTo(fileOld)) {
+                throw new IOException
+                    (sm.getString("memoryUserDatabase.renameOld",
+                                  fileOld.getAbsolutePath()));
+            }
+        }
+        if (!fileNew.renameTo(fileOrig)) {
+            if (fileOld.exists()) {
+                fileOld.renameTo(fileOrig);
+            }
+            throw new IOException
+                (sm.getString("memoryUserDatabase.renameNew",
+                              fileOrig.getAbsolutePath()));
+        }
+        fileOld.delete();
+
+    }
+
+
+    /**
+     * Return a String representation of this UserDatabase.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("MemoryUserDatabase[id=");
+        sb.append(this.id);
+        sb.append(",pathname=");
+        sb.append(pathname);
+        sb.append(",groupCount=");
+        sb.append(this.groups.size());
+        sb.append(",roleCount=");
+        sb.append(this.roles.size());
+        sb.append(",userCount=");
+        sb.append(this.users.size());
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+    // -------------------------------------------------------- Package Methods
+
+
+    /**
+     * Return the <code>StringManager</code> for use in looking up messages.
+     */
+    StringManager getStringManager() {
+
+        return (sm);
+
+    }
+
+
+}
+
+
+
+/**
+ * Digester object creation factory for group instances.
+ */
+class MemoryGroupCreationFactory implements ObjectCreationFactory {
+
+    public MemoryGroupCreationFactory(MemoryUserDatabase database) {
+        this.database = database;
+    }
+
+    public Object createObject(Attributes attributes) {
+        String groupname = attributes.getValue("groupname");
+        if (groupname == null) {
+            groupname = attributes.getValue("name");
+        }
+        String description = attributes.getValue("description");
+        String roles = attributes.getValue("roles");
+        Group group = database.createGroup(groupname, description);
+        if (roles != null) {
+            while (roles.length() > 0) {
+                String rolename = null;
+                int comma = roles.indexOf(',');
+                if (comma >= 0) {
+                    rolename = roles.substring(0, comma).trim();
+                    roles = roles.substring(comma + 1);
+                } else {
+                    rolename = roles.trim();
+                    roles = "";
+                }
+                if (rolename.length() > 0) {
+                    Role role = database.findRole(rolename);
+                    if (role == null) {
+                        role = database.createRole(rolename, null);
+                    }
+                    group.addRole(role);
+                }
+            }
+        }
+        return (group);
+    }
+
+    private MemoryUserDatabase database = null;
+
+    private Digester digester = null;
+
+    public Digester getDigester() {
+        return (this.digester);
+    }
+
+    public void setDigester(Digester digester) {
+        this.digester = digester;
+    }
+
+}
+
+
+/**
+ * Digester object creation factory for role instances.
+ */
+class MemoryRoleCreationFactory implements ObjectCreationFactory {
+
+    public MemoryRoleCreationFactory(MemoryUserDatabase database) {
+        this.database = database;
+    }
+
+    public Object createObject(Attributes attributes) {
+        String rolename = attributes.getValue("rolename");
+        if (rolename == null) {
+            rolename = attributes.getValue("name");
+        }
+        String description = attributes.getValue("description");
+        Role role = database.createRole(rolename, description);
+        return (role);
+    }
+
+    private MemoryUserDatabase database = null;
+
+    private Digester digester = null;
+
+    public Digester getDigester() {
+        return (this.digester);
+    }
+
+    public void setDigester(Digester digester) {
+        this.digester = digester;
+    }
+
+}
+
+
+/**
+ * Digester object creation factory for user instances.
+ */
+class MemoryUserCreationFactory implements ObjectCreationFactory {
+
+    public MemoryUserCreationFactory(MemoryUserDatabase database) {
+        this.database = database;
+    }
+
+    public Object createObject(Attributes attributes) {
+        String username = attributes.getValue("username");
+        if (username == null) {
+            username = attributes.getValue("name");
+        }
+        String password = attributes.getValue("password");
+        String fullName = attributes.getValue("fullName");
+        if (fullName == null) {
+            fullName = attributes.getValue("fullname");
+        }
+        String groups = attributes.getValue("groups");
+        String roles = attributes.getValue("roles");
+        User user = database.createUser(username, password, fullName);
+        if (groups != null) {
+            while (groups.length() > 0) {
+                String groupname = null;
+                int comma = groups.indexOf(',');
+                if (comma >= 0) {
+                    groupname = groups.substring(0, comma).trim();
+                    groups = groups.substring(comma + 1);
+                } else {
+                    groupname = groups.trim();
+                    groups = "";
+                }
+                if (groupname.length() > 0) {
+                    Group group = database.findGroup(groupname);
+                    if (group == null) {
+                        group = database.createGroup(groupname, null);
+                    }
+                    user.addGroup(group);
+                }
+            }
+        }
+        if (roles != null) {
+            while (roles.length() > 0) {
+                String rolename = null;
+                int comma = roles.indexOf(',');
+                if (comma >= 0) {
+                    rolename = roles.substring(0, comma).trim();
+                    roles = roles.substring(comma + 1);
+                } else {
+                    rolename = roles.trim();
+                    roles = "";
+                }
+                if (rolename.length() > 0) {
+                    Role role = database.findRole(rolename);
+                    if (role == null) {
+                        role = database.createRole(rolename, null);
+                    }
+                    user.addRole(role);
+                }
+            }
+        }
+        return (user);
+    }
+
+    private MemoryUserDatabase database = null;
+
+    private Digester digester = null;
+
+    public Digester getDigester() {
+        return (this.digester);
+    }
+
+    public void setDigester(Digester digester) {
+        this.digester = digester;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/users/MemoryUserDatabaseFactory.java b/bundles/org.apache.tomcat/src/org/apache/catalina/users/MemoryUserDatabaseFactory.java
new file mode 100644
index 0000000..50d5e65
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/users/MemoryUserDatabaseFactory.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.users;
+
+
+import java.util.Hashtable;
+
+import javax.naming.Context;
+import javax.naming.Name;
+import javax.naming.RefAddr;
+import javax.naming.Reference;
+import javax.naming.spi.ObjectFactory;
+
+
+/**
+ * <p>JNDI object creation factory for <code>MemoryUserDatabase</code>
+ * instances.  This makes it convenient to configure a user database
+ * in the global JNDI resources associated with this Catalina instance,
+ * and then link to that resource for web applications that administer
+ * the contents of the user database.</p>
+ *
+ * <p>The <code>MemoryUserDatabase</code> instance is configured based
+ * on the following parameter values:</p>
+ * <ul>
+ * <li><strong>pathname</strong> - Absolute or relative (to the directory
+ *     path specified by the <code>catalina.base</code> system property)
+ *     pathname to the XML file from which our user information is loaded,
+ *     and to which it is stored.  [conf/tomcat-users.xml]</li>
+ * </ul>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: MemoryUserDatabaseFactory.java,v 1.1 2011/06/28 21:08:20 rherrmann Exp $
+ * @since 4.1
+ */
+
+public class MemoryUserDatabaseFactory implements ObjectFactory {
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * <p>Create and return a new <code>MemoryUserDatabase</code> instance
+     * that has been configured according to the properties of the
+     * specified <code>Reference</code>.  If you instance can be created,
+     * return <code>null</code> instead.</p>
+     *
+     * @param obj The possibly null object containing location or
+     *  reference information that can be used in creating an object
+     * @param name The name of this object relative to <code>nameCtx</code>
+     * @param nameCtx The context relative to which the <code>name</code>
+     *  parameter is specified, or <code>null</code> if <code>name</code>
+     *  is relative to the default initial context
+     * @param environment The possibly null environment that is used in
+     *  creating this object
+     */
+    public Object getObjectInstance(Object obj, Name name, Context nameCtx,
+                                    Hashtable<?,?> environment)
+        throws Exception {
+
+        // We only know how to deal with <code>javax.naming.Reference</code>s
+        // that specify a class name of "org.apache.catalina.UserDatabase"
+        if ((obj == null) || !(obj instanceof Reference)) {
+            return (null);
+        }
+        Reference ref = (Reference) obj;
+        if (!"org.apache.catalina.UserDatabase".equals(ref.getClassName())) {
+            return (null);
+        }
+
+        // Create and configure a MemoryUserDatabase instance based on the
+        // RefAddr values associated with this Reference
+        MemoryUserDatabase database = new MemoryUserDatabase(name.toString());
+        RefAddr ra = null;
+
+        ra = ref.get("pathname");
+        if (ra != null) {
+            database.setPathname(ra.getContent().toString());
+        }
+
+        ra = ref.get("readonly");
+        if (ra != null) {
+            database.setReadonly(Boolean.valueOf(ra.getContent().toString()).booleanValue());
+        }
+
+        // Return the configured database instance
+        database.open();
+        // Don't try something we know won't work
+        if (!database.getReadonly())
+            database.save();
+        return (database);
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/users/mbeans-descriptors.xml b/bundles/org.apache.tomcat/src/org/apache/catalina/users/mbeans-descriptors.xml
new file mode 100644
index 0000000..504f706
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/users/mbeans-descriptors.xml
@@ -0,0 +1,155 @@
+<?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="MemoryUserDatabase"
+            className="org.apache.catalina.mbeans.MemoryUserDatabaseMBean"
+          description="In-memory user and group database"
+               domain="Users"
+                group="UserDatabase"
+                 type="org.apache.catalina.users.MemoryUserDatabase">
+
+    <attribute   name="groups"
+          description="MBean Names of all defined groups"
+                 type="[Ljava.lang.String;"
+            writeable="false"/>
+
+    <attribute   name="pathname"
+          description="Relative or absolute pathname to database file"
+                 type="java.lang.String"/>
+
+    <attribute   name="roles"
+          description="MBean Names of all defined roles"
+                 type="[Ljava.lang.String;"
+            writeable="false"/>
+
+    <attribute   name="users"
+          description="MBean Names of all defined users"
+                 type="[Ljava.lang.String;"
+            writeable="false"/>
+
+    <attribute   name="readonly"
+          description="No persistant save of the user database"
+                 type="boolean"
+            writeable="false"/>
+
+    <attribute   name="writeable"
+          description="Check if user database is writeable"
+               impact="INFO"
+                   is="true"
+           writeable="false"/>
+
+    <operation   name="createGroup"
+          description="Create new group and return MBean name"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="groupname"
+          description="Group name of the new group"
+                 type="java.lang.String"/>
+      <parameter name="description"
+          description="Description of the new group"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="createRole"
+          description="Create new role and return MBean name"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="rolename"
+          description="Role name of the new role"
+                 type="java.lang.String"/>
+      <parameter name="description"
+          description="Description of the new role"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="createUser"
+          description="Create new user and return MBean name"
+               impact="ACTION"
+           returnType="java.lang.String">
+      <parameter name="username"
+          description="User name of the new user"
+                 type="java.lang.String"/>
+      <parameter name="password"
+          description="Password of the new user"
+                 type="java.lang.String"/>
+      <parameter name="fullName"
+          description="Full name of the new user"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="findGroup"
+          description="Return MBean Name of the specified group (if any)"
+               impact="INFO"
+           returnType="java.lang.String">
+      <parameter name="groupname"
+          description="Group name of the requested group"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="findRole"
+          description="Return MBean Name of the specified role (if any)"
+               impact="INFO"
+           returnType="java.lang.String">
+      <parameter name="rolename"
+          description="Role name of the requested role"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="findUser"
+          description="Return MBean Name of the specified user (if any)"
+               impact="INFO"
+           returnType="java.lang.String">
+      <parameter name="username"
+          description="User name of the requested user"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="removeGroup"
+          description="Remove existing group (and all user memberships)"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="groupname"
+          description="Group name of the group to remove"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="removeRole"
+          description="Remove existing role"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="rolename"
+          description="Role name of the role to remove"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="removeUser"
+          description="Remove existing user (and all group memberships)"
+               impact="ACTION"
+           returnType="void">
+      <parameter name="username"
+          description="User name of the user to remove"
+                 type="java.lang.String"/>
+    </operation>
+
+    <operation   name="save"
+          description="Save current users and groups to persistent storage"
+               impact="ACTION"
+           returnType="void">
+    </operation>
+  </mbean>
+</mbeans-descriptors>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/Base64.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/Base64.java
new file mode 100644
index 0000000..a65a787
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/Base64.java
@@ -0,0 +1,374 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.util;
+
+import java.io.UnsupportedEncodingException;
+
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.CharChunk;
+
+/**
+ * This class provides encode/decode for RFC 2045 Base64 as defined by
+ * RFC 2045, N. Freed and N. Borenstein.  <a
+ * href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>:
+ * Multipurpose Internet Mail Extensions (MIME) Part One: Format of
+ * Internet Message Bodies. Reference 1996
+ *
+ * @author Jeffrey Rodriguez
+ * @version $Id: Base64.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+public final class  Base64
+{
+    private static final int  BASELENGTH         = 255;
+    private static final int  LOOKUPLENGTH       = 64;
+    private static final int  TWENTYFOURBITGROUP = 24;
+    private static final int  EIGHTBIT           = 8;
+    private static final int  SIXTEENBIT         = 16;
+    private static final int  FOURBYTE           = 4;
+    private static final int  SIGN               = -128;
+    private static final byte PAD                = (byte) '=';
+    private static byte [] base64Alphabet       = new byte[BASELENGTH];
+    private static byte [] lookUpBase64Alphabet = new byte[LOOKUPLENGTH];
+
+    static
+    {
+        for (int i = 0; i < BASELENGTH; i++ )
+        {
+            base64Alphabet[i] = -1;
+        }
+        for (int i = 'Z'; i >= 'A'; i--)
+        {
+            base64Alphabet[i] = (byte) (i - 'A');
+        }
+        for (int i = 'z'; i>= 'a'; i--)
+        {
+            base64Alphabet[i] = (byte) (i - 'a' + 26);
+        }
+        for (int i = '9'; i >= '0'; i--)
+        {
+            base64Alphabet[i] = (byte) (i - '0' + 52);
+        }
+
+        base64Alphabet['+']  = 62;
+        base64Alphabet['/']  = 63;
+
+        for (int i = 0; i <= 25; i++ )
+            lookUpBase64Alphabet[i] = (byte) ('A' + i);
+
+        for (int i = 26,  j = 0; i <= 51; i++, j++ )
+            lookUpBase64Alphabet[i] = (byte) ('a'+ j);
+
+        for (int i = 52,  j = 0; i <= 61; i++, j++ )
+            lookUpBase64Alphabet[i] = (byte) ('0' + j);
+
+        lookUpBase64Alphabet[62] = (byte) '+';
+        lookUpBase64Alphabet[63] = (byte) '/';
+    }
+
+    public static boolean isBase64( String isValidString )
+    {
+        return isArrayByteBase64(isValidString.getBytes());
+    }
+
+    public static boolean isBase64( byte octect )
+    {
+        //shall we ignore white space? JEFF??
+        return (octect == PAD || base64Alphabet[octect] != -1);
+    }
+
+    public static boolean isArrayByteBase64( byte[] arrayOctect )
+    {
+        int length = arrayOctect.length;
+        if (length == 0)
+        {
+            // shouldn't a 0 length array be valid base64 data?
+            // return false;
+            return true;
+        }
+        for (int i=0; i < length; i++)
+        {
+            if ( !Base64.isBase64(arrayOctect[i]) )
+                return false;
+        }
+        return true;
+    }
+
+    /**
+     * Encodes hex octets into Base64.
+     *
+     * @param binaryData Array containing binary data to encode.
+     * @return Base64-encoded data.
+     */
+    public static String encode(byte[] binaryData) {
+        int      lengthDataBits    = binaryData.length*EIGHTBIT;
+        int      fewerThan24bits   = lengthDataBits%TWENTYFOURBITGROUP;
+        int      numberTriplets    = lengthDataBits/TWENTYFOURBITGROUP;
+        byte     encodedData[]     = null;
+
+
+        if (fewerThan24bits != 0)
+        {
+            //data not divisible by 24 bit
+            encodedData = new byte[ (numberTriplets + 1 ) * 4 ];
+        }
+        else
+        {
+            // 16 or 8 bit
+            encodedData = new byte[ numberTriplets * 4 ];
+        }
+
+        byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0;
+
+        int encodedIndex = 0;
+        int dataIndex   = 0;
+        int i           = 0;
+        //log.debug("number of triplets = " + numberTriplets);
+        for ( i = 0; i<numberTriplets; i++ )
+        {
+            dataIndex = i*3;
+            b1 = binaryData[dataIndex];
+            b2 = binaryData[dataIndex + 1];
+            b3 = binaryData[dataIndex + 2];
+
+            //log.debug("b1= " + b1 +", b2= " + b2 + ", b3= " + b3);
+
+            l  = (byte)(b2 & 0x0f);
+            k  = (byte)(b1 & 0x03);
+
+            encodedIndex = i * 4;
+            byte val1 = ((b1 & SIGN)==0)?(byte)(b1>>2):(byte)((b1)>>2^0xc0);
+            byte val2 = ((b2 & SIGN)==0)?(byte)(b2>>4):(byte)((b2)>>4^0xf0);
+            byte val3 = ((b3 & SIGN)==0)?(byte)(b3>>6):(byte)((b3)>>6^0xfc);
+
+            encodedData[encodedIndex]   = lookUpBase64Alphabet[ val1 ];
+            //log.debug( "val2 = " + val2 );
+            //log.debug( "k4   = " + (k<<4) );
+            //log.debug(  "vak  = " + (val2 | (k<<4)) );
+            encodedData[encodedIndex+1] =
+                lookUpBase64Alphabet[ val2 | ( k<<4 )];
+            encodedData[encodedIndex+2] =
+                lookUpBase64Alphabet[ (l <<2 ) | val3 ];
+            encodedData[encodedIndex+3] = lookUpBase64Alphabet[ b3 & 0x3f ];
+        }
+
+        // form integral number of 6-bit groups
+        dataIndex    = i*3;
+        encodedIndex = i*4;
+        if (fewerThan24bits == EIGHTBIT )
+        {
+            b1 = binaryData[dataIndex];
+            k = (byte) ( b1 &0x03 );
+            //log.debug("b1=" + b1);
+            //log.debug("b1<<2 = " + (b1>>2) );
+            byte val1 = ((b1 & SIGN)==0)?(byte)(b1>>2):(byte)((b1)>>2^0xc0);
+            encodedData[encodedIndex]     = lookUpBase64Alphabet[ val1 ];
+            encodedData[encodedIndex + 1] = lookUpBase64Alphabet[ k<<4 ];
+            encodedData[encodedIndex + 2] = PAD;
+            encodedData[encodedIndex + 3] = PAD;
+        }
+        else if (fewerThan24bits == SIXTEENBIT)
+        {
+
+            b1 = binaryData[dataIndex];
+            b2 = binaryData[dataIndex +1 ];
+            l = (byte) (b2 & 0x0f);
+            k = (byte) (b1 & 0x03);
+
+            byte val1 = ((b1 & SIGN) == 0)?(byte)(b1>>2):(byte)((b1)>>2^0xc0);
+            byte val2 = ((b2 & SIGN) == 0)?(byte)(b2>>4):(byte)((b2)>>4^0xf0);
+
+            encodedData[encodedIndex]     = lookUpBase64Alphabet[ val1 ];
+            encodedData[encodedIndex + 1] =
+                lookUpBase64Alphabet[ val2 | ( k<<4 )];
+            encodedData[encodedIndex + 2] = lookUpBase64Alphabet[ l<<2 ];
+            encodedData[encodedIndex + 3] = PAD;
+        }
+
+        String result;
+        try {
+            result = new String(encodedData, "ISO-8859-1");
+        } catch (UnsupportedEncodingException e) {
+            // Should never happen but in case it does...
+            result = new String(encodedData);
+        }
+        return result;
+    }
+
+    /**
+     * Decodes Base64 data into octets
+     *
+     * @param base64DataBC Byte array containing Base64 data
+     * @param decodedDataCC The decoded data chars
+     */
+    public static void decode( ByteChunk base64DataBC, CharChunk decodedDataCC)
+    {
+        int start = base64DataBC.getStart();
+        int end = base64DataBC.getEnd();
+        byte[] base64Data = base64DataBC.getBuffer();
+        
+        decodedDataCC.recycle();
+        
+        // handle the edge case, so we don't have to worry about it later
+        if(end - start == 0) { return; }
+
+        int      numberQuadruple    = (end - start)/FOURBYTE;
+        byte     b1=0,b2=0,b3=0, b4=0, marker0=0, marker1=0;
+
+        // Throw away anything not in base64Data
+
+        int encodedIndex = 0;
+        int dataIndex = start;
+        char[] decodedData = null;
+        
+        {
+            // this sizes the output array properly - rlw
+            int lastData = end - start;
+            // ignore the '=' padding
+            while (base64Data[start+lastData-1] == PAD)
+            {
+                if (--lastData == 0)
+                {
+                    return;
+                }
+            }
+            decodedDataCC.allocate(lastData - numberQuadruple, -1);
+            decodedDataCC.setEnd(lastData - numberQuadruple);
+            decodedData = decodedDataCC.getBuffer();
+        }
+
+        for (int i = 0; i < numberQuadruple; i++)
+        {
+            dataIndex = start + i * 4;
+            marker0   = base64Data[dataIndex + 2];
+            marker1   = base64Data[dataIndex + 3];
+
+            b1 = base64Alphabet[base64Data[dataIndex]];
+            b2 = base64Alphabet[base64Data[dataIndex +1]];
+
+            if (marker0 != PAD && marker1 != PAD)
+            {
+                //No PAD e.g 3cQl
+                b3 = base64Alphabet[ marker0 ];
+                b4 = base64Alphabet[ marker1 ];
+
+                decodedData[encodedIndex]   = (char) ((  b1 <<2 | b2>>4 ) & 0xff);
+                decodedData[encodedIndex + 1] =
+                    (char) ((((b2 & 0xf)<<4 ) |( (b3>>2) & 0xf) ) & 0xff);
+                decodedData[encodedIndex + 2] = (char) (( b3<<6 | b4 ) & 0xff);
+            }
+            else if (marker0 == PAD)
+            {
+                //Two PAD e.g. 3c[Pad][Pad]
+                decodedData[encodedIndex]   = (char) ((  b1 <<2 | b2>>4 ) & 0xff);
+            }
+            else if (marker1 == PAD)
+            {
+                //One PAD e.g. 3cQ[Pad]
+                b3 = base64Alphabet[ marker0 ];
+
+                decodedData[encodedIndex]   = (char) ((  b1 <<2 | b2>>4 ) & 0xff);
+                decodedData[encodedIndex + 1] =
+                    (char) ((((b2 & 0xf)<<4 ) |( (b3>>2) & 0xf) ) & 0xff);
+            }
+            encodedIndex += 3;
+        }
+    }
+
+
+    /**
+     * Decodes Base64 data into octets
+     *
+     * @param base64DataBC Byte array containing Base64 data
+     * @param decodedDataBC The decoded data bytes
+     */
+    public static void decode( ByteChunk base64DataBC, ByteChunk decodedDataBC)
+    {
+        int start = base64DataBC.getStart();
+        int end = base64DataBC.getEnd();
+        byte[] base64Data = base64DataBC.getBuffer();
+        
+        decodedDataBC.recycle();
+        
+        // handle the edge case, so we don't have to worry about it later
+        if(end - start == 0) { return; }
+
+        int      numberQuadruple    = (end - start)/FOURBYTE;
+        byte     b1=0,b2=0,b3=0, b4=0, marker0=0, marker1=0;
+
+        // Throw away anything not in base64Data
+
+        int encodedIndex = 0;
+        int dataIndex = start;
+        byte[] decodedData = null;
+        
+        {
+            // this sizes the output array properly - rlw
+            int lastData = end - start;
+            // ignore the '=' padding
+            while (base64Data[start+lastData-1] == PAD)
+            {
+                if (--lastData == 0)
+                {
+                    return;
+                }
+            }
+            decodedDataBC.allocate(lastData - numberQuadruple, -1);
+            decodedDataBC.setEnd(lastData - numberQuadruple);
+            decodedData = decodedDataBC.getBuffer();
+        }
+
+        for (int i = 0; i < numberQuadruple; i++)
+        {
+            dataIndex = start + i * 4;
+            marker0   = base64Data[dataIndex + 2];
+            marker1   = base64Data[dataIndex + 3];
+
+            b1 = base64Alphabet[base64Data[dataIndex]];
+            b2 = base64Alphabet[base64Data[dataIndex +1]];
+
+            if (marker0 != PAD && marker1 != PAD)
+            {
+                //No PAD e.g 3cQl
+                b3 = base64Alphabet[ marker0 ];
+                b4 = base64Alphabet[ marker1 ];
+
+                decodedData[encodedIndex]   = (byte) ((  b1 <<2 | b2>>4 ) & 0xff);
+                decodedData[encodedIndex + 1] =
+                    (byte) ((((b2 & 0xf)<<4 ) |( (b3>>2) & 0xf) ) & 0xff);
+                decodedData[encodedIndex + 2] = (byte) (( b3<<6 | b4 ) & 0xff);
+            }
+            else if (marker0 == PAD)
+            {
+                //Two PAD e.g. 3c[Pad][Pad]
+                decodedData[encodedIndex]   = (byte) ((  b1 <<2 | b2>>4 ) & 0xff);
+            }
+            else if (marker1 == PAD)
+            {
+                //One PAD e.g. 3cQ[Pad]
+                b3 = base64Alphabet[ marker0 ];
+
+                decodedData[encodedIndex]   = (byte) ((  b1 <<2 | b2>>4 ) & 0xff);
+                decodedData[encodedIndex + 1] =
+                    (byte) ((((b2 & 0xf)<<4 ) |( (b3>>2) & 0xf) ) & 0xff);
+            }
+            encodedIndex += 3;
+        }
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/CharsetMapper.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/CharsetMapper.java
new file mode 100644
index 0000000..75bee8b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/CharsetMapper.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.util;
+
+
+import java.io.InputStream;
+import java.util.Locale;
+import java.util.Properties;
+
+import org.apache.tomcat.util.ExceptionUtils;
+
+
+
+/**
+ * Utility class that attempts to map from a Locale to the corresponding
+ * character set to be used for interpreting input text (or generating
+ * output text) when the Content-Type header does not include one.  You
+ * can customize the behavior of this class by modifying the mapping data
+ * it loads, or by subclassing it (to change the algorithm) and then using
+ * your own version for a particular web application.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: CharsetMapper.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public class CharsetMapper {
+
+
+    // ---------------------------------------------------- Manifest Constants
+
+
+    /**
+     * Default properties resource name.
+     */
+    public static final String DEFAULT_RESOURCE =
+      "/org/apache/catalina/util/CharsetMapperDefault.properties";
+
+
+    // ---------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new CharsetMapper using the default properties resource.
+     */
+    public CharsetMapper() {
+        this(DEFAULT_RESOURCE);
+    }
+
+
+    /**
+     * Construct a new CharsetMapper using the specified properties resource.
+     *
+     * @param name Name of a properties resource to be loaded
+     *
+     * @exception IllegalArgumentException if the specified properties
+     *  resource could not be loaded for any reason.
+     */
+    public CharsetMapper(String name) {
+        try {
+            InputStream stream =
+              this.getClass().getResourceAsStream(name);
+            map.load(stream);
+            stream.close();
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            throw new IllegalArgumentException(t.toString());
+        }
+    }
+
+
+    // ---------------------------------------------------- Instance Variables
+
+
+    /**
+     * The mapping properties that have been initialized from the specified or
+     * default properties resource.
+     */
+    private Properties map = new Properties();
+
+
+    // ------------------------------------------------------- Public Methods
+
+
+    /**
+     * Calculate the name of a character set to be assumed, given the specified
+     * Locale and the absence of a character set specified as part of the
+     * content type header.
+     *
+     * @param locale The locale for which to calculate a character set
+     */
+    public String getCharset(Locale locale) {
+        // Match full language_country_variant first, then language_country, 
+        // then language only
+        String charset = map.getProperty(locale.toString());
+        if (charset == null) {
+            charset = map.getProperty(locale.getLanguage() + "_" 
+                    + locale.getCountry());
+            if (charset == null) {
+                charset = map.getProperty(locale.getLanguage());
+            }
+        }
+        return (charset);
+    }
+
+    
+    /**
+     * The deployment descriptor can have a
+     * locale-encoding-mapping-list element which describes the
+     * webapp's desired mapping from locale to charset.  This method
+     * gets called when processing the web.xml file for a context
+     *
+     * @param locale The locale for a character set
+     * @param charset The charset to be associated with the locale
+     */
+    public void addCharsetMappingFromDeploymentDescriptor(String locale, String charset) {
+        map.put(locale, charset);
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/CharsetMapperDefault.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/util/CharsetMapperDefault.properties
new file mode 100644
index 0000000..6f8bf49
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/CharsetMapperDefault.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.
+
+en=ISO-8859-1
+fr=ISO-8859-1
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/ContextName.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/ContextName.java
new file mode 100644
index 0000000..68797c5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/ContextName.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.util;
+
+import java.util.Locale;
+
+/**
+ * Utility class to manage context names so there is one place where the
+ * conversions between baseName, path and version take place.
+ */
+public final class ContextName {
+    private static final String ROOT_NAME = "ROOT";
+    private static final String VERSION_MARKER = "##";
+    private static final String FWD_SLASH_REPLACEMENT = "#";
+
+    private final String baseName;
+    private final String path;
+    private final String version;
+    private final String name;
+    
+    /**
+     * Creates an instance from a context name, display name, base name,
+     * directory name, WAR name or context.xml name.
+     * 
+     * @param name  The name to use as the basis for this object
+     */
+    public ContextName(String name) {
+        
+        String tmp1 = name;
+        
+        // Convert Context names and display names to base names
+        
+        // Strip off any leading "/"
+        if (tmp1.startsWith("/")) {
+            tmp1 = tmp1.substring(1);
+        }
+        
+        // Replace any remaining /
+        tmp1 = tmp1.replaceAll("/", FWD_SLASH_REPLACEMENT);
+        
+        // Insert the ROOT name if required
+        if (tmp1.startsWith(VERSION_MARKER) || "".equals(tmp1)) {
+            tmp1 = ROOT_NAME + tmp1;
+        }
+
+        // Remove any file extensions
+        if (tmp1.toLowerCase(Locale.ENGLISH).endsWith(".war") ||
+                tmp1.toLowerCase(Locale.ENGLISH).endsWith(".xml")) {
+            tmp1 = tmp1.substring(0, tmp1.length() -4);
+        }
+
+        baseName = tmp1;
+        
+        String tmp2;
+        // Extract version number
+        int versionIndex = baseName.indexOf(VERSION_MARKER);
+        if (versionIndex > -1) {
+            version = baseName.substring(versionIndex + 2);
+            tmp2 = baseName.substring(0, versionIndex);
+        } else {
+            version = "";
+            tmp2 = baseName;
+        }
+
+        if (ROOT_NAME.equals(tmp2)) {
+            path = "";
+        } else {
+            path = "/" + tmp2.replaceAll(FWD_SLASH_REPLACEMENT, "/");
+        }
+        
+        if (versionIndex > -1) {
+            this.name = path + VERSION_MARKER + version;
+        } else {
+            this.name = path;
+        }
+    }
+    
+    /**
+     * Construct an instance from a path and version.
+     * 
+     * @param path      Context path to use
+     * @param version   Context version to use
+     */
+    public ContextName(String path, String version) {
+        // Path should never be null or '/'
+        if (path == null || "/".equals(path)) {
+            this.path = "";
+        } else {
+            this.path = path;
+        }
+
+        // Version should never be null
+        if (version == null) {
+            this.version = "";
+        } else {
+            this.version = version;
+        }
+        
+        // Name is path + version
+        if ("".equals(this.version)) {
+            name = this.path;
+        } else {
+            name = this.path + VERSION_MARKER + this.version;
+        }
+
+        // Base name is converted path + version
+        StringBuilder tmp = new StringBuilder();
+        if ("".equals(this.path)) {
+            tmp.append(ROOT_NAME);
+        } else {
+            tmp.append(this.path.substring(1).replaceAll("/",
+                    FWD_SLASH_REPLACEMENT));
+        }
+        if (this.version.length() > 0) {
+            tmp.append(VERSION_MARKER);
+            tmp.append(this.version);
+        }
+        this.baseName = tmp.toString();
+    }
+
+    public String getBaseName() {
+        return baseName;
+    }
+    
+    public String getPath() {
+        return path;
+    }
+    
+    public String getVersion() {
+        return version;
+    }
+    
+    public String getName() {
+        return name;
+    }
+
+    public String getDisplayName() {
+        StringBuilder tmp = new StringBuilder();
+        if ("".equals(path)) {
+            tmp.append('/');
+        } else {
+            tmp.append(path);
+        }
+        
+        if (!"".equals(version)) {
+            tmp.append(VERSION_MARKER);
+            tmp.append(version);
+        }
+        
+        return tmp.toString();
+    }
+    
+    @Override
+    public String toString() {
+        return getDisplayName();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/CustomObjectInputStream.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/CustomObjectInputStream.java
new file mode 100644
index 0000000..0477c7c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/CustomObjectInputStream.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.catalina.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+import java.lang.reflect.Proxy;
+
+/**
+ * Custom subclass of <code>ObjectInputStream</code> that loads from the
+ * class loader for this web application.  This allows classes defined only
+ * with the web application to be found correctly.
+ *
+ * @author Craig R. McClanahan
+ * @author Bip Thelin
+ * @version $Id: CustomObjectInputStream.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+
+public final class CustomObjectInputStream
+    extends ObjectInputStream {
+
+
+    /**
+     * The class loader we will use to resolve classes.
+     */
+    private ClassLoader classLoader = null;
+
+
+    /**
+     * Construct a new instance of CustomObjectInputStream
+     *
+     * @param stream The input stream we will read from
+     * @param classLoader The class loader used to instantiate objects
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public CustomObjectInputStream(InputStream stream,
+                                   ClassLoader classLoader)
+        throws IOException {
+
+        super(stream);
+        this.classLoader = classLoader;
+    }
+
+
+    /**
+     * Load the local class equivalent of the specified stream class
+     * description, by using the class loader assigned to this Context.
+     *
+     * @param classDesc Class description from the input stream
+     *
+     * @exception ClassNotFoundException if this class cannot be found
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public Class<?> resolveClass(ObjectStreamClass classDesc)
+        throws ClassNotFoundException, IOException {
+        try {
+            return Class.forName(classDesc.getName(), false, classLoader);
+        } catch (ClassNotFoundException e) {
+            try {
+                // Try also the superclass because of primitive types
+                return super.resolveClass(classDesc);
+            } catch (ClassNotFoundException e2) {
+                // Rethrow original exception, as it can have more information
+                // about why the class was not found. BZ 48007
+                throw e;
+            }
+        }
+    }
+
+
+    /**
+     * Return a proxy class that implements the interfaces named in a proxy
+     * class descriptor. Do this using the class loader assigned to this
+     * Context.
+     */
+    @Override
+    protected Class<?> resolveProxyClass(String[] interfaces)
+        throws IOException, ClassNotFoundException {
+
+        Class<?>[] cinterfaces = new Class[interfaces.length];
+        for (int i = 0; i < interfaces.length; i++)
+            cinterfaces[i] = classLoader.loadClass(interfaces[i]);
+
+        try {
+            return Proxy.getProxyClass(classLoader, cinterfaces);
+        } catch (IllegalArgumentException e) {
+            throw new ClassNotFoundException(null, e);
+        }
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/DOMWriter.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/DOMWriter.java
new file mode 100644
index 0000000..a5ec657
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/DOMWriter.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.util;
+
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * A sample DOM writer. This sample program illustrates how to
+ * traverse a DOM tree in order to print a document that is parsed.
+ */
+public class DOMWriter {
+
+   //
+   // Data
+   //
+
+   /** Default Encoding */
+   private static  String
+   PRINTWRITER_ENCODING = "UTF8";
+
+   private static String MIME2JAVA_ENCODINGS[] =
+    { "Default", "UTF-8", "US-ASCII", "ISO-8859-1", "ISO-8859-2", "ISO-8859-3", "ISO-8859-4",
+      "ISO-8859-5", "ISO-8859-6", "ISO-8859-7", "ISO-8859-8", "ISO-8859-9", "ISO-2022-JP",
+      "SHIFT_JIS", "EUC-JP","GB2312", "BIG5", "EUC-KR", "ISO-2022-KR", "KOI8-R", "EBCDIC-CP-US",
+      "EBCDIC-CP-CA", "EBCDIC-CP-NL", "EBCDIC-CP-DK", "EBCDIC-CP-NO", "EBCDIC-CP-FI", "EBCDIC-CP-SE",
+      "EBCDIC-CP-IT", "EBCDIC-CP-ES", "EBCDIC-CP-GB", "EBCDIC-CP-FR", "EBCDIC-CP-AR1",
+      "EBCDIC-CP-HE", "EBCDIC-CP-CH", "EBCDIC-CP-ROECE","EBCDIC-CP-YU",
+      "EBCDIC-CP-IS", "EBCDIC-CP-AR2", "UTF-16"
+    };
+
+   /** Output qualified names */
+   private boolean qualifiedNames = true;
+
+   /** Print writer. */
+   protected PrintWriter out;
+
+   /** Canonical output. */
+   protected boolean canonical;
+
+
+   public DOMWriter(String encoding, boolean canonical)
+   throws UnsupportedEncodingException {
+      out = new PrintWriter(new OutputStreamWriter(System.out, encoding));
+      this.canonical = canonical;
+   } // <init>(String,boolean)
+
+   //
+   // Constructors
+   //
+
+   /** Default constructor. */
+   public DOMWriter(boolean canonical) throws UnsupportedEncodingException {
+      this( getWriterEncoding(), canonical);
+   }
+
+    public DOMWriter(Writer writer, boolean canonical) {
+        out = new PrintWriter(writer);
+        this.canonical = canonical;
+    }
+
+   public boolean getQualifiedNames() {
+      return this.qualifiedNames;
+   }
+
+   public void setQualifiedNames(boolean qualifiedNames) {
+      this.qualifiedNames = qualifiedNames;
+   }
+
+   public static String getWriterEncoding( ) {
+      return (PRINTWRITER_ENCODING);
+   }// getWriterEncoding
+
+   public static void  setWriterEncoding( String encoding ) {
+      if( encoding.equalsIgnoreCase( "DEFAULT" ) )
+         PRINTWRITER_ENCODING  = "UTF8";
+      else if( encoding.equalsIgnoreCase( "UTF-16" ) )
+         PRINTWRITER_ENCODING  = "Unicode";
+      else
+         PRINTWRITER_ENCODING = MIME2Java.convert( encoding );
+   }// setWriterEncoding
+
+
+   public static boolean isValidJavaEncoding( String encoding ) {
+      for ( int i = 0; i < MIME2JAVA_ENCODINGS.length; i++ )
+         if ( encoding.equals( MIME2JAVA_ENCODINGS[i] ) )
+            return (true);
+
+      return (false);
+   }// isValidJavaEncoding
+
+
+   /** Prints the specified node, recursively. */
+   public void print(Node node) {
+
+      // is there anything to do?
+      if ( node == null ) {
+         return;
+      }
+
+      int type = node.getNodeType();
+      switch ( type ) {
+         // print document
+         case Node.DOCUMENT_NODE: {
+               if ( !canonical ) {
+                  String  Encoding = getWriterEncoding();
+                  if( Encoding.equalsIgnoreCase( "DEFAULT" ) )
+                     Encoding = "UTF-8";
+                  else if( Encoding.equalsIgnoreCase( "Unicode" ) )
+                     Encoding = "UTF-16";
+                  else
+                     Encoding = MIME2Java.reverse( Encoding );
+
+                  out.println("<?xml version=\"1.0\" encoding=\""+
+                           Encoding + "\"?>");
+               }
+               print(((Document)node).getDocumentElement());
+               out.flush();
+               break;
+            }
+
+            // print element with attributes
+         case Node.ELEMENT_NODE: {
+               out.print('<');
+               if (this.qualifiedNames) { 
+                  out.print(node.getNodeName());
+               } else {
+                  out.print(node.getLocalName());
+               }
+               Attr attrs[] = sortAttributes(node.getAttributes());
+               for ( int i = 0; i < attrs.length; i++ ) {
+                  Attr attr = attrs[i];
+                  out.print(' ');
+                  if (this.qualifiedNames) {
+                     out.print(attr.getNodeName());
+                  } else {
+                     out.print(attr.getLocalName());
+                  }
+                  
+                  out.print("=\"");
+                  out.print(normalize(attr.getNodeValue()));
+                  out.print('"');
+               }
+               out.print('>');
+               NodeList children = node.getChildNodes();
+               if ( children != null ) {
+                  int len = children.getLength();
+                  for ( int i = 0; i < len; i++ ) {
+                     print(children.item(i));
+                  }
+               }
+               break;
+            }
+
+            // handle entity reference nodes
+         case Node.ENTITY_REFERENCE_NODE: {
+               if ( canonical ) {
+                  NodeList children = node.getChildNodes();
+                  if ( children != null ) {
+                     int len = children.getLength();
+                     for ( int i = 0; i < len; i++ ) {
+                        print(children.item(i));
+                     }
+                  }
+               } else {
+                  out.print('&');
+                  if (this.qualifiedNames) {
+                     out.print(node.getNodeName());
+                  } else {
+                     out.print(node.getLocalName());
+                  }
+                  out.print(';');
+               }
+               break;
+            }
+
+            // print cdata sections
+         case Node.CDATA_SECTION_NODE: {
+               if ( canonical ) {
+                  out.print(normalize(node.getNodeValue()));
+               } else {
+                  out.print("<![CDATA[");
+                  out.print(node.getNodeValue());
+                  out.print("]]>");
+               }
+               break;
+            }
+
+            // print text
+         case Node.TEXT_NODE: {
+               out.print(normalize(node.getNodeValue()));
+               break;
+            }
+
+            // print processing instruction
+         case Node.PROCESSING_INSTRUCTION_NODE: {
+               out.print("<?");
+               if (this.qualifiedNames) {
+                  out.print(node.getNodeName());
+               } else {
+                  out.print(node.getLocalName());
+               }
+               
+               String data = node.getNodeValue();
+               if ( data != null && data.length() > 0 ) {
+                  out.print(' ');
+                  out.print(data);
+               }
+               out.print("?>");
+               break;
+            }
+      }
+
+      if ( type == Node.ELEMENT_NODE ) {
+         out.print("</");
+         if (this.qualifiedNames) {
+            out.print(node.getNodeName());
+         } else {
+            out.print(node.getLocalName());
+         }
+         out.print('>');
+      }
+
+      out.flush();
+
+   } // print(Node)
+
+   /** Returns a sorted list of attributes. */
+   protected Attr[] sortAttributes(NamedNodeMap attrs) {
+
+      int len = (attrs != null) ? attrs.getLength() : 0;
+      Attr array[] = new Attr[len];
+      for ( int i = 0; i < len; i++ ) {
+         array[i] = (Attr)attrs.item(i);
+      }
+      for ( int i = 0; i < len - 1; i++ ) {
+         String name = null;
+         if (this.qualifiedNames) {
+            name  = array[i].getNodeName();
+         } else {
+            name  = array[i].getLocalName();
+         }
+         int    index = i;
+         for ( int j = i + 1; j < len; j++ ) {
+            String curName = null;
+            if (this.qualifiedNames) {
+               curName = array[j].getNodeName();
+            } else {
+               curName = array[j].getLocalName();
+            }
+            if ( curName.compareTo(name) < 0 ) {
+               name  = curName;
+               index = j;
+            }
+         }
+         if ( index != i ) {
+            Attr temp    = array[i];
+            array[i]     = array[index];
+            array[index] = temp;
+         }
+      }
+
+      return (array);
+
+   } // sortAttributes(NamedNodeMap):Attr[]
+
+
+   /** Normalizes the given string. */
+   protected String normalize(String s) {
+      StringBuilder str = new StringBuilder();
+
+      int len = (s != null) ? s.length() : 0;
+      for ( int i = 0; i < len; i++ ) {
+         char ch = s.charAt(i);
+         switch ( ch ) {
+            case '<': {
+                  str.append("&lt;");
+                  break;
+               }
+            case '>': {
+                  str.append("&gt;");
+                  break;
+               }
+            case '&': {
+                  str.append("&amp;");
+                  break;
+               }
+            case '"': {
+                  str.append("&quot;");
+                  break;
+               }
+            case '\r':
+            case '\n': {
+                  if ( canonical ) {
+                     str.append("&#");
+                     str.append(Integer.toString(ch));
+                     str.append(';');
+                     break;
+                  }
+                  // else, default append char
+               }
+            default: {
+                  str.append(ch);
+               }
+         }
+      }
+
+      return (str.toString());
+
+   } // normalize(String):String
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/DateTool.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/DateTool.java
new file mode 100644
index 0000000..82cd8a3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/DateTool.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 org.apache.catalina.util;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ *  Common place for date utils.
+ *
+ * @author dac@eng.sun.com
+ * @author Jason Hunter [jch@eng.sun.com]
+ * @author James Todd [gonzo@eng.sun.com]
+ * @author Costin Manolache
+ * @author fhanik
+ */
+public class DateTool {
+
+    /**
+     * US locale - all HTTP dates are in English
+     */
+    public static final Locale LOCALE_US = Locale.US;
+
+    /**
+     * GMT timezone - all HTTP dates are on GMT
+     */
+    public static final TimeZone GMT_ZONE = TimeZone.getTimeZone("GMT");
+
+    /**
+     * format for RFC 1123 date string -- "Sun, 06 Nov 1994 08:49:37 GMT"
+     */
+    public static final String RFC1123_PATTERN =
+        "EEE, dd MMM yyyyy HH:mm:ss z";
+
+    /** 
+     * Format for http response header date field
+     */
+    public static final String HTTP_RESPONSE_DATE_HEADER =
+        "EEE, dd MMM yyyy HH:mm:ss zzz";
+
+    // format for RFC 1036 date string -- "Sunday, 06-Nov-94 08:49:37 GMT"
+    private static final String rfc1036Pattern =
+        "EEEEEEEEE, dd-MMM-yy HH:mm:ss z";
+
+    // format for C asctime() date string -- "Sun Nov  6 08:49:37 1994"
+    private static final String asctimePattern =
+        "EEE MMM d HH:mm:ss yyyyy";
+
+    /**
+     * Pattern used for old cookies
+     */
+    public static final String OLD_COOKIE_PATTERN = "EEE, dd-MMM-yyyy HH:mm:ss z";
+
+    /**
+     * DateFormat to be used to format dates
+     */
+    public static final ThreadLocal<DateFormat> rfc1123Format = new ThreadLocal<DateFormat>() {
+        @Override
+        public DateFormat initialValue() {
+            DateFormat result = new SimpleDateFormat(RFC1123_PATTERN, LOCALE_US);
+            result.setTimeZone(GMT_ZONE);
+            return result;
+        }
+    };
+
+    /**
+     * DateFormat to be used to format old netscape cookies
+     */
+    public static final ThreadLocal<DateFormat> oldCookieFormat = new ThreadLocal<DateFormat>() {
+        @Override
+        public DateFormat initialValue() {
+            DateFormat result = new SimpleDateFormat(OLD_COOKIE_PATTERN, LOCALE_US);
+            result.setTimeZone(GMT_ZONE);
+            return result;
+        }
+    };
+
+
+    public static final ThreadLocal<DateFormat> rfc1036Format = new ThreadLocal<DateFormat>() {
+        @Override
+        public DateFormat initialValue() {
+            DateFormat result = new SimpleDateFormat(rfc1036Pattern, LOCALE_US);
+            result.setTimeZone(GMT_ZONE);
+            return result;
+        }
+    };
+
+    public static final ThreadLocal<DateFormat> asctimeFormat = new ThreadLocal<DateFormat>() {
+        @Override
+        public DateFormat initialValue() {
+            DateFormat result = new SimpleDateFormat(asctimePattern, LOCALE_US);
+            result.setTimeZone(GMT_ZONE);
+            return result;
+        }
+    };
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/Enumerator.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/Enumerator.java
new file mode 100644
index 0000000..45e6ef5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/Enumerator.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.catalina.util;
+
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+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:08:15 rherrmann Exp $
+ */
+
+public final class Enumerator<T> implements Enumeration<T> {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Return an Enumeration over the values of the specified Collection.
+     *
+     * @param collection Collection whose values should be enumerated
+     */
+    public Enumerator(Collection<T> collection) {
+
+        this(collection.iterator());
+
+    }
+
+
+    /**
+     * Return an Enumeration over the values of the specified Collection.
+     *
+     * @param collection Collection whose values should be enumerated
+     * @param clone true to clone iterator
+     */
+    public Enumerator(Collection<T> collection, boolean clone) {
+
+        this(collection.iterator(), clone);
+
+    }
+
+
+    /**
+     * 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();   
+        }
+
+    }
+
+
+    /**
+     * Return an Enumeration over the values of the specified Map.
+     *
+     * @param map Map whose values should be enumerated
+     */
+    public Enumerator(Map<?,T> map) {
+
+        this(map.values().iterator());
+
+    }
+
+
+    /**
+     * Return an Enumeration over the values of the specified Map.
+     *
+     * @param map Map whose values should be enumerated
+     * @param clone true to clone iterator
+     */
+    public Enumerator(Map<?,T> map, boolean clone) {
+
+        this(map.values().iterator(), clone);
+
+    }
+
+
+    // ----------------------------------------------------- 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
+     */
+    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
+     */
+    public T nextElement() throws NoSuchElementException {
+
+        return (iterator.next());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/Extension.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/Extension.java
new file mode 100644
index 0000000..e88b991
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/Extension.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.catalina.util;
+
+
+import java.util.StringTokenizer;
+
+
+/**
+ * Utility class that represents either an available "Optional Package"
+ * (formerly known as "Standard Extension") as described in the manifest
+ * of a JAR file, or the requirement for such an optional package.  It is
+ * used to support the requirements of the Servlet Specification, version
+ * 2.3, related to providing shared extensions to all webapps.
+ * <p>
+ * In addition, static utility methods are available to scan a manifest
+ * and return an array of either available or required optional modules
+ * documented in that manifest.
+ * <p>
+ * For more information about optional packages, see the document
+ * <em>Optional Package Versioning</em> in the documentation bundle for your
+ * Java2 Standard Edition package, in file
+ * <code>guide/extensions/versioning.html</code>.
+ *
+ * @author Craig McClanahan
+ * @author Justyna Horwat
+ * @author Greg Murray
+ * @version $Id: Extension.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public final class Extension {
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * The name of the optional package being made available, or required.
+     */
+    private String extensionName = null;
+    
+
+    public String getExtensionName() {
+        return (this.extensionName);
+    }
+
+    public void setExtensionName(String extensionName) {
+        this.extensionName = extensionName;
+    }
+
+    /**
+     * The URL from which the most recent version of this optional package
+     * can be obtained if it is not already installed.
+     */
+    private String implementationURL = null;
+
+    public String getImplementationURL() {
+        return (this.implementationURL);
+    }
+
+    public void setImplementationURL(String implementationURL) {
+        this.implementationURL = implementationURL;
+    }
+
+
+    /**
+     * The name of the company or organization that produced this
+     * implementation of this optional package.
+     */
+    private String implementationVendor = null;
+
+    public String getImplementationVendor() {
+        return (this.implementationVendor);
+    }
+
+    public void setImplementationVendor(String implementationVendor) {
+        this.implementationVendor = implementationVendor;
+    }
+
+
+    /**
+     * The unique identifier of the company that produced the optional
+     * package contained in this JAR file.
+     */
+    private String implementationVendorId = null;
+
+    public String getImplementationVendorId() {
+        return (this.implementationVendorId);
+    }
+
+    public void setImplementationVendorId(String implementationVendorId) {
+        this.implementationVendorId = implementationVendorId;
+    }
+
+
+    /**
+     * The version number (dotted decimal notation) for this implementation
+     * of the optional package.
+     */
+    private String implementationVersion = null;
+
+    public String getImplementationVersion() {
+        return (this.implementationVersion);
+    }
+
+    public void setImplementationVersion(String implementationVersion) {
+        this.implementationVersion = implementationVersion;
+    }
+
+
+    /**
+     * The name of the company or organization that originated the
+     * specification to which this optional package conforms.
+     */
+    private String specificationVendor = null;
+
+    public String getSpecificationVendor() {
+        return (this.specificationVendor);
+    }
+
+    public void setSpecificationVendor(String specificationVendor) {
+        this.specificationVendor = specificationVendor;
+    }
+
+
+    /**
+     * The version number (dotted decimal notation) of the specification
+     * to which this optional package conforms.
+     */
+    private String specificationVersion = null;
+
+    public String getSpecificationVersion() {
+        return (this.specificationVersion);
+    }
+
+    public void setSpecificationVersion(String specificationVersion) {
+        this.specificationVersion = specificationVersion;
+    }
+
+
+    /**
+     * fulfilled is true if all the required extension dependencies have been
+     * satisfied
+     */
+    private boolean fulfilled = false;
+
+    public void setFulfilled(boolean fulfilled) {
+        this.fulfilled = fulfilled;
+    }
+    
+    public boolean isFulfilled() {
+        return fulfilled;
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Return <code>true</code> if the specified <code>Extension</code>
+     * (which represents an optional package required by this application)
+     * is satisfied by this <code>Extension</code> (which represents an
+     * optional package that is already installed.  Otherwise, return
+     * <code>false</code>.
+     *
+     * @param required Extension of the required optional package
+     */
+    public boolean isCompatibleWith(Extension required) {
+
+        // Extension Name must match
+        if (extensionName == null)
+            return (false);
+        if (!extensionName.equals(required.getExtensionName()))
+            return (false);
+
+        // If specified, available specification version must be >= required
+        if (required.getSpecificationVersion() != null) {
+            if (!isNewer(specificationVersion,
+                         required.getSpecificationVersion()))
+                return (false);
+        }
+
+        // If specified, Implementation Vendor ID must match
+        if (required.getImplementationVendorId() != null) {
+            if (implementationVendorId == null)
+                return (false);
+            if (!implementationVendorId.equals(required
+                    .getImplementationVendorId()))
+                return (false);
+        }
+
+        // If specified, Implementation version must be >= required
+        if (required.getImplementationVersion() != null) {
+            if (!isNewer(implementationVersion,
+                         required.getImplementationVersion()))
+                return (false);
+        }
+
+        // This available optional package satisfies the requirements
+        return (true);
+
+    }
+
+    /**
+     * Return a String representation of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("Extension[");
+        sb.append(extensionName);
+        if (implementationURL != null) {
+            sb.append(", implementationURL=");
+            sb.append(implementationURL);
+        }
+        if (implementationVendor != null) {
+            sb.append(", implementationVendor=");
+            sb.append(implementationVendor);
+        }
+        if (implementationVendorId != null) {
+            sb.append(", implementationVendorId=");
+            sb.append(implementationVendorId);
+        }
+        if (implementationVersion != null) {
+            sb.append(", implementationVersion=");
+            sb.append(implementationVersion);
+        }
+        if (specificationVendor != null) {
+            sb.append(", specificationVendor=");
+            sb.append(specificationVendor);
+        }
+        if (specificationVersion != null) {
+            sb.append(", specificationVersion=");
+            sb.append(specificationVersion);
+        }
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+    // -------------------------------------------------------- Private Methods
+
+
+
+    /**
+     * Return <code>true</code> if the first version number is greater than
+     * or equal to the second; otherwise return <code>false</code>.
+     *
+     * @param first First version number (dotted decimal)
+     * @param second Second version number (dotted decimal)
+     *
+     * @exception NumberFormatException on a malformed version number
+     */
+    private boolean isNewer(String first, String second)
+        throws NumberFormatException {
+
+        if ((first == null) || (second == null))
+            return (false);
+        if (first.equals(second))
+            return (true);
+
+        StringTokenizer fTok = new StringTokenizer(first, ".", true);
+        StringTokenizer sTok = new StringTokenizer(second, ".", true);
+        int fVersion = 0;
+        int sVersion = 0;
+        while (fTok.hasMoreTokens() || sTok.hasMoreTokens()) {
+            if (fTok.hasMoreTokens())
+                fVersion = Integer.parseInt(fTok.nextToken());
+            else
+                fVersion = 0;
+            if (sTok.hasMoreTokens())
+                sVersion = Integer.parseInt(sTok.nextToken());
+            else
+                sVersion = 0;
+            if (fVersion < sVersion)
+                return (false);
+            else if (fVersion > sVersion)
+                return (true);
+            if (fTok.hasMoreTokens())   // Swallow the periods
+                fTok.nextToken();
+            if (sTok.hasMoreTokens())
+                sTok.nextToken();
+        }
+
+        return (true);  // Exact match
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/ExtensionValidator.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/ExtensionValidator.java
new file mode 100644
index 0000000..64b287f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/ExtensionValidator.java
@@ -0,0 +1,446 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+
+import javax.naming.Binding;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+
+import org.apache.catalina.Context;
+import org.apache.naming.resources.Resource;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Ensures that all extension dependencies are resolved for a WEB application
+ * are met. This class builds a master list of extensions available to an
+ * application and then validates those extensions.
+ *
+ * See http://download.oracle.com/javase/1.4.2/docs/guide/extensions/spec.html
+ * for a detailed explanation of the extension mechanism in Java.
+ *
+ * @author Greg Murray
+ * @author Justyna Horwat
+ * @version $Id: ExtensionValidator.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ *
+ */
+public final class ExtensionValidator {
+
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog(ExtensionValidator.class);
+
+    /**
+     * The string resources for this package.
+     */
+    private static final StringManager sm =
+        StringManager.getManager("org.apache.catalina.util");
+    
+    private static volatile ArrayList<Extension> containerAvailableExtensions =
+        null;
+    private static ArrayList<ManifestResource> containerManifestResources =
+        new ArrayList<ManifestResource>();
+
+
+    // ----------------------------------------------------- Static Initializer
+
+
+    /**
+     *  This static initializer loads the container level extensions that are
+     *  available to all web applications. This method scans all extension 
+     *  directories available via the "java.ext.dirs" System property. 
+     *
+     *  The System Class-Path is also scanned for jar files that may contain 
+     *  available extensions.
+     */
+    static {
+
+        // check for container level optional packages
+        String systemClasspath = System.getProperty("java.class.path");
+
+        StringTokenizer strTok = new StringTokenizer(systemClasspath, 
+                                                     File.pathSeparator);
+
+        // build a list of jar files in the classpath
+        while (strTok.hasMoreTokens()) {
+            String classpathItem = strTok.nextToken();
+            if (classpathItem.toLowerCase(Locale.ENGLISH).endsWith(".jar")) {
+                File item = new File(classpathItem);
+                if (item.isFile()) {
+                    try {
+                        addSystemResource(item);
+                    } catch (IOException e) {
+                        log.error(sm.getString
+                                  ("extensionValidator.failload", item), e);
+                    }
+                }
+            }
+        }
+
+        // add specified folders to the list
+        addFolderList("java.ext.dirs");
+        addFolderList("catalina.ext.dirs");
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Runtime validation of a Web Application.
+     *
+     * This method uses JNDI to look up the resources located under a 
+     * <code>DirContext</code>. It locates Web Application MANIFEST.MF 
+     * file in the /META-INF/ directory of the application and all 
+     * MANIFEST.MF files in each JAR file located in the WEB-INF/lib 
+     * directory and creates an <code>ArrayList</code> of 
+     * <code>ManifestResorce<code> objects. These objects are then passed 
+     * to the validateManifestResources method for validation.
+     *
+     * @param dirContext The JNDI root of the Web Application
+     * @param context The context from which the Logger and path to the
+     *                application
+     *
+     * @return true if all required extensions satisfied
+     */
+    public static synchronized boolean validateApplication(
+                                           DirContext dirContext, 
+                                           Context context)
+                    throws IOException {
+
+        String appName = context.getName();
+        ArrayList<ManifestResource> appManifestResources =
+            new ArrayList<ManifestResource>();
+        // If the application context is null it does not exist and 
+        // therefore is not valid
+        if (dirContext == null) return false;
+        // Find the Manifest for the Web Application
+        InputStream inputStream = null;
+        try {
+            NamingEnumeration<Binding> wne =
+                dirContext.listBindings("/META-INF/");
+            Binding binding = wne.nextElement();
+            if (binding.getName().toUpperCase(Locale.ENGLISH).equals("MANIFEST.MF")) {
+                Resource resource = (Resource)dirContext.lookup
+                                    ("/META-INF/" + binding.getName());
+                inputStream = resource.streamContent();
+                Manifest manifest = new Manifest(inputStream);
+                inputStream.close();
+                inputStream = null;
+                ManifestResource mre = new ManifestResource
+                    (sm.getString("extensionValidator.web-application-manifest"),
+                    manifest, ManifestResource.WAR);
+                appManifestResources.add(mre);
+            } 
+        } catch (NamingException nex) {
+            // Application does not contain a MANIFEST.MF file
+        } catch (NoSuchElementException nse) {
+            // Application does not contain a MANIFEST.MF file
+        } finally {
+            if (inputStream != null) {
+                try {
+                    inputStream.close();
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                }
+            }
+        }
+
+        // Locate the Manifests for all bundled JARs
+        NamingEnumeration<Binding> ne = null;
+        try {
+            ne = dirContext.listBindings("WEB-INF/lib/");
+            while ((ne != null) && ne.hasMoreElements()) {
+                Binding binding = ne.nextElement();
+                if (!binding.getName().toLowerCase(Locale.ENGLISH).endsWith(".jar")) {
+                    continue;
+                }
+                Object obj =
+                    dirContext.lookup("/WEB-INF/lib/" + binding.getName());
+                if (!(obj instanceof Resource)) {
+                    // Probably a directory named xxx.jar - ignore it
+                    continue;
+                }
+                Resource resource = (Resource) obj;
+                inputStream = resource.streamContent();
+                Manifest jmanifest = getManifest(inputStream);
+                if (jmanifest != null) {
+                    ManifestResource mre = new ManifestResource(
+                                                binding.getName(),
+                                                jmanifest, 
+                                                ManifestResource.APPLICATION);
+                    appManifestResources.add(mre);
+                }
+            }
+        } catch (NamingException nex) {
+            // Jump out of the check for this application because it 
+            // has no resources
+        } finally {
+            if (inputStream != null) {
+                try {
+                    inputStream.close();
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                }
+            }
+        }
+
+        return validateManifestResources(appName, appManifestResources);
+    }
+
+
+    /**
+     * Checks to see if the given system JAR file contains a MANIFEST, and adds
+     * it to the container's manifest resources.
+     *
+     * @param jarFile The system JAR whose manifest to add
+     */
+    public static void addSystemResource(File jarFile) throws IOException {
+        Manifest manifest = getManifest(new FileInputStream(jarFile));
+        if (manifest != null)  {
+            ManifestResource mre
+                = new ManifestResource(jarFile.getAbsolutePath(),
+                                       manifest,
+                                       ManifestResource.SYSTEM);
+            containerManifestResources.add(mre);
+        }
+    }
+
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Validates a <code>ArrayList</code> of <code>ManifestResource</code> 
+     * objects. This method requires an application name (which is the 
+     * context root of the application at runtime).  
+     *
+     * <code>false</false> is returned if the extension dependencies
+     * represented by any given <code>ManifestResource</code> objects 
+     * is not met.
+     *
+     * This method should also provide static validation of a Web Application 
+     * if provided with the necessary parameters.
+     *
+     * @param appName The name of the Application that will appear in the 
+     *                error messages
+     * @param resources A list of <code>ManifestResource</code> objects 
+     *                  to be validated.
+     *
+     * @return true if manifest resource file requirements are met
+     */
+    private static boolean validateManifestResources(String appName,
+            ArrayList<ManifestResource> resources) {
+        boolean passes = true;
+        int failureCount = 0;        
+        ArrayList<Extension> availableExtensions = null;
+
+        Iterator<ManifestResource> it = resources.iterator();
+        while (it.hasNext()) {
+            ManifestResource mre = it.next();
+            ArrayList<Extension> requiredList = mre.getRequiredExtensions();
+            if (requiredList == null) {
+                continue;
+            }
+
+            // build the list of available extensions if necessary
+            if (availableExtensions == null) {
+                availableExtensions = buildAvailableExtensionsList(resources);
+            }
+
+            // load the container level resource map if it has not been built
+            // yet
+            if (containerAvailableExtensions == null) {
+                containerAvailableExtensions
+                    = buildAvailableExtensionsList(containerManifestResources);
+            }
+
+            // iterate through the list of required extensions
+            Iterator<Extension> rit = requiredList.iterator();
+            while (rit.hasNext()) {
+                boolean found = false;
+                Extension requiredExt = rit.next();
+                // check the application itself for the extension
+                if (availableExtensions != null) {
+                    Iterator<Extension> ait = availableExtensions.iterator();
+                    while (ait.hasNext()) {
+                        Extension targetExt = ait.next();
+                        if (targetExt.isCompatibleWith(requiredExt)) {
+                            requiredExt.setFulfilled(true);
+                            found = true;
+                            break;
+                        }
+                    }
+                }
+                // check the container level list for the extension
+                if (!found && containerAvailableExtensions != null) {
+                    Iterator<Extension> cit =
+                        containerAvailableExtensions.iterator();
+                    while (cit.hasNext()) {
+                        Extension targetExt = cit.next();
+                        if (targetExt.isCompatibleWith(requiredExt)) {
+                            requiredExt.setFulfilled(true);
+                            found = true;
+                            break;
+                        }
+                    }
+                }
+                if (!found) {
+                    // Failure
+                    log.info(sm.getString(
+                        "extensionValidator.extension-not-found-error",
+                        appName, mre.getResourceName(),
+                        requiredExt.getExtensionName()));
+                    passes = false;
+                    failureCount++;
+                }
+            }
+        }
+
+        if (!passes) {
+            log.info(sm.getString(
+                     "extensionValidator.extension-validation-error", appName,
+                     failureCount + ""));
+        }
+
+        return passes;
+    }
+    
+   /* 
+    * Build this list of available extensions so that we do not have to 
+    * re-build this list every time we iterate through the list of required 
+    * extensions. All available extensions in all of the 
+    * <code>MainfestResource</code> objects will be added to a 
+    * <code>HashMap</code> which is returned on the first dependency list
+    * processing pass. 
+    *
+    * The key is the name + implementation version.
+    *
+    * NOTE: A list is built only if there is a dependency that needs 
+    * to be checked (performance optimization).
+    *
+    * @param resources A list of <code>ManifestResource</code> objects
+    *
+    * @return HashMap Map of available extensions
+    */
+    private static ArrayList<Extension> buildAvailableExtensionsList(
+            ArrayList<ManifestResource> resources) {
+
+        ArrayList<Extension> availableList = null;
+
+        Iterator<ManifestResource> it = resources.iterator();
+        while (it.hasNext()) {
+            ManifestResource mre = it.next();
+            ArrayList<Extension> list = mre.getAvailableExtensions();
+            if (list != null) {
+                Iterator<Extension> values = list.iterator();
+                while (values.hasNext()) {
+                    Extension ext = values.next();
+                    if (availableList == null) {
+                        availableList = new ArrayList<Extension>();
+                        availableList.add(ext);
+                    } else {
+                        availableList.add(ext);
+                    }
+                }
+            }
+        }
+
+        return availableList;
+    }
+    
+    /**
+     * Return the Manifest from a jar file or war file
+     *
+     * @param inStream Input stream to a WAR or JAR file
+     * @return The WAR's or JAR's manifest
+     */
+    private static Manifest getManifest(InputStream inStream)
+            throws IOException {
+
+        Manifest manifest = null;
+        JarInputStream jin = null;
+
+        try {
+            jin = new JarInputStream(inStream);
+            manifest = jin.getManifest();
+            jin.close();
+            jin = null;
+        } finally {
+            if (jin != null) {
+                try {
+                    jin.close();
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                }
+            }
+        }
+
+        return manifest;
+    }
+
+
+    /**
+     * Add the JARs specified to the extension list.
+     */
+    private static void addFolderList(String property) {
+
+        // get the files in the extensions directory
+        String extensionsDir = System.getProperty(property);
+        if (extensionsDir != null) {
+            StringTokenizer extensionsTok
+                = new StringTokenizer(extensionsDir, File.pathSeparator);
+            while (extensionsTok.hasMoreTokens()) {
+                File targetDir = new File(extensionsTok.nextToken());
+                if (!targetDir.isDirectory()) {
+                    continue;
+                }
+                File[] files = targetDir.listFiles();
+                for (int i = 0; i < files.length; i++) {
+                    if (files[i].getName().toLowerCase(Locale.ENGLISH).endsWith(".jar") &&
+                            files[i].isFile()) {
+                        try {
+                            addSystemResource(files[i]);
+                        } catch (IOException e) {
+                            log.error
+                                (sm.getString
+                                 ("extensionValidator.failload", files[i]), e);
+                        }
+                    }
+                }
+            }
+        }
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/HexUtils.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/HexUtils.java
new file mode 100644
index 0000000..9ad664c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/HexUtils.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.catalina.util;
+
+import java.io.ByteArrayOutputStream;
+
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Library of utility methods useful in dealing with converting byte arrays
+ * to and from strings of hexadecimal digits.
+ *
+ * @author Craig R. McClanahan
+ */
+
+public final class HexUtils {
+    // Code from Ajp11, from Apache's JServ
+
+    // 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,
+    };
+
+
+
+    /**
+     * The string manager for this package.
+     */
+    private static final StringManager sm =
+        StringManager.getManager("org.apache.catalina.util");
+
+
+    /**
+     * Convert a String of hexadecimal digits into the corresponding
+     * byte array by encoding each two hexadecimal digits as a byte.
+     *
+     * @param digits Hexadecimal digits representation
+     *
+     * @exception IllegalArgumentException if an invalid hexadecimal digit
+     *  is found, or the input string contains an odd number of hexadecimal
+     *  digits
+     */
+    public static byte[] convert(String digits) {
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        for (int i = 0; i < digits.length(); i += 2) {
+            char c1 = digits.charAt(i);
+            if ((i+1) >= digits.length())
+                throw new IllegalArgumentException
+                    (sm.getString("hexUtil.odd"));
+            char c2 = digits.charAt(i + 1);
+            byte b = 0;
+            if ((c1 >= '0') && (c1 <= '9'))
+                b += ((c1 - '0') * 16);
+            else if ((c1 >= 'a') && (c1 <= 'f'))
+                b += ((c1 - 'a' + 10) * 16);
+            else if ((c1 >= 'A') && (c1 <= 'F'))
+                b += ((c1 - 'A' + 10) * 16);
+            else
+                throw new IllegalArgumentException
+                    (sm.getString("hexUtil.bad"));
+            if ((c2 >= '0') && (c2 <= '9'))
+                b += (c2 - '0');
+            else if ((c2 >= 'a') && (c2 <= 'f'))
+                b += (c2 - 'a' + 10);
+            else if ((c2 >= 'A') && (c2 <= 'F'))
+                b += (c2 - 'A' + 10);
+            else
+                throw new IllegalArgumentException
+                    (sm.getString("hexUtil.bad"));
+            baos.write(b);
+        }
+        return (baos.toByteArray());
+
+    }
+
+
+    /**
+     * Convert a byte array into a printable format containing a
+     * String of hexadecimal digit characters (two per byte).
+     *
+     * @param bytes Byte array representation
+     */
+    public static String convert(byte bytes[]) {
+
+        StringBuilder sb = new StringBuilder(bytes.length * 2);
+        for (int i = 0; i < bytes.length; i++) {
+            sb.append(convertDigit(bytes[i] >> 4));
+            sb.append(convertDigit(bytes[i] & 0x0f));
+        }
+        return (sb.toString());
+
+    }
+
+    /**
+     * Convert 4 hex digits to an int, and return the number of converted
+     * bytes.
+     *
+     * @param hex Byte array containing exactly four hexadecimal digits
+     *
+     * @exception IllegalArgumentException if an invalid hexadecimal digit
+     *  is included
+     */
+    public static int convert2Int( byte[] hex ) {
+        // Code from Ajp11, from Apache's JServ
+
+        // assert b.length==4
+        // assert valid data
+        int len;
+        if(hex.length < 4 ) return 0;
+        if( DEC[hex[0]]<0 )
+            throw new IllegalArgumentException(sm.getString("hexUtil.bad"));
+        len = DEC[hex[0]];
+        len = len << 4;
+        if( DEC[hex[1]]<0 )
+            throw new IllegalArgumentException(sm.getString("hexUtil.bad"));
+        len += DEC[hex[1]];
+        len = len << 4;
+        if( DEC[hex[2]]<0 )
+            throw new IllegalArgumentException(sm.getString("hexUtil.bad"));
+        len += DEC[hex[2]];
+        len = len << 4;
+        if( DEC[hex[3]]<0 )
+            throw new IllegalArgumentException(sm.getString("hexUtil.bad"));
+        len += DEC[hex[3]];
+        return len;
+    }
+
+
+
+    /**
+     * [Private] Convert the specified value (0 .. 15) to the corresponding
+     * hexadecimal digit.
+     *
+     * @param value Value to be converted
+     */
+    private static char convertDigit(int value) {
+
+        value &= 0x0f;
+        if (value >= 10)
+            return ((char) (value - 10 + 'a'));
+        else
+            return ((char) (value + '0'));
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/IOTools.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/IOTools.java
new file mode 100644
index 0000000..0da38f2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/IOTools.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+
+
+/**
+ * Contains commonly needed I/O-related methods 
+ *
+ * @author Dan Sandberg
+ */
+public class IOTools {
+    protected static final int DEFAULT_BUFFER_SIZE=4*1024; //4k
+
+    private IOTools() {
+      //Ensure non-instantiability
+    }
+
+    /**
+     * Read input from reader and write it to writer until there is no more
+     * input from reader.
+     *
+     * @param reader the reader to read from.
+     * @param writer the writer to write to.
+     * @param buf the char array to use as a buffer
+     */
+    public static void flow( Reader reader, Writer writer, char[] buf ) 
+        throws IOException {
+        int numRead;
+        while ( (numRead = reader.read(buf) ) >= 0) {
+            writer.write(buf, 0, numRead);
+        }
+    }
+
+    /**
+     * @see #flow( Reader, Writer, char[] )
+     */
+    public static void flow( Reader reader, Writer writer ) 
+        throws IOException {
+        char[] buf = new char[DEFAULT_BUFFER_SIZE];
+        flow( reader, writer, buf );
+    }
+
+    /**
+     * Read input from input stream and write it to output stream 
+     * until there is no more input from input stream.
+     *
+     * @param is input stream the input stream to read from.
+     * @param os output stream the output stream to write to.
+     * @param buf the byte array to use as a buffer
+     */
+    public static void flow( InputStream is, OutputStream os, byte[] buf ) 
+        throws IOException {
+        int numRead;
+        while ( (numRead = is.read(buf) ) >= 0) {
+            os.write(buf, 0, numRead);
+        }
+    }  
+
+    /**
+     * @see #flow( java.io.InputStream, java.io.OutputStream, byte[] )
+     */ 
+    public static void flow( InputStream is, OutputStream os ) 
+        throws IOException {
+        byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
+        flow( is, os, buf );
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/InstanceSupport.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/InstanceSupport.java
new file mode 100644
index 0000000..46c8370
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/InstanceSupport.java
@@ -0,0 +1,341 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.util;
+
+
+import javax.servlet.Filter;
+import javax.servlet.Servlet;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.apache.catalina.InstanceEvent;
+import org.apache.catalina.InstanceListener;
+import org.apache.catalina.Wrapper;
+
+
+/**
+ * Support class to assist in firing InstanceEvent notifications to
+ * registered InstanceListeners.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: InstanceSupport.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public final class InstanceSupport {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new InstanceSupport object associated with the specified
+     * Instance component.
+     *
+     * @param wrapper The component that will be the source
+     *  of events that we fire
+     */
+    public InstanceSupport(Wrapper wrapper) {
+
+        super();
+        this.wrapper = wrapper;
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The set of registered InstanceListeners for event notifications.
+     */
+    private InstanceListener listeners[] = new InstanceListener[0];
+    
+    private final Object listenersLock = new Object(); // Lock object for changes to listeners
+
+
+    /**
+     * The source component for instance events that we will fire.
+     */
+    private Wrapper wrapper = null;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the Wrapper with which we are associated.
+     */
+    public Wrapper getWrapper() {
+
+        return (this.wrapper);
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add a lifecycle event listener to this component.
+     *
+     * @param listener The listener to add
+     */
+    public void addInstanceListener(InstanceListener listener) {
+
+      synchronized (listenersLock) {
+          InstanceListener results[] =
+            new InstanceListener[listeners.length + 1];
+          for (int i = 0; i < listeners.length; i++)
+              results[i] = listeners[i];
+          results[listeners.length] = listener;
+          listeners = results;
+      }
+
+    }
+
+
+    /**
+     * Notify all lifecycle event listeners that a particular event has
+     * occurred for this Container.  The default implementation performs
+     * this notification synchronously using the calling thread.
+     *
+     * @param type Event type
+     * @param filter The relevant Filter for this event
+     */
+    public void fireInstanceEvent(String type, Filter filter) {
+
+        if (listeners.length == 0)
+            return;
+
+        InstanceEvent event = new InstanceEvent(wrapper, filter, type);
+        InstanceListener interested[] = listeners;
+        for (int i = 0; i < interested.length; i++)
+            interested[i].instanceEvent(event);
+
+    }
+
+
+    /**
+     * Notify all lifecycle event listeners that a particular event has
+     * occurred for this Container.  The default implementation performs
+     * this notification synchronously using the calling thread.
+     *
+     * @param type Event type
+     * @param filter The relevant Filter for this event
+     * @param exception Exception that occurred
+     */
+    public void fireInstanceEvent(String type, Filter filter,
+                                  Throwable exception) {
+
+        if (listeners.length == 0)
+            return;
+
+        InstanceEvent event = new InstanceEvent(wrapper, filter, type,
+                                                exception);
+        InstanceListener interested[] = listeners;
+        for (int i = 0; i < interested.length; i++)
+            interested[i].instanceEvent(event);
+
+    }
+
+
+    /**
+     * Notify all lifecycle event listeners that a particular event has
+     * occurred for this Container.  The default implementation performs
+     * this notification synchronously using the calling thread.
+     *
+     * @param type Event type
+     * @param filter The relevant Filter for this event
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are processing
+     */
+    public void fireInstanceEvent(String type, Filter filter,
+                                  ServletRequest request,
+                                  ServletResponse response) {
+
+        if (listeners.length == 0)
+            return;
+
+        InstanceEvent event = new InstanceEvent(wrapper, filter, type,
+                                                request, response);
+        InstanceListener interested[] = listeners;
+        for (int i = 0; i < interested.length; i++)
+            interested[i].instanceEvent(event);
+
+    }
+
+
+    /**
+     * Notify all lifecycle event listeners that a particular event has
+     * occurred for this Container.  The default implementation performs
+     * this notification synchronously using the calling thread.
+     *
+     * @param type Event type
+     * @param filter The relevant Filter for this event
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are processing
+     * @param exception Exception that occurred
+     */
+    public void fireInstanceEvent(String type, Filter filter,
+                                  ServletRequest request,
+                                  ServletResponse response,
+                                  Throwable exception) {
+
+        if (listeners.length == 0)
+            return;
+
+        InstanceEvent event = new InstanceEvent(wrapper, filter, type,
+                                                request, response, exception);
+        InstanceListener interested[] = listeners;
+        for (int i = 0; i < interested.length; i++)
+            interested[i].instanceEvent(event);
+
+    }
+
+
+    /**
+     * Notify all lifecycle event listeners that a particular event has
+     * occurred for this Container.  The default implementation performs
+     * this notification synchronously using the calling thread.
+     *
+     * @param type Event type
+     * @param servlet The relevant Servlet for this event
+     */
+    public void fireInstanceEvent(String type, Servlet servlet) {
+
+        if (listeners.length == 0)
+            return;
+
+        InstanceEvent event = new InstanceEvent(wrapper, servlet, type);
+        InstanceListener interested[] = listeners;
+        for (int i = 0; i < interested.length; i++)
+            interested[i].instanceEvent(event);
+
+    }
+
+
+    /**
+     * Notify all lifecycle event listeners that a particular event has
+     * occurred for this Container.  The default implementation performs
+     * this notification synchronously using the calling thread.
+     *
+     * @param type Event type
+     * @param servlet The relevant Servlet for this event
+     * @param exception Exception that occurred
+     */
+    public void fireInstanceEvent(String type, Servlet servlet,
+                                  Throwable exception) {
+
+        if (listeners.length == 0)
+            return;
+
+        InstanceEvent event = new InstanceEvent(wrapper, servlet, type,
+                                                exception);
+        InstanceListener interested[] = listeners;
+        for (int i = 0; i < interested.length; i++)
+            interested[i].instanceEvent(event);
+
+    }
+
+
+    /**
+     * Notify all lifecycle event listeners that a particular event has
+     * occurred for this Container.  The default implementation performs
+     * this notification synchronously using the calling thread.
+     *
+     * @param type Event type
+     * @param servlet The relevant Servlet for this event
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are processing
+     */
+    public void fireInstanceEvent(String type, Servlet servlet,
+                                  ServletRequest request,
+                                  ServletResponse response) {
+
+        if (listeners.length == 0)
+            return;
+
+        InstanceEvent event = new InstanceEvent(wrapper, servlet, type,
+                                                request, response);
+        InstanceListener interested[] = listeners;
+        for (int i = 0; i < interested.length; i++)
+            interested[i].instanceEvent(event);
+
+    }
+
+
+    /**
+     * Notify all lifecycle event listeners that a particular event has
+     * occurred for this Container.  The default implementation performs
+     * this notification synchronously using the calling thread.
+     *
+     * @param type Event type
+     * @param servlet The relevant Servlet for this event
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are processing
+     * @param exception Exception that occurred
+     */
+    public void fireInstanceEvent(String type, Servlet servlet,
+                                  ServletRequest request,
+                                  ServletResponse response,
+                                  Throwable exception) {
+
+        if (listeners.length == 0)
+            return;
+
+        InstanceEvent event = new InstanceEvent(wrapper, servlet, type,
+                                                request, response, exception);
+        InstanceListener interested[] = listeners;
+        for (int i = 0; i < interested.length; i++)
+            interested[i].instanceEvent(event);
+
+    }
+
+
+    /**
+     * Remove a lifecycle event listener from this component.
+     *
+     * @param listener The listener to remove
+     */
+    public void removeInstanceListener(InstanceListener listener) {
+
+        synchronized (listenersLock) {
+            int n = -1;
+            for (int i = 0; i < listeners.length; i++) {
+                if (listeners[i] == listener) {
+                    n = i;
+                    break;
+                }
+            }
+            if (n < 0)
+                return;
+            InstanceListener results[] =
+              new InstanceListener[listeners.length - 1];
+            int j = 0;
+            for (int i = 0; i < listeners.length; i++) {
+                if (i != n)
+                    results[j++] = listeners[i];
+            }
+            listeners = results;
+        }
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/LifecycleBase.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/LifecycleBase.java
new file mode 100644
index 0000000..66b6be2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/LifecycleBase.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.catalina.util;
+
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.LifecycleState;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Base implementation of the {@link Lifecycle} interface that implements the
+ * state transition rules for {@link Lifecycle#start()} and
+ * {@link Lifecycle#stop()}
+ */
+public abstract class LifecycleBase implements Lifecycle {
+
+    private static Log log = LogFactory.getLog(LifecycleBase.class);
+    
+    private static StringManager sm =
+        StringManager.getManager("org.apache.catalina.util");
+
+
+    /**
+     * Used to handle firing lifecycle events.
+     * TODO: Consider merging LifecycleSupport into this class.
+     */
+    private LifecycleSupport lifecycle = new LifecycleSupport(this);
+
+
+    /**
+     * The current state of the source component.
+     */
+    private volatile LifecycleState state = LifecycleState.NEW;
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addLifecycleListener(LifecycleListener listener) {
+        lifecycle.addLifecycleListener(listener);
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public LifecycleListener[] findLifecycleListeners() {
+        return lifecycle.findLifecycleListeners();
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void removeLifecycleListener(LifecycleListener listener) {
+        lifecycle.removeLifecycleListener(listener);
+    }
+
+    
+    /**
+     * Allow sub classes to fire {@link Lifecycle} events.
+     * 
+     * @param type  Event type
+     * @param data  Data associated with event.
+     */
+    protected void fireLifecycleEvent(String type, Object data) {
+        lifecycle.fireLifecycleEvent(type, data);
+    }
+
+    
+    @Override
+    public final synchronized void init() throws LifecycleException {
+        if (!state.equals(LifecycleState.NEW)) {
+            invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
+        }
+        setStateInternal(LifecycleState.INITIALIZING, null, false);
+
+        try {
+            initInternal();
+        } catch (LifecycleException e) {
+            setStateInternal(LifecycleState.FAILED, null, false);
+            throw e;
+        }
+
+        setStateInternal(LifecycleState.INITIALIZED, null, false);
+    }
+    
+    
+    protected abstract void initInternal() throws LifecycleException;
+    
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public final synchronized void start() throws LifecycleException {
+        
+        if (LifecycleState.STARTING_PREP.equals(state) ||
+                LifecycleState.STARTING.equals(state) ||
+                LifecycleState.STARTED.equals(state)) {
+            
+            if (log.isDebugEnabled()) {
+                Exception e = new LifecycleException();
+                log.debug(sm.getString("lifecycleBase.alreadyStarted",
+                        toString()), e);
+            } else if (log.isInfoEnabled()) {
+                log.info(sm.getString("lifecycleBase.alreadyStarted",
+                        toString()));
+            }
+            
+            return;
+        }
+        
+        if (state.equals(LifecycleState.NEW)) {
+            init();
+        } else if (!state.equals(LifecycleState.INITIALIZED) &&
+                !state.equals(LifecycleState.STOPPED)) {
+            invalidTransition(Lifecycle.BEFORE_START_EVENT);
+        }
+
+        setStateInternal(LifecycleState.STARTING_PREP, null, false);
+
+        try {
+            startInternal();
+        } catch (LifecycleException e) {
+            setStateInternal(LifecycleState.FAILED, null, false);
+            throw e;
+        }
+
+        if (state.equals(LifecycleState.FAILED) ||
+                state.equals(LifecycleState.MUST_STOP)) {
+            stop();
+        } else {
+            // Shouldn't be necessary but acts as a check that sub-classes are
+            // doing what they are supposed to.
+            if (!state.equals(LifecycleState.STARTING)) {
+                invalidTransition(Lifecycle.AFTER_START_EVENT);
+            }
+            
+            setStateInternal(LifecycleState.STARTED, null, false);
+        }
+    }
+
+
+    /**
+     * Sub-classes must ensure that the state is changed to
+     * {@link LifecycleState#STARTING} during the execution of this method.
+     * Changing state will trigger the {@link Lifecycle#START_EVENT} event.
+     * 
+     * If a component fails to start it may either throw a
+     * {@link LifecycleException} which will cause it's parent to fail to start
+     * or it can place itself in the error state in which case {@link #stop()}
+     * will be called on the failed component but the parent component will
+     * continue to start normally.
+     * 
+     * @throws LifecycleException
+     */
+    protected abstract void startInternal() throws LifecycleException;
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public final synchronized void stop() throws LifecycleException {
+
+        if (LifecycleState.STOPPING_PREP.equals(state) ||
+                LifecycleState.STOPPING.equals(state) ||
+                LifecycleState.STOPPED.equals(state)) {
+
+            if (log.isDebugEnabled()) {
+                Exception e = new LifecycleException();
+                log.debug(sm.getString("lifecycleBase.alreadyStopped",
+                        toString()), e);
+            } else if (log.isInfoEnabled()) {
+                log.info(sm.getString("lifecycleBase.alreadyStopped",
+                        toString()));
+            }
+            
+            return;
+        }
+        
+        if (state.equals(LifecycleState.NEW)) {
+            state = LifecycleState.STOPPED;
+            return;
+        }
+
+        if (!state.equals(LifecycleState.STARTED) &&
+                !state.equals(LifecycleState.FAILED) &&
+                !state.equals(LifecycleState.MUST_STOP)) {
+            invalidTransition(Lifecycle.BEFORE_STOP_EVENT);
+        }
+        
+        if (state.equals(LifecycleState.FAILED)) {
+            // Don't transition to STOPPING_PREP as that would briefly mark the
+            // component as available but do ensure the BEFORE_STOP_EVENT is
+            // fired
+            fireLifecycleEvent(BEFORE_STOP_EVENT, null);
+        } else {
+            setStateInternal(LifecycleState.STOPPING_PREP, null, false);
+        }
+
+        try {
+            stopInternal();
+        } catch (LifecycleException e) {
+            setStateInternal(LifecycleState.FAILED, null, false);
+            throw e;
+        }
+
+        if (state.equals(LifecycleState.MUST_DESTROY)) {
+            // Complete stop process first
+            setStateInternal(LifecycleState.STOPPED, null, false);
+
+            destroy();
+        } else {
+            // Shouldn't be necessary but acts as a check that sub-classes are
+            // doing what they are supposed to.
+            if (!state.equals(LifecycleState.STOPPING)) {
+                invalidTransition(Lifecycle.AFTER_STOP_EVENT);
+            }
+
+            setStateInternal(LifecycleState.STOPPED, null, false);
+        }
+    }
+
+
+    /**
+     * Sub-classes must ensure that the state is changed to
+     * {@link LifecycleState#STOPPING} during the execution of this method.
+     * Changing state will trigger the {@link Lifecycle#STOP_EVENT} event.
+     * 
+     * @throws LifecycleException
+     */
+    protected abstract void stopInternal() throws LifecycleException;
+
+
+    @Override
+    public final synchronized void destroy() throws LifecycleException {
+        if (LifecycleState.DESTROYING.equals(state) ||
+                LifecycleState.DESTROYED.equals(state)) {
+
+            if (log.isDebugEnabled()) {
+                Exception e = new LifecycleException();
+                log.debug(sm.getString("lifecycleBase.alreadyDestroyed",
+                        toString()), e);
+            } else if (log.isInfoEnabled()) {
+                log.info(sm.getString("lifecycleBase.alreadyDestroyed",
+                        toString()));
+            }
+            
+            return;
+        }
+        
+        if (!state.equals(LifecycleState.STOPPED) &&
+                !state.equals(LifecycleState.FAILED) &&
+                !state.equals(LifecycleState.NEW)) {
+            invalidTransition(Lifecycle.BEFORE_DESTROY_EVENT);
+        }
+
+        setStateInternal(LifecycleState.DESTROYING, null, false);
+        
+        try {
+            destroyInternal();
+        } catch (LifecycleException e) {
+            setStateInternal(LifecycleState.FAILED, null, false);
+            throw e;
+        }
+        
+        setStateInternal(LifecycleState.DESTROYED, null, false);
+    }
+    
+    
+    protected abstract void destroyInternal() throws LifecycleException;
+    
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public LifecycleState getState() {
+        return state;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getStateName() {
+        return getState().toString();
+    }
+
+
+    /**
+     * Provides a mechanism for sub-classes to update the component state.
+     * Calling this method will automatically fire any associated
+     * {@link Lifecycle} event. It will also check that any attempted state
+     * transition is valid for a sub-class.
+     * 
+     * @param state The new state for this component
+     */
+    protected synchronized void setState(LifecycleState state)
+            throws LifecycleException {
+        setStateInternal(state, null, true);
+    }
+    
+    
+    /**
+     * Provides a mechanism for sub-classes to update the component state.
+     * Calling this method will automatically fire any associated
+     * {@link Lifecycle} event. It will also check that any attempted state
+     * transition is valid for a sub-class.
+     * 
+     * @param state The new state for this component
+     * @param data  The data to pass to the associated {@link Lifecycle} event
+     */
+    protected synchronized void setState(LifecycleState state, Object data)
+            throws LifecycleException {
+        setStateInternal(state, data, true);
+    }
+
+    private synchronized void setStateInternal(LifecycleState state,
+            Object data, boolean check) throws LifecycleException {
+        
+        if (log.isDebugEnabled()) {
+            log.debug(sm.getString("lifecycleBase.setState", this, state));
+        }
+        
+        if (check) {
+            // Must have been triggered by one of the abstract methods (assume
+            // code in this class is correct)
+            // null is never a valid state
+            if (state == null) {
+                invalidTransition("null");
+                // Unreachable code - here to stop eclipse complaining about
+                // a possible NPE further down the method
+                return;
+            }
+            
+            // Any method can transition to failed
+            // startInternal() permits STARTING_PREP to STARTING
+            // stopInternal() permits STOPPING_PREP to STOPPING and FAILED to
+            // STOPPING
+            if (!(state == LifecycleState.FAILED ||
+                    (this.state == LifecycleState.STARTING_PREP &&
+                            state == LifecycleState.STARTING) ||
+                    (this.state == LifecycleState.STOPPING_PREP &&
+                            state == LifecycleState.STOPPING) ||
+                    (this.state == LifecycleState.FAILED &&
+                            state == LifecycleState.STOPPING))) {
+                // No other transition permitted
+                invalidTransition(state.name());
+            }
+        }
+        
+        this.state = state;
+        String lifecycleEvent = state.getLifecycleEvent();
+        if (lifecycleEvent != null) {
+            fireLifecycleEvent(lifecycleEvent, data);
+        }
+    }
+
+    private void invalidTransition(String type) throws LifecycleException {
+        String msg = sm.getString("lifecycleBase.invalidTransition", type,
+                toString(), state);
+        throw new LifecycleException(msg);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/LifecycleMBeanBase.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/LifecycleMBeanBase.java
new file mode 100644
index 0000000..2939d92
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/LifecycleMBeanBase.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.util;
+
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanRegistration;
+import javax.management.MBeanRegistrationException;
+import javax.management.MBeanServer;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+
+import org.apache.catalina.Globals;
+import org.apache.catalina.LifecycleException;
+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;
+
+public abstract class LifecycleMBeanBase extends LifecycleBase
+        implements MBeanRegistration {
+
+    private static Log log = LogFactory.getLog(LifecycleMBeanBase.class);
+    
+    private static StringManager sm =
+        StringManager.getManager("org.apache.catalina.util");
+
+    
+    /* Cache components of the MBean registration. */
+    private String domain = null;
+    private ObjectName oname = null;
+    protected MBeanServer mserver = null;
+    
+    /**
+     * Sub-classes wishing to perform additional initialization should override
+     * this method, ensuring that super.initInternal() is the first call in the
+     * overriding method.
+     */
+    @Override
+    protected void initInternal() throws LifecycleException {
+        
+        // If oname is not null then registration has already happened via
+        // preRegister().
+        if (oname == null) {
+            mserver = Registry.getRegistry(null, null).getMBeanServer();
+            
+            oname = register(this, getObjectNameKeyProperties());
+        }
+    }
+
+    
+    /**
+     * Sub-classes wishing to perform additional clean-up should override this
+     * method, ensuring that super.destroyInternal() is the last call in the
+     * overriding method.
+     */
+    @Override
+    protected void destroyInternal() throws LifecycleException {
+        unregister(oname);
+    }
+
+    
+    /**
+     * Specify the domain under which this component should be registered. Used
+     * with components that cannot (easily) navigate the component hierarchy to
+     * determine the correct domain to use. 
+     */
+    public final void setDomain(String domain) {
+        this.domain = domain;
+    }
+
+    
+    /**
+     * Obtain the domain under which this component will be / has been
+     * registered.
+     */
+    public final String getDomain() {
+        if (domain == null) {
+            domain = getDomainInternal();
+        }
+
+        if (domain == null) {
+            domain = Globals.DEFAULT_MBEAN_DOMAIN;
+        }
+        
+        return domain;
+    }
+
+    
+    /**
+     * Method implemented by sub-classes to identify the domain in which MBeans
+     * should be registered.
+     * 
+     * @return  The name of the domain to use to register MBeans.
+     */
+    protected abstract String getDomainInternal();
+
+    
+    /**
+     * Obtain the name under which this component has been registered with JMX.
+     */
+    public final ObjectName getObjectName() {
+        return oname;
+    }
+
+
+    /**
+     * Allow sub-classes to specify the key properties component of the
+     * {@link ObjectName} that will be used to register this component.
+     * 
+     * @return  The string representation of the key properties component of the
+     *          desired {@link ObjectName}
+     */
+    protected abstract String getObjectNameKeyProperties();
+    
+    
+    /**
+     * Utility method to enable sub-classes to easily register additional
+     * components that don't implement {@link MBeanRegistration} with
+     * an MBean server.<br/>
+     * Note: This method should only be used once {@link #initInternal()} has
+     * been called and before {@link #destroyInternal()} has been called. 
+     * 
+     * @param obj                       The object the register
+     * @param objectNameKeyProperties   The key properties component of the
+     *                                  object name to use to register the
+     *                                  object
+     *
+     * @return  The name used to register the object
+     */
+    protected final ObjectName register(Object obj,
+            String objectNameKeyProperties) {
+        
+        // Construct an object name with the right domain
+        StringBuilder name = new StringBuilder(getDomain());
+        name.append(':');
+        name.append(objectNameKeyProperties);
+
+        ObjectName on = null;
+
+        try {
+            on = new ObjectName(name.toString());
+            
+            Registry.getRegistry(null, null).registerComponent(obj, on, null);
+        } catch (MalformedObjectNameException e) {
+            log.warn(sm.getString("lifecycleMBeanBase.registerFail", obj, name),
+                    e);
+        } catch (Exception e) {
+            log.warn(sm.getString("lifecycleMBeanBase.registerFail", obj, name),
+                    e);
+        }
+
+        return on;
+    }
+    
+    
+    /**
+     * Utility method to enable sub-classes to easily unregister additional
+     * components that don't implement {@link MBeanRegistration} with
+     * an MBean server.<br/>
+     * Note: This method should only be used once {@link #initInternal()} has
+     * been called and before {@link #destroyInternal()} has been called. 
+     * 
+     * @param on    The name of the component to unregister
+     */
+    protected final void unregister(ObjectName on) {
+        
+        // If null ObjectName, just return without complaint
+        if (on == null) {
+            return;
+        }
+        
+        // If the MBeanServer is null, log a warning & return
+        if (mserver == null) {
+            log.warn(sm.getString("lifecycleMBeanBase.unregisterNoServer", on));
+            return;
+        }
+        
+        try {
+            mserver.unregisterMBean(on);
+        } catch (MBeanRegistrationException e) {
+            log.warn(sm.getString("lifecycleMBeanBase.unregisterFail", on), e);
+        } catch (InstanceNotFoundException e) {
+            log.warn(sm.getString("lifecycleMBeanBase.unregisterFail", on), e);
+        }
+
+    }
+    
+    
+    /**
+     * Not used - NOOP.
+     */
+    @Override
+    public final void postDeregister() {
+        // NOOP
+    }
+
+    
+    /**
+     * Not used - NOOP.
+     */
+    @Override
+    public final void postRegister(Boolean registrationDone) {
+        // NOOP
+    }
+
+    
+    /**
+     * Not used - NOOP.
+     */
+    @Override
+    public final void preDeregister() throws Exception {
+        // NOOP
+    }
+
+
+    /**
+     * Allows the object to be registered with an alternative
+     * {@link MBeanServer} and/or {@link ObjectName}.
+     */
+    @Override
+    public final ObjectName preRegister(MBeanServer server, ObjectName name)
+            throws Exception {
+        
+        this.mserver = server;
+        this.oname = name;
+        this.domain = name.getDomain();
+
+        return oname;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/LifecycleSupport.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/LifecycleSupport.java
new file mode 100644
index 0000000..f0d9dc5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/LifecycleSupport.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.util;
+
+
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleListener;
+
+
+/**
+ * Support class to assist in firing LifecycleEvent notifications to
+ * registered LifecycleListeners.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: LifecycleSupport.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public final class LifecycleSupport {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new LifecycleSupport object associated with the specified
+     * Lifecycle component.
+     *
+     * @param lifecycle The Lifecycle component that will be the source
+     *  of events that we fire
+     */
+    public LifecycleSupport(Lifecycle lifecycle) {
+
+        super();
+        this.lifecycle = lifecycle;
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The source component for lifecycle events that we will fire.
+     */
+    private Lifecycle lifecycle = null;
+
+
+    /**
+     * The set of registered LifecycleListeners for event notifications.
+     */
+    private LifecycleListener listeners[] = new LifecycleListener[0];
+    
+    private final Object listenersLock = new Object(); // Lock object for changes to listeners
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add a lifecycle event listener to this component.
+     *
+     * @param listener The listener to add
+     */
+    public void addLifecycleListener(LifecycleListener listener) {
+
+      synchronized (listenersLock) {
+          LifecycleListener results[] =
+            new LifecycleListener[listeners.length + 1];
+          for (int i = 0; i < listeners.length; i++)
+              results[i] = listeners[i];
+          results[listeners.length] = listener;
+          listeners = results;
+      }
+
+    }
+
+
+    /**
+     * Get the lifecycle listeners associated with this lifecycle. If this 
+     * Lifecycle has no listeners registered, a zero-length array is returned.
+     */
+    public LifecycleListener[] findLifecycleListeners() {
+
+        return listeners;
+
+    }
+
+
+    /**
+     * Notify all lifecycle event listeners that a particular event has
+     * occurred for this Container.  The default implementation performs
+     * this notification synchronously using the calling thread.
+     *
+     * @param type Event type
+     * @param data Event data
+     */
+    public void fireLifecycleEvent(String type, Object data) {
+
+        LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
+        LifecycleListener interested[] = listeners;
+        for (int i = 0; i < interested.length; i++)
+            interested[i].lifecycleEvent(event);
+
+    }
+
+
+    /**
+     * Remove a lifecycle event listener from this component.
+     *
+     * @param listener The listener to remove
+     */
+    public void removeLifecycleListener(LifecycleListener listener) {
+
+        synchronized (listenersLock) {
+            int n = -1;
+            for (int i = 0; i < listeners.length; i++) {
+                if (listeners[i] == listener) {
+                    n = i;
+                    break;
+                }
+            }
+            if (n < 0)
+                return;
+            LifecycleListener results[] =
+              new LifecycleListener[listeners.length - 1];
+            int j = 0;
+            for (int i = 0; i < listeners.length; i++) {
+                if (i != n)
+                    results[j++] = listeners[i];
+            }
+            listeners = results;
+        }
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/util/LocalStrings.properties
new file mode 100644
index 0000000..af98d77
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/LocalStrings.properties
@@ -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.
+
+parameterMap.locked=No modifications are allowed to a locked ParameterMap
+resourceSet.locked=No modifications are allowed to a locked ResourceSet
+hexUtil.bad=Bad hexadecimal digit
+hexUtil.odd=Odd number of hexadecimal digits
+#Default Messages Utilized by the ExtensionValidator
+extensionValidator.web-application-manifest=Web Application Manifest
+extensionValidator.extension-not-found-error=ExtensionValidator[{0}][{1}]: Required extension [{2}] not found.
+extensionValidator.extension-validation-error=ExtensionValidator[{0}]: Failure to find [{1}] required extension(s).
+extensionValidator.failload=Failure loading extension [{0}]
+lifecycleBase.initMBeanFail=Failed to register component [{0}] with MBean name [{1}]
+lifecycleBase.alreadyStarted=The start() method was called on component [{0}] after start() had already been called. The second call will be ignored.
+lifecycleBase.alreadyStopped=The stop() method was called on component [{0}] after stop() had already been called. The second call will be ignored.
+lifecycleBase.alreadyDestroyed=The destroy() method was called on component [{0}] after destroy() had already been called. The second call will be ignored.
+lifecycleBase.invalidTransition=An invalid Lifecycle transition was attempted ([{0}]) for component [{1}] in state [{2}]
+lifecycleBase.setState=Setting state for [{0}] to [{1}]
+lifecycleMBeanBase.registerFail=Failed to register object [{0}] with name [{0}] during component initialisation
+lifecycleMBeanBase.unregisterFail=Failed to unregister MBean with name [{0}] during component destruction
+lifecycleMBeanBase.unregisterNoServer=No MBean server was available to unregister the MBean [{0}]
+requestUtil.convertHexDigit.notHex=[{0}] is not a hexadecimal digit
+requestUtil.parseParameters.uee=Unable to parse the parameters since the encoding [{0}] is not supported.
+requestUtil.urlDecode.missingDigit=The % character must be followed by two hexademical digits
+requestUtil.urlDecode.uee=Unable to URL decode the specified input since the encoding [{0}] is not supported.
+SecurityUtil.doAsPrivilege=An exception occurs when running the PrivilegedExceptionAction block.
+sessionIdGenerator.createRandom=Creation of SecureRandom instance for session ID generation using [{0}] took [{1}] milliseconds.
+sessionIdGenerator.random=Exception initializing random number generator of class [{0}]. Falling back to java.secure.SecureRandom
+sessionIdGenerator.randomAlgorithm=Exception initializing random number generator using algorithm [{0}] 
+sessionIdGenerator.randomProviderException initializing random number generator using provider [{0}]
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/util/LocalStrings_es.properties
new file mode 100644
index 0000000..604e715
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/LocalStrings_es.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.
+parameterMap.locked = No se permiten modificaciones en un ParameterMap bloqueado
+resourceSet.locked = No se permiten modificaciones en un ResourceSet bloqueado
+hexUtil.bad = D\u00EDgito hexadecimal incorrecto
+hexUtil.odd = N\u00FAmero de d\u00EDgitos hexadecimales impar
+#Default Messages Utilized by the ExtensionValidator
+extensionValidator.web-application-manifest = Manifiesto de Aplicaci\u00F3n Web
+extensionValidator.extension-not-found-error = ExtensionValidator[{0}][{1}]\: La extensi\u00F3n no encuentra el "{2}" requerido.
+extensionValidator.extension-validation-error = ExtensionValidator[{0}]\: Imposible de hallar la(s) extension(es) {1} requerida(s).
+extensionValidator.failload = No pude cargar la extensi\u00F3n {0}
+SecurityUtil.doAsPrivilege = Una excepci\u00F3n se ha producido durante la ejecuci\u00F3n del bloque PrivilegedExceptionAction.
+sessionIdGenerator.random = Excepci\u00F3n inicializando generador de n\u00FAmeros aleatorios de clase {0}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/util/LocalStrings_fr.properties
new file mode 100644
index 0000000..5053a9d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/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.
+
+parameterMap.locked=Aucune modification n''est authoris\u00e9e sur un ParameterMap verrouill\u00e9
+resourceSet.locked=Aucune modification n''est authoris\u00e9e sur un ResourceSet verrouill\u00e9
+hexUtil.bad=Mauvais digit hexadecimal
+hexUtil.odd=Nombre impair de digits hexadecimaux
+#Default Messages Utilized by the ExtensionValidator
+extensionValidator.web-application-manifest=Web Application Manifest
+extensionValidator.extension-not-found-error=ExtensionValidator[{0}][{1}]: L''extension requise "{2}" est introuvable.
+extensionValidator.extension-validation-error=ExtensionValidator[{0}]: Impossible de trouver {1} extension(s) requise(s).
+SecurityUtil.doAsPrivilege=Une exception s''est produite lors de l''execution du bloc PrivilegedExceptionAction.
+sessionIdGenerator.random=Exception durant l''initialisation de la classe du g\u00e9n\u00e9rateur de nombre al\u00e9atoire {0}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/LocalStrings_ja.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/util/LocalStrings_ja.properties
new file mode 100644
index 0000000..b3799e2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/LocalStrings_ja.properties
@@ -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.
+
+parameterMap.locked=\u30ed\u30c3\u30af\u3055\u308c\u305fParameterMap\u306f\u5909\u66f4\u304c\u8a31\u3055\u308c\u307e\u305b\u3093
+resourceSet.locked=\u30ed\u30c3\u30af\u3055\u308c\u305fResourceSet\u306f\u5909\u66f4\u304c\u8a31\u3055\u308c\u307e\u305b\u3093
+hexUtil.bad=\u7121\u52b9\u306a16\u9032\u6570\u5024\u3067\u3059
+hexUtil.odd=\u5947\u6570\u6841\u306e16\u9032\u6570\u5024\u3067\u3059
+#Default Messages Utilized by the ExtensionValidator
+extensionValidator.web-application-manifest=Web\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30de\u30cb\u30d5\u30a7\u30b9\u30c8
+extensionValidator.extension-not-found-error=ExtensionValidator[{0}][{1}]: \u5fc5\u8981\u306a\u62e1\u5f35 "{2}" \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002
+extensionValidator.extension-validation-error=ExtensionValidator[{0}]: \u5fc5\u8981\u306a\u62e1\u5f35 "{1}" \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002
+extensionValidator.failload=\u62e1\u5f35 {0} \u306e\u30ed\u30fc\u30c9\u306b\u5931\u6557\u3057\u307e\u3057\u305f
+SecurityUtil.doAsPrivilege=PrivilegedExceptionAction\u30d6\u30ed\u30c3\u30af\u3092\u5b9f\u884c\u4e2d\u306b\u4f8b\u5916\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002
+sessionIdGenerator.random=\u30af\u30e9\u30b9 {0} \u306e\u4e71\u6570\u767a\u751f\u5668\u306e\u521d\u671f\u5316\u306e\u4f8b\u5916\u3067\u3059
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/MD5Encoder.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/MD5Encoder.java
new file mode 100644
index 0000000..50bc8ea
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/MD5Encoder.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.util;
+
+
+/**
+ * Encode an MD5 digest into a String.
+ * <p>
+ * The 128 bit MD5 hash is converted into a 32 character long String.
+ * Each character of the String is the hexadecimal representation of 4 bits
+ * of the digest.
+ *
+ * @author Remy Maucherat
+ * @version $Id: MD5Encoder.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public final class MD5Encoder {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    private static final char[] hexadecimal =
+    {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+     'a', 'b', 'c', 'd', 'e', 'f'};
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Encodes the 128 bit (16 bytes) MD5 into a 32 character String.
+     *
+     * @param binaryData Array containing the digest
+     * @return Encoded MD5, or null if encoding failed
+     */
+    public String encode( byte[] binaryData ) {
+
+        if (binaryData.length != 16)
+            return null;
+
+        char[] buffer = new char[32];
+
+        for (int i=0; i<16; i++) {
+            int low = binaryData[i] & 0x0f;
+            int high = (binaryData[i] & 0xf0) >> 4;
+            buffer[i*2] = hexadecimal[high];
+            buffer[i*2 + 1] = hexadecimal[low];
+        }
+
+        return new String(buffer);
+
+    }
+
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/MIME2Java.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/MIME2Java.java
new file mode 100644
index 0000000..2ee4254
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/MIME2Java.java
@@ -0,0 +1,604 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.util;
+
+import java.util.Hashtable;
+import java.util.Locale;
+
+/**
+ * MIME2Java is a convenience class which handles conversions between MIME charset names
+ * and Java encoding names.
+ * <p>The supported XML encodings are the intersection of XML-supported code sets and those
+ * supported in JDK 1.1.
+ * <p>MIME charset names are used on <var>xmlEncoding</var> parameters to methods such
+ * as <code>TXDocument#setEncoding</code> and <code>DTD#setEncoding</code>.
+ * <p>Java encoding names are used on <var>encoding</var> parameters to
+ * methods such as <code>TXDocument#printWithFormat</code> and <code>DTD#printExternal</code>.
+ * <P>
+ * <TABLE BORDER="0" WIDTH="100%">
+ *  <TR>
+ *      <TD WIDTH="33%">
+ *          <P ALIGN="CENTER"><B>Common Name</B>
+ *      </TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER"><B>Use this name in XML files</B>
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER"><B>Name Type</B>
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER"><B>Xerces converts to this Java Encoder Name</B>
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">8 bit Unicode</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">UTF-8
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">UTF8
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin 1</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-1
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-1
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin 2</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-2
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-2
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin 3</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-3
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-3
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin 4</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-4
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-4
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin Cyrillic</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-5
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-5
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin Arabic</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-6
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-6
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin Greek</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-7
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-7
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin Hebrew</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-8
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-8
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin 5</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-9
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-9
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: US</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-us
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp037
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Canada</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-ca
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp037
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Netherlands</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-nl
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp037
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Denmark</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-dk
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp277
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Norway</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-no
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp277
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Finland</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-fi
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp278
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Sweden</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-se
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp278
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Italy</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-it
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp280
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Spain, Latin America</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-es
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp284
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Great Britain</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-gb
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp285
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: France</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-fr
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp297
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Arabic</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-ar1
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp420
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Hebrew</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-he
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp424
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Switzerland</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-ch
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp500
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Roece</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-roece
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp870
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Yogoslavia</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-yu
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp870
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Iceland</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-is
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp871
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Urdu</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-ar2
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp918
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">Chinese for PRC, mixed 1/2 byte</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">gb2312
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">GB2312
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">Extended Unix Code, packed for Japanese</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">euc-jp
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">eucjis
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">Japanese: iso-2022-jp</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">iso-2020-jp
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">JIS
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">Japanese: Shift JIS</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">Shift_JIS
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">SJIS
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">Chinese: Big5</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">Big5
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">Big5
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">Extended Unix Code, packed for Korean</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">euc-kr
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">iso2022kr
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">Cyrillic</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">koi8-r
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">koi8-r
+ *      </TD>
+ *  </TR>
+ * </TABLE>
+ *
+ * @version $Id: MIME2Java.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ * @author TAMURA Kent &lt;kent@trl.ibm.co.jp&gt;
+ */
+public class MIME2Java {
+
+    private static Hashtable<String,String> s_enchash;
+    private static Hashtable<String,String> s_revhash;
+
+    static {
+        s_enchash = new Hashtable<String,String>();
+        //    <preferred MIME name>, <Java encoding name>
+        s_enchash.put("UTF-8", "UTF8");
+        s_enchash.put("US-ASCII",        "8859_1");    // ?
+        s_enchash.put("ISO-8859-1",      "8859_1");
+        s_enchash.put("ISO-8859-2",      "8859_2");
+        s_enchash.put("ISO-8859-3",      "8859_3");
+        s_enchash.put("ISO-8859-4",      "8859_4");
+        s_enchash.put("ISO-8859-5",      "8859_5");
+        s_enchash.put("ISO-8859-6",      "8859_6");
+        s_enchash.put("ISO-8859-7",      "8859_7");
+        s_enchash.put("ISO-8859-8",      "8859_8");
+        s_enchash.put("ISO-8859-9",      "8859_9");
+        s_enchash.put("ISO-2022-JP",     "JIS");
+        s_enchash.put("SHIFT_JIS",       "SJIS");
+        s_enchash.put("EUC-JP",          "EUCJIS");
+        s_enchash.put("GB2312",          "GB2312");
+        s_enchash.put("BIG5",            "Big5");
+        s_enchash.put("EUC-KR",          "KSC5601");
+        s_enchash.put("ISO-2022-KR",     "ISO2022KR");
+        s_enchash.put("KOI8-R",          "KOI8_R");
+
+        s_enchash.put("EBCDIC-CP-US",    "CP037");
+        s_enchash.put("EBCDIC-CP-CA",    "CP037");
+        s_enchash.put("EBCDIC-CP-NL",    "CP037");
+        s_enchash.put("EBCDIC-CP-DK",    "CP277");
+        s_enchash.put("EBCDIC-CP-NO",    "CP277");
+        s_enchash.put("EBCDIC-CP-FI",    "CP278");
+        s_enchash.put("EBCDIC-CP-SE",    "CP278");
+        s_enchash.put("EBCDIC-CP-IT",    "CP280");
+        s_enchash.put("EBCDIC-CP-ES",    "CP284");
+        s_enchash.put("EBCDIC-CP-GB",    "CP285");
+        s_enchash.put("EBCDIC-CP-FR",    "CP297");
+        s_enchash.put("EBCDIC-CP-AR1",   "CP420");
+        s_enchash.put("EBCDIC-CP-HE",    "CP424");
+        s_enchash.put("EBCDIC-CP-CH",    "CP500");
+        s_enchash.put("EBCDIC-CP-ROECE", "CP870");
+        s_enchash.put("EBCDIC-CP-YU",    "CP870");
+        s_enchash.put("EBCDIC-CP-IS",    "CP871");
+        s_enchash.put("EBCDIC-CP-AR2",   "CP918");
+
+                                                // j:CNS11643 -> EUC-TW?
+                                                // ISO-2022-CN? ISO-2022-CN-EXT?
+
+        s_revhash = new Hashtable<String,String>();
+        //    <Java encoding name>, <preferred MIME name>
+        s_revhash.put("UTF8", "UTF-8");
+        //s_revhash.put("8859_1", "US-ASCII");    // ?
+        s_revhash.put("8859_1", "ISO-8859-1");
+        s_revhash.put("8859_2", "ISO-8859-2");
+        s_revhash.put("8859_3", "ISO-8859-3");
+        s_revhash.put("8859_4", "ISO-8859-4");
+        s_revhash.put("8859_5", "ISO-8859-5");
+        s_revhash.put("8859_6", "ISO-8859-6");
+        s_revhash.put("8859_7", "ISO-8859-7");
+        s_revhash.put("8859_8", "ISO-8859-8");
+        s_revhash.put("8859_9", "ISO-8859-9");
+        s_revhash.put("JIS", "ISO-2022-JP");
+        s_revhash.put("SJIS", "Shift_JIS");
+        s_revhash.put("EUCJIS", "EUC-JP");
+        s_revhash.put("GB2312", "GB2312");
+        s_revhash.put("BIG5", "Big5");
+        s_revhash.put("KSC5601", "EUC-KR");
+        s_revhash.put("ISO2022KR", "ISO-2022-KR");
+        s_revhash.put("KOI8_R", "KOI8-R");
+
+        s_revhash.put("CP037", "EBCDIC-CP-US");
+        s_revhash.put("CP037", "EBCDIC-CP-CA");
+        s_revhash.put("CP037", "EBCDIC-CP-NL");
+        s_revhash.put("CP277", "EBCDIC-CP-DK");
+        s_revhash.put("CP277", "EBCDIC-CP-NO");
+        s_revhash.put("CP278", "EBCDIC-CP-FI");
+        s_revhash.put("CP278", "EBCDIC-CP-SE");
+        s_revhash.put("CP280", "EBCDIC-CP-IT");
+        s_revhash.put("CP284", "EBCDIC-CP-ES");
+        s_revhash.put("CP285", "EBCDIC-CP-GB");
+        s_revhash.put("CP297", "EBCDIC-CP-FR");
+        s_revhash.put("CP420", "EBCDIC-CP-AR1");
+        s_revhash.put("CP424", "EBCDIC-CP-HE");
+        s_revhash.put("CP500", "EBCDIC-CP-CH");
+        s_revhash.put("CP870", "EBCDIC-CP-ROECE");
+        s_revhash.put("CP870", "EBCDIC-CP-YU");
+        s_revhash.put("CP871", "EBCDIC-CP-IS");
+        s_revhash.put("CP918", "EBCDIC-CP-AR2");
+    }
+
+    private MIME2Java() {
+    }
+
+    /**
+     * Convert a MIME charset name, also known as an XML encoding name, to a Java encoding name.
+     * @param   mimeCharsetName Case insensitive MIME charset name: <code>UTF-8, US-ASCII, ISO-8859-1,
+     *                          ISO-8859-2, ISO-8859-3, ISO-8859-4, ISO-8859-5, ISO-8859-6,
+     *                          ISO-8859-7, ISO-8859-8, ISO-8859-9, ISO-2022-JP, Shift_JIS,
+     *                          EUC-JP, GB2312, Big5, EUC-KR, ISO-2022-KR, KOI8-R,
+     *                          EBCDIC-CP-US, EBCDIC-CP-CA, EBCDIC-CP-NL, EBCDIC-CP-DK,
+     *                          EBCDIC-CP-NO, EBCDIC-CP-FI, EBCDIC-CP-SE, EBCDIC-CP-IT,
+     *                          EBCDIC-CP-ES, EBCDIC-CP-GB, EBCDIC-CP-FR, EBCDIC-CP-AR1,
+     *                          EBCDIC-CP-HE, EBCDIC-CP-CH, EBCDIC-CP-ROECE, EBCDIC-CP-YU,
+     *                          EBCDIC-CP-IS and EBCDIC-CP-AR2</code>.
+     * @return                  Java encoding name, or <var>null</var> if <var>mimeCharsetName</var>
+     *                          is unknown.
+     * @see #reverse
+     */
+    public static String convert(String mimeCharsetName) {
+        return s_enchash.get(mimeCharsetName.toUpperCase(Locale.ENGLISH));
+    }
+
+    /**
+     * Convert a Java encoding name to MIME charset name.
+     * Available values of <i>encoding</i> are "UTF8", "8859_1", "8859_2", "8859_3", "8859_4",
+     * "8859_5", "8859_6", "8859_7", "8859_8", "8859_9", "JIS", "SJIS", "EUCJIS",
+     * "GB2312", "BIG5", "KSC5601", "ISO2022KR",  "KOI8_R", "CP037", "CP277", "CP278",
+     * "CP280", "CP284", "CP285", "CP297", "CP420", "CP424", "CP500", "CP870", "CP871" and "CP918".
+     * @param   encoding    Case insensitive Java encoding name: <code>UTF8, 8859_1, 8859_2, 8859_3,
+     *                      8859_4, 8859_5, 8859_6, 8859_7, 8859_8, 8859_9, JIS, SJIS, EUCJIS,
+     *                      GB2312, BIG5, KSC5601, ISO2022KR, KOI8_R, CP037, CP277, CP278,
+     *                      CP280, CP284, CP285, CP297, CP420, CP424, CP500, CP870, CP871
+     *                      and CP918</code>.
+     * @return              MIME charset name, or <var>null</var> if <var>encoding</var> is unknown.
+     * @see #convert
+     */
+    public static String reverse(String encoding) {
+        return s_revhash.get(encoding.toUpperCase(Locale.ENGLISH));
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/ManifestResource.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/ManifestResource.java
new file mode 100644
index 0000000..dd260d5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/ManifestResource.java
@@ -0,0 +1,243 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.util;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+/**
+ *  Representation of a Manifest file and its available extensions and
+ *  required extensions
+ *  
+ * @author Greg Murray
+ * @author Justyna Horwat
+ * @version $Id: ManifestResource.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ * 
+ */
+public class ManifestResource {
+    
+    // ------------------------------------------------------------- Properties
+
+    // These are the resource types for determining effect error messages
+    public static final int SYSTEM = 1;
+    public static final int WAR = 2;
+    public static final int APPLICATION = 3;
+    
+    private ArrayList<Extension> availableExtensions = null;
+    private ArrayList<Extension> requiredExtensions = null;
+    
+    private String resourceName = null;
+    private int resourceType = -1;
+        
+    public ManifestResource(String resourceName, Manifest manifest, 
+                            int resourceType) {
+        this.resourceName = resourceName;
+        this.resourceType = resourceType;
+        processManifest(manifest);
+    }
+    
+    /**
+     * Gets the name of the resource
+     *
+     * @return The name of the resource
+     */
+    public String getResourceName() {
+        return resourceName;
+    }
+
+    /**
+     * Gets the list of available extensions
+     *
+     * @return List of available extensions
+     */
+    public ArrayList<Extension> getAvailableExtensions() {
+        return availableExtensions;
+    }
+    
+    /**
+     * Gets the list of required extensions
+     *
+     * @return List of required extensions
+     */
+    public ArrayList<Extension> getRequiredExtensions() {
+        return requiredExtensions;   
+    }
+    
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Gets the number of available extensions
+     *
+     * @return The number of available extensions
+     */
+    public int getAvailableExtensionCount() {
+        return (availableExtensions != null) ? availableExtensions.size() : 0;
+    }
+    
+    /**
+     * Gets the number of required extensions
+     *
+     * @return The number of required extensions
+     */
+    public int getRequiredExtensionCount() {
+        return (requiredExtensions != null) ? requiredExtensions.size() : 0;
+    }
+    
+    /**
+     * Convenience method to check if this <code>ManifestResource</code>
+     * has an requires extensions.
+     *
+     * @return true if required extensions are present
+     */
+    public boolean requiresExtensions() {
+        return (requiredExtensions != null) ? true : false;
+    }
+    
+    /**
+     * Returns <code>true</code> if all required extension dependencies
+     * have been meet for this <code>ManifestResource</code> object.
+     *
+     * @return boolean true if all extension dependencies have been satisfied
+     */
+    public boolean isFulfilled() {
+        if (requiredExtensions == null) {
+            return true;
+        }
+        Iterator<Extension> it = requiredExtensions.iterator();
+        while (it.hasNext()) {
+            Extension ext = it.next();
+            if (!ext.isFulfilled()) return false;            
+        }
+        return true;
+    }
+    
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("ManifestResource[");
+        sb.append(resourceName);
+
+        sb.append(", isFulfilled=");
+        sb.append(isFulfilled() +"");
+        sb.append(", requiredExtensionCount =");
+        sb.append(getRequiredExtensionCount());
+        sb.append(", availableExtensionCount=");
+        sb.append(getAvailableExtensionCount());
+        switch (resourceType) {
+            case SYSTEM : sb.append(", resourceType=SYSTEM"); break;
+            case WAR : sb.append(", resourceType=WAR"); break;
+            case APPLICATION : sb.append(", resourceType=APPLICATION"); break;
+        }
+        sb.append("]");
+        return (sb.toString());
+    }
+
+
+    // -------------------------------------------------------- Private Methods
+
+    private void processManifest(Manifest manifest) {
+        availableExtensions = getAvailableExtensions(manifest);
+        requiredExtensions = getRequiredExtensions(manifest);
+    }
+    
+    /**
+     * Return the set of <code>Extension</code> objects representing optional
+     * packages that are required by the application associated with the
+     * specified <code>Manifest</code>.
+     *
+     * @param manifest Manifest to be parsed
+     *
+     * @return List of required extensions, or null if the application
+     * does not require any extensions
+     */
+    private ArrayList<Extension> getRequiredExtensions(Manifest manifest) {
+
+        Attributes attributes = manifest.getMainAttributes();
+        String names = attributes.getValue("Extension-List");
+        if (names == null)
+            return null;
+
+        ArrayList<Extension> extensionList = new ArrayList<Extension>();
+        names += " ";
+
+        while (true) {
+
+            int space = names.indexOf(' ');
+            if (space < 0)
+                break;
+            String name = names.substring(0, space).trim();
+            names = names.substring(space + 1);
+
+            String value =
+                attributes.getValue(name + "-Extension-Name");
+            if (value == null)
+                continue;
+            Extension extension = new Extension();
+            extension.setExtensionName(value);
+            extension.setImplementationURL
+                (attributes.getValue(name + "-Implementation-URL"));
+            extension.setImplementationVendorId
+                (attributes.getValue(name + "-Implementation-Vendor-Id"));
+            String version = attributes.getValue(name + "-Implementation-Version");
+            extension.setImplementationVersion(version);
+            extension.setSpecificationVersion
+                (attributes.getValue(name + "-Specification-Version"));
+            extensionList.add(extension);
+        }
+        return extensionList;
+    }
+    
+    /**
+     * Return the set of <code>Extension</code> objects representing optional
+     * packages that are bundled with the application associated with the
+     * specified <code>Manifest</code>.
+     *
+     * @param manifest Manifest to be parsed
+     *
+     * @return List of available extensions, or null if the web application
+     * does not bundle any extensions
+     */
+    private ArrayList<Extension> getAvailableExtensions(Manifest manifest) {
+
+        Attributes attributes = manifest.getMainAttributes();
+        String name = attributes.getValue("Extension-Name");
+        if (name == null)
+            return null;
+
+        ArrayList<Extension> extensionList = new ArrayList<Extension>();
+
+        Extension extension = new Extension();
+        extension.setExtensionName(name);
+        extension.setImplementationURL(
+            attributes.getValue("Implementation-URL"));
+        extension.setImplementationVendor(
+            attributes.getValue("Implementation-Vendor"));
+        extension.setImplementationVendorId(
+            attributes.getValue("Implementation-Vendor-Id"));
+        extension.setImplementationVersion(
+            attributes.getValue("Implementation-Version"));
+        extension.setSpecificationVersion(
+            attributes.getValue("Specification-Version"));
+
+        extensionList.add(extension);
+
+        return extensionList;
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/ParameterMap.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/ParameterMap.java
new file mode 100644
index 0000000..18e38c9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/ParameterMap.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.
+ */
+
+
+package org.apache.catalina.util;
+
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Extended implementation of <strong>HashMap</strong> that includes a
+ * <code>locked</code> property.  This class can be used to safely expose
+ * Catalina internal parameter map objects to user classes without having
+ * to clone them in order to avoid modifications.  When first created, a
+ * <code>ParmaeterMap</code> instance is not locked.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: ParameterMap.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public final class ParameterMap<K,V> extends HashMap<K,V> {
+
+    private static final long serialVersionUID = 1L;
+
+
+    // ----------------------------------------------------------- Constructors
+    /**
+     * Construct a new, empty map with the default initial capacity and
+     * load factor.
+     */
+    public ParameterMap() {
+
+        super();
+
+    }
+
+
+    /**
+     * Construct a new, empty map with the specified initial capacity and
+     * default load factor.
+     *
+     * @param initialCapacity The initial capacity of this map
+     */
+    public ParameterMap(int initialCapacity) {
+
+        super(initialCapacity);
+
+    }
+
+
+    /**
+     * Construct a new, empty map with the specified initial capacity and
+     * load factor.
+     *
+     * @param initialCapacity The initial capacity of this map
+     * @param loadFactor The load factor of this map
+     */
+    public ParameterMap(int initialCapacity, float loadFactor) {
+
+        super(initialCapacity, loadFactor);
+
+    }
+
+
+    /**
+     * Construct a new map with the same mappings as the given map.
+     *
+     * @param map Map whose contents are duplicated in the new map
+     */
+    public ParameterMap(Map<K,V> map) {
+
+        super(map);
+
+    }
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * The current lock state of this parameter map.
+     */
+    private boolean locked = false;
+
+
+    /**
+     * Return the locked state of this parameter map.
+     */
+    public boolean isLocked() {
+
+        return (this.locked);
+
+    }
+
+
+    /**
+     * Set the locked state of this parameter map.
+     *
+     * @param locked The new locked state
+     */
+    public void setLocked(boolean locked) {
+
+        this.locked = locked;
+
+    }
+
+
+    /**
+     * The string manager for this package.
+     */
+    private static final StringManager sm =
+        StringManager.getManager("org.apache.catalina.util");
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+
+    /**
+     * Remove all mappings from this map.
+     *
+     * @exception IllegalStateException if this map is currently locked
+     */
+    @Override
+    public void clear() {
+
+        if (locked)
+            throw new IllegalStateException
+                (sm.getString("parameterMap.locked"));
+        super.clear();
+
+    }
+
+
+    /**
+     * Associate the specified value with the specified key in this map.  If
+     * the map previously contained a mapping for this key, the old value is
+     * replaced.
+     *
+     * @param key Key with which the specified value is to be associated
+     * @param value Value to be associated with the specified key
+     *
+     * @return The previous value associated with the specified key, or
+     *  <code>null</code> if there was no mapping for key
+     *
+     * @exception IllegalStateException if this map is currently locked
+     */
+    @Override
+    public V put(K key, V value) {
+
+        if (locked)
+            throw new IllegalStateException
+                (sm.getString("parameterMap.locked"));
+        return (super.put(key, value));
+
+    }
+
+
+    /**
+     * Copy all of the mappings from the specified map to this one.  These
+     * mappings replace any mappings that this map had for any of the keys
+     * currently in the specified Map.
+     *
+     * @param map Mappings to be stored into this map
+     *
+     * @exception IllegalStateException if this map is currently locked
+     */
+    @Override
+    public void putAll(Map<? extends K,? extends V> map) {
+
+        if (locked)
+            throw new IllegalStateException
+                (sm.getString("parameterMap.locked"));
+        super.putAll(map);
+
+    }
+
+
+    /**
+     * Remove the mapping for this key from the map if present.
+     *
+     * @param key Key whose mapping is to be removed from the map
+     *
+     * @return The previous value associated with the specified key, or
+     *  <code>null</code> if there was no mapping for that key
+     *
+     * @exception IllegalStateException if this map is currently locked
+     */
+    @Override
+    public V remove(Object key) {
+
+        if (locked)
+            throw new IllegalStateException
+                (sm.getString("parameterMap.locked"));
+        return (super.remove(key));
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/RequestUtil.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/RequestUtil.java
new file mode 100644
index 0000000..4f9a13a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/RequestUtil.java
@@ -0,0 +1,460 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.util;
+
+import java.io.UnsupportedEncodingException;
+import java.text.SimpleDateFormat;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * General purpose request parsing and encoding utility methods.
+ *
+ * @author Craig R. McClanahan
+ * @author Tim Tye
+ * @version $Id: RequestUtil.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public final class RequestUtil {
+
+
+    private static final Log log = LogFactory.getLog(RequestUtil.class);
+
+    /**
+     * The string resources for this package.
+     */
+    private static final StringManager sm =
+        StringManager.getManager("org.apache.catalina.util");
+    
+    /**
+     * The DateFormat to use for generating readable dates in cookies.
+     */
+    private static SimpleDateFormat format =
+        new SimpleDateFormat(" EEEE, dd-MMM-yy kk:mm:ss zz");
+
+    static {
+        format.setTimeZone(TimeZone.getTimeZone("GMT"));
+    }
+
+
+    /**
+     * 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());
+
+    }
+
+
+    /**
+     * Normalize a relative URI path that may have relative values ("/./",
+     * "/../", and so on ) it it.  <strong>WARNING</strong> - This method is
+     * useful only for normalizing application-generated paths.  It does not
+     * try to perform security checks for malicious input.
+     *
+     * @param path Relative path to be normalized
+     */
+    public static String normalize(String path) {
+        return normalize(path, true);
+    }
+
+    /**
+     * Normalize a relative URI path that may have relative values ("/./",
+     * "/../", and so on ) it it.  <strong>WARNING</strong> - This method is
+     * useful only for normalizing application-generated paths.  It does not
+     * try to perform security checks for malicious input.
+     *
+     * @param path Relative path to be normalized
+     * @param replaceBackSlash Should '\\' be replaced with '/'
+     */
+    public static String normalize(String path, boolean replaceBackSlash) {
+
+        if (path == null)
+            return null;
+
+        // Create a place for the normalized path
+        String normalized = path;
+
+        if (replaceBackSlash && normalized.indexOf('\\') >= 0)
+            normalized = normalized.replace('\\', '/');
+
+        if (normalized.equals("/."))
+            return "/";
+
+        // Add a leading "/" if necessary
+        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)
+                return (null);  // Trying to go outside our context
+            int index2 = normalized.lastIndexOf('/', index - 1);
+            normalized = normalized.substring(0, index2) +
+                normalized.substring(index + 3);
+        }
+
+        // Return the normalized path that we have completed
+        return (normalized);
+
+    }
+
+
+    /**
+     * Append request parameters from the specified String to the specified
+     * Map.  It is presumed that the specified Map is not accessed from any
+     * other thread, so no synchronization is performed.
+     * <p>
+     * <strong>IMPLEMENTATION NOTE</strong>:  URL decoding is performed
+     * individually on the parsed name and value elements, rather than on
+     * the entire query string ahead of time, to properly deal with the case
+     * where the name or value includes an encoded "=" or "&" character
+     * that would otherwise be interpreted as a delimiter.
+     *
+     * @param map Map that accumulates the resulting parameters
+     * @param data Input string containing request parameters
+     * @param encoding The encoding to use; if null, the default encoding is
+     * used. If an unsupported encoding is specified the parameters will not be
+     * parsed and the map will not be modified
+     */
+    public static void parseParameters(Map<String,String[]> map, String data,
+            String encoding) {
+
+        if ((data != null) && (data.length() > 0)) {
+
+            // use the specified encoding to extract bytes out of the
+            // given string so that the encoding is not lost. If an
+            // encoding is not specified, let it use platform default
+            byte[] bytes = null;
+            try {
+                if (encoding == null) {
+                    bytes = data.getBytes();
+                } else {
+                    bytes = data.getBytes(encoding);
+                }
+                parseParameters(map, bytes, encoding);
+            } catch (UnsupportedEncodingException uee) {
+                log.debug(sm.getString("requestUtil.parseParameters.uee",
+                        encoding), uee);
+            }
+
+        }
+
+    }
+
+
+    /**
+     * Decode and return the specified URL-encoded String.
+     * When the byte array is converted to a string, the system default
+     * character encoding is used...  This may be different than some other
+     * servers. It is assumed the string is not a query string.
+     *
+     * @param str The url-encoded string
+     *
+     * @exception IllegalArgumentException if a '%' character is not followed
+     * by a valid 2-digit hexadecimal number
+     */
+    public static String URLDecode(String str) {
+        return URLDecode(str, null);
+    }
+    
+    
+    /**
+     * Decode and return the specified URL-encoded String. It is assumed the
+     * string is not a query string.
+     *
+     * @param str The url-encoded string
+     * @param enc The encoding to use; if null, the default encoding is used. If
+     * an unsupported encoding is specified null will be returned
+     * @exception IllegalArgumentException if a '%' character is not followed
+     * by a valid 2-digit hexadecimal number
+     */
+    public static String URLDecode(String str, String enc) {
+        return URLDecode(str, enc, false);
+    }
+    
+    /**
+     * Decode and return the specified URL-encoded String.
+     *
+     * @param str The url-encoded string
+     * @param enc The encoding to use; if null, the default encoding is used. If
+     * an unsupported encoding is specified null will be returned
+     * @param isQuery Is this a query string being processed
+     * @exception IllegalArgumentException if a '%' character is not followed
+     * by a valid 2-digit hexadecimal number
+     */
+    public static String URLDecode(String str, String enc, boolean isQuery) {
+        if (str == null)
+            return (null);
+
+        // use the specified encoding to extract bytes out of the
+        // given string so that the encoding is not lost. If an
+        // encoding is not specified, let it use platform default
+        byte[] bytes = null;
+        try {
+            if (enc == null) {
+                bytes = str.getBytes();
+            } else {
+                bytes = str.getBytes(enc);
+            }
+        } catch (UnsupportedEncodingException uee) {
+            log.debug(sm.getString("requestUtil.urlDecode.uee", enc), uee);
+        }
+
+        return URLDecode(bytes, enc, isQuery);
+
+    }
+
+
+    /**
+     * Decode and return the specified URL-encoded byte array. It is assumed
+     * the string is not a query string.
+     *
+     * @param bytes The url-encoded byte array
+     * @exception IllegalArgumentException if a '%' character is not followed
+     * by a valid 2-digit hexadecimal number
+     */
+    public static String URLDecode(byte[] bytes) {
+        return URLDecode(bytes, null);
+    }
+
+
+    /**
+     * Decode and return the specified URL-encoded byte array. It is assumed
+     * the string is not a query string.
+     *
+     * @param bytes The url-encoded byte array
+     * @param enc The encoding to use; if null, the default encoding is used
+     * @exception IllegalArgumentException if a '%' character is not followed
+     * by a valid 2-digit hexadecimal number
+     */
+    public static String URLDecode(byte[] bytes, String enc) {
+        return URLDecode(bytes, enc, false);
+    }
+
+    /**
+     * Decode and return the specified URL-encoded byte array.
+     *
+     * @param bytes The url-encoded byte array
+     * @param enc The encoding to use; if null, the default encoding is used. If
+     * an unsupported encoding is specified null will be returned
+     * @param isQuery Is this a query string being processed
+     * @exception IllegalArgumentException if a '%' character is not followed
+     * by a valid 2-digit hexadecimal number
+     */
+    public static String URLDecode(byte[] bytes, String enc, boolean isQuery) {
+    
+        if (bytes == null)
+            return null;
+
+        int len = bytes.length;
+        int ix = 0;
+        int ox = 0;
+        while (ix < len) {
+            byte b = bytes[ix++];     // Get byte to test
+            if (b == '+' && isQuery) {
+                b = (byte)' ';
+            } else if (b == '%') {
+                if (ix + 2 > len) {
+                    throw new IllegalArgumentException(
+                            sm.getString("requestUtil.urlDecode.missingDigit"));
+                }
+                b = (byte) ((convertHexDigit(bytes[ix++]) << 4)
+                            + convertHexDigit(bytes[ix++]));
+            }
+            bytes[ox++] = b;
+        }
+        if (enc != null) {
+            try {
+                return new String(bytes, 0, ox, enc);
+            } catch (UnsupportedEncodingException uee) {
+                log.debug(sm.getString("requestUtil.urlDecode.uee", enc), uee);
+                return null;
+            }
+        }
+        return new String(bytes, 0, ox);
+
+    }
+
+
+    /**
+     * Convert a byte character value to hexadecimal digit value.
+     *
+     * @param b the character value byte
+     */
+    private static byte convertHexDigit( byte b ) {
+        if ((b >= '0') && (b <= '9')) return (byte)(b - '0');
+        if ((b >= 'a') && (b <= 'f')) return (byte)(b - 'a' + 10);
+        if ((b >= 'A') && (b <= 'F')) return (byte)(b - 'A' + 10);
+        throw new IllegalArgumentException(
+                sm.getString("requestUtil.convertHexDigit.notHex",
+                        Character.valueOf((char)b)));
+    }
+
+
+    /**
+     * Put name and value pair in map.  When name already exist, add value
+     * to array of values.
+     *
+     * @param map The map to populate
+     * @param name The parameter name
+     * @param value The parameter value
+     */
+    private static void putMapEntry( Map<String,String[]> map, String name,
+            String value) {
+        String[] newValues = null;
+        String[] oldValues = map.get(name);
+        if (oldValues == null) {
+            newValues = new String[1];
+            newValues[0] = value;
+        } else {
+            newValues = new String[oldValues.length + 1];
+            System.arraycopy(oldValues, 0, newValues, 0, oldValues.length);
+            newValues[oldValues.length] = value;
+        }
+        map.put(name, newValues);
+    }
+
+
+    /**
+     * Append request parameters from the specified String to the specified
+     * Map.  It is presumed that the specified Map is not accessed from any
+     * other thread, so no synchronization is performed.
+     * <p>
+     * <strong>IMPLEMENTATION NOTE</strong>:  URL decoding is performed
+     * individually on the parsed name and value elements, rather than on
+     * the entire query string ahead of time, to properly deal with the case
+     * where the name or value includes an encoded "=" or "&" character
+     * that would otherwise be interpreted as a delimiter.
+     *
+     * NOTE: byte array data is modified by this method.  Caller beware.
+     *
+     * @param map Map that accumulates the resulting parameters
+     * @param data Input string containing request parameters
+     * @param encoding The encoding to use; if null, the default encoding is
+     * used
+     *
+     * @exception UnsupportedEncodingException if the requested encoding is not
+     * supported.
+     */
+    public static void parseParameters(Map<String,String[]> map, byte[] data,
+            String encoding) throws UnsupportedEncodingException {
+
+        if (data != null && data.length > 0) {
+            int    ix = 0;
+            int    ox = 0;
+            String key = null;
+            String value = null;
+            while (ix < data.length) {
+                byte c = data[ix++];
+                switch ((char) c) {
+                case '&':
+                    value = new String(data, 0, ox, encoding);
+                    if (key != null) {
+                        putMapEntry(map, key, value);
+                        key = null;
+                    }
+                    ox = 0;
+                    break;
+                case '=':
+                    if (key == null) {
+                        key = new String(data, 0, ox, encoding);
+                        ox = 0;
+                    } else {
+                        data[ox++] = c;
+                    }                   
+                    break;  
+                case '+':
+                    data[ox++] = (byte)' ';
+                    break;
+                case '%':
+                    data[ox++] = (byte)((convertHexDigit(data[ix++]) << 4)
+                                    + convertHexDigit(data[ix++]));
+                    break;
+                default:
+                    data[ox++] = c;
+                }
+            }
+            //The last value does not end in '&'.  So save it now.
+            if (key != null) {
+                value = new String(data, 0, ox, encoding);
+                putMapEntry(map, key, value);
+            }
+        }
+
+    }
+
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/ResourceSet.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/ResourceSet.java
new file mode 100644
index 0000000..8134fad
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/ResourceSet.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.util;
+
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Extended implementation of <strong>HashSet</strong> that includes a
+ * <code>locked</code> property.  This class can be used to safely expose
+ * resource path sets to user classes without having to clone them in order
+ * to avoid modifications.  When first created, a <code>ResourceMap</code>
+ * is not locked.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: ResourceSet.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public final class ResourceSet<T> extends HashSet<T> {
+
+    private static final long serialVersionUID = 1L;
+
+    // ----------------------------------------------------------- Constructors
+    /**
+     * Construct a new, empty set with the default initial capacity and
+     * load factor.
+     */
+    public ResourceSet() {
+
+        super();
+
+    }
+
+
+    /**
+     * Construct a new, empty set with the specified initial capacity and
+     * default load factor.
+     *
+     * @param initialCapacity The initial capacity of this set
+     */
+    public ResourceSet(int initialCapacity) {
+
+        super(initialCapacity);
+
+    }
+
+
+    /**
+     * Construct a new, empty set with the specified initial capacity and
+     * load factor.
+     *
+     * @param initialCapacity The initial capacity of this set
+     * @param loadFactor The load factor of this set
+     */
+    public ResourceSet(int initialCapacity, float loadFactor) {
+
+        super(initialCapacity, loadFactor);
+
+    }
+
+
+    /**
+     * Construct a new set with the same contents as the existing collection.
+     *
+     * @param coll The collection whose contents we should copy
+     */
+    public ResourceSet(Collection<T> coll) {
+
+        super(coll);
+
+    }
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * The current lock state of this parameter map.
+     */
+    private boolean locked = false;
+
+
+    /**
+     * Return the locked state of this parameter map.
+     */
+    public boolean isLocked() {
+
+        return (this.locked);
+
+    }
+
+
+    /**
+     * Set the locked state of this parameter map.
+     *
+     * @param locked The new locked state
+     */
+    public void setLocked(boolean locked) {
+
+        this.locked = locked;
+
+    }
+
+
+    /**
+     * The string manager for this package.
+     */
+    private static final StringManager sm =
+        StringManager.getManager("org.apache.catalina.util");
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add the specified element to this set if it is not already present.
+     * Return <code>true</code> if the element was added.
+     *
+     * @param o The object to be added
+     *
+     * @exception IllegalStateException if this ResourceSet is locked
+     */
+    @Override
+    public boolean add(T o) {
+
+        if (locked)
+            throw new IllegalStateException
+              (sm.getString("resourceSet.locked"));
+        return (super.add(o));
+
+    }
+
+
+    /**
+     * Remove all of the elements from this set.
+     *
+     * @exception IllegalStateException if this ResourceSet is locked
+     */
+    @Override
+    public void clear() {
+
+        if (locked)
+            throw new IllegalStateException
+              (sm.getString("resourceSet.locked"));
+        super.clear();
+
+    }
+
+
+    /**
+     * Remove the given element from this set if it is present.
+     * Return <code>true</code> if the element was removed.
+     *
+     * @param o The object to be removed
+     *
+     * @exception IllegalStateException if this ResourceSet is locked
+     */
+    @Override
+    public boolean remove(Object o) {
+
+        if (locked)
+            throw new IllegalStateException
+              (sm.getString("resourceSet.locked"));
+        return (super.remove(o));
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/SchemaResolver.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/SchemaResolver.java
new file mode 100644
index 0000000..d178290
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/SchemaResolver.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.util;
+
+
+import java.util.HashMap;
+
+import org.apache.tomcat.util.digester.Digester;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * This class implements a local SAX's <code>EntityResolver</code>. All
+ * DTDs and schemas used to validate the web.xml file will re-directed
+ * to a local file stored in the servlet-api.jar and jsp-api.jar.
+ *
+ * @author Jean-Francois Arcand
+ */
+public class SchemaResolver implements EntityResolver {
+
+    /**
+     * The digester instance for which this class is the entity resolver.
+     */
+    protected Digester digester;
+
+
+    /**
+     * The URLs of dtds and schemas that have been registered, keyed by the
+     * public identifier that corresponds.
+     */
+    protected HashMap<String,String> entityValidator =
+        new HashMap<String,String>();
+
+
+    /**
+     * The public identifier of the DTD we are currently parsing under
+     * (if any).
+     */
+    protected String publicId = null;
+
+
+    /**
+     * Extension to make the difference between DTD and Schema.
+     */
+    protected String schemaExtension = "xsd";
+
+
+    /**
+     * Create a new <code>EntityResolver</code> that will redirect
+     * all remote dtds and schema to a local destination.
+     * @param digester The digester instance.
+     */
+    public SchemaResolver(Digester digester) {
+        this.digester = digester;
+    }
+
+
+    /**
+     * Register the specified DTD/Schema URL for the specified public
+     * identifier. This must be called before the first call to
+     * <code>parse()</code>.
+     *
+     * When adding a schema file (*.xsd), only the name of the file
+     * will get added. If two schemas with the same name are added,
+     * only the last one will be stored.
+     *
+     * @param publicId Public identifier of the DTD to be resolved
+     * @param entityURL The URL to use for reading this DTD
+     */
+     public void register(String publicId, String entityURL) {
+         String key = publicId;
+         if (publicId.indexOf(schemaExtension) != -1)
+             key = publicId.substring(publicId.lastIndexOf('/')+1);
+         entityValidator.put(key, entityURL);
+     }
+
+
+    /**
+     * Resolve the requested external entity.
+     *
+     * @param publicId The public identifier of the entity being referenced
+     * @param systemId The system identifier of the entity being referenced
+     *
+     * @exception SAXException if a parsing exception occurs
+     *
+     */
+    public InputSource resolveEntity(String publicId, String systemId)
+        throws SAXException {
+
+        if (publicId != null) {
+            this.publicId = publicId;
+            digester.setPublicId(publicId);
+        }
+
+        // Has this system identifier been registered?
+        String entityURL = null;
+        if (publicId != null) {
+            entityURL = entityValidator.get(publicId);
+        }
+
+        // Redirect the schema location to a local destination
+        String key = null;
+        if (entityURL == null && systemId != null) {
+            key = systemId.substring(systemId.lastIndexOf('/')+1);
+            entityURL = entityValidator.get(key);
+        }
+
+        if (entityURL == null) {
+           return (null);
+        }
+
+        try {
+            return (new InputSource(entityURL));
+        } catch (Exception e) {
+            throw new SAXException(e);
+        }
+
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/ServerInfo.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/ServerInfo.java
new file mode 100644
index 0000000..d9ee7f7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/ServerInfo.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.catalina.util;
+
+
+import java.io.InputStream;
+import java.util.Properties;
+
+import org.apache.tomcat.util.ExceptionUtils;
+
+
+/**
+ * Simple utility module to make it easy to plug in the server identifier
+ * when integrating Tomcat.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: ServerInfo.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public class ServerInfo {
+
+
+    // ------------------------------------------------------- Static Variables
+
+
+    /**
+     * The server information String with which we identify ourselves.
+     */
+    private static String serverInfo = null;
+
+    /**
+     * The server built String.
+     */
+    private static String serverBuilt = null;
+
+    /**
+     * The server's version number String.
+     */
+    private static String serverNumber = null;
+
+    static {
+
+        try {
+            InputStream is = ServerInfo.class.getResourceAsStream
+                ("/org/apache/catalina/util/ServerInfo.properties");
+            Properties props = new Properties();
+            props.load(is);
+            is.close();
+            serverInfo = props.getProperty("server.info");
+            serverBuilt = props.getProperty("server.built");
+            serverNumber = props.getProperty("server.number");
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+        }
+        if (serverInfo == null)
+            serverInfo = "Apache Tomcat 7.0.x-dev";
+        if (serverBuilt == null)
+            serverBuilt = "unknown";
+        if (serverNumber == null)
+            serverNumber = "7.0.x";
+        
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return the server identification for this version of Tomcat.
+     */
+    public static String getServerInfo() {
+
+        return (serverInfo);
+
+    }
+
+    /**
+     * Return the server built time for this version of Tomcat.
+     */
+    public static String getServerBuilt() {
+
+        return (serverBuilt);
+
+    }
+
+    /**
+     * Return the server's version number.
+     */
+    public static String getServerNumber() {
+
+        return (serverNumber);
+
+    }
+
+    public static void main(String args[]) {
+        System.out.println("Server version: " + getServerInfo());
+        System.out.println("Server built:   " + getServerBuilt());
+        System.out.println("Server number:  " + getServerNumber());
+        System.out.println("OS Name:        " +
+                           System.getProperty("os.name"));
+        System.out.println("OS Version:     " +
+                           System.getProperty("os.version"));
+        System.out.println("Architecture:   " +
+                           System.getProperty("os.arch"));
+        System.out.println("JVM Version:    " +
+                           System.getProperty("java.runtime.version"));
+        System.out.println("JVM Vendor:     " +
+                           System.getProperty("java.vm.vendor"));                        
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/ServerInfo.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/util/ServerInfo.properties
new file mode 100644
index 0000000..86b406c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/ServerInfo.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+server.info=Apache Tomcat/@VERSION@
+server.number=@VERSION_NUMBER@
+server.built=@VERSION_BUILT@
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/SessionIdGenerator.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/SessionIdGenerator.java
new file mode 100644
index 0000000..0e6b115
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/SessionIdGenerator.java
@@ -0,0 +1,254 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.util;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.res.StringManager;
+
+public class SessionIdGenerator {
+
+    private static Log log = LogFactory.getLog(SessionIdGenerator.class);
+
+
+    private static StringManager sm =
+        StringManager.getManager("org.apache.catalina.util");
+
+
+    /**
+     * Queue of random number generator objects to be used when creating session
+     * identifiers. If the queue is empty when a random number generator is
+     * required, a new random number generator object is created. This is
+     * designed this way since random number generators use a sync to make them
+     * thread-safe and the sync makes using a a single object slow(er).
+     */
+    private Queue<SecureRandom> randoms =
+        new ConcurrentLinkedQueue<SecureRandom>();
+
+
+    /**
+     * The Java class name of the secure random number generator class to be
+     * used when generating session identifiers. The random number generator
+     * class must be self-seeding and have a zero-argument constructor. If not
+     * specified, an instance of {@link SecureRandom} will be generated.
+     */
+    private String secureRandomClass = null;
+
+
+    /**
+     * The name of the algorithm to use to create instances of
+     * {@link SecureRandom} which are used to generate session IDs. If no
+     * algorithm is specified, SHA1PRNG is used. To use the platform default
+     * (which may be SHA1PRNG), specify the empty string. If an invalid
+     * algorithm and/or provider is specified the {@link SecureRandom} instances
+     * will be created using the defaults. If that fails, the {@link
+     * SecureRandom} instances will be created using platform defaults.
+     */
+    private String secureRandomAlgorithm = "SHA1PRNG";
+
+
+    /**
+     * The name of the provider to use to create instances of
+     * {@link SecureRandom} which are used to generate session IDs. If
+     * no algorithm is specified the of SHA1PRNG default is used. If an invalid
+     * algorithm and/or provider is specified the {@link SecureRandom} instances
+     * will be created using the defaults. If that fails, the {@link
+     * SecureRandom} instances will be created using platform defaults.
+     */
+    private String secureRandomProvider = null;
+
+
+    /** Node identifier when in a cluster. Defaults to the empty string. */
+    private String jvmRoute = "";
+
+
+    /** Number of bytes in a session ID. Defaults to 16. */
+    private int sessionIdLength = 16;
+
+
+    /**
+     * Specify a non-default @{link {@link SecureRandom} implementation to use.
+     * 
+     * @param secureRandomClass The fully-qualified class name
+     */
+    public void setSecureRandomClass(String secureRandomClass) {
+        this.secureRandomClass = secureRandomClass;
+    }
+
+
+    /**
+     * Specify a non-default algorithm to use to generate random numbers.
+     * 
+     * @param secureRandomAlgorithm The name of the algorithm
+     */
+    public void setSecureRandomAlgorithm(String secureRandomAlgorithm) {
+        this.secureRandomAlgorithm = secureRandomAlgorithm;
+    }
+
+
+    /**
+     * Specify a non-default provider to use to generate random numbers.
+     * 
+     * @param secureRandomProvider  The name of the provider
+     */
+    public void setSecureRandomProvider(String secureRandomProvider) {
+        this.secureRandomProvider = secureRandomProvider;
+    }
+
+
+    /**
+     * Specify the node identifier associated with this node which will be
+     * included in the generated session ID.
+     * 
+     * @param jvmRoute  The node identifier
+     */
+    public void setJvmRoute(String jvmRoute) {
+        this.jvmRoute = jvmRoute;
+    }
+
+
+    /**
+     * Specify the number of bytes for a session ID
+     * 
+     * @param sessionIdLength   Number of bytes
+     */
+    public void setSessionIdLength(int sessionIdLength) {
+        this.sessionIdLength = sessionIdLength;
+    }
+
+
+    /**
+     * Generate and return a new session identifier.
+     */
+    public String generateSessionId() {
+
+        byte random[] = new byte[16];
+
+        // Render the result as a String of hexadecimal digits
+        StringBuilder buffer = new StringBuilder();
+
+        int resultLenBytes = 0;
+
+        while (resultLenBytes < sessionIdLength) {
+            getRandomBytes(random);
+            for (int j = 0;
+            j < random.length && resultLenBytes < sessionIdLength;
+            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)));
+                resultLenBytes++;
+            }
+        }
+
+        if (jvmRoute != null && jvmRoute.length() > 0) {
+            buffer.append('.').append(jvmRoute);
+        }
+
+        return buffer.toString();
+    }
+    
+    
+    private void getRandomBytes(byte bytes[]) {
+
+        SecureRandom random = randoms.poll();
+        if (random == null) {
+            random = createSecureRandom();
+        }
+        random.nextBytes(bytes);
+        randoms.add(random);
+    }
+    
+    
+    /**
+     * Create a new random number generator instance we should use for
+     * generating session identifiers.
+     */
+    private SecureRandom createSecureRandom() {
+
+        SecureRandom result = null;
+        
+        long t1 = System.currentTimeMillis();
+        if (secureRandomClass != null) {
+            try {
+                // Construct and seed a new random number generator
+                Class<?> clazz = Class.forName(secureRandomClass);
+                result = (SecureRandom) clazz.newInstance();
+            } catch (Exception e) {
+                log.error(sm.getString("sessionIdGenerator.random",
+                        secureRandomClass), e);
+            }
+        }
+
+        if (result == null) {
+            // No secureRandomClass or creation failed. Use SecureRandom.
+            try {
+                if (secureRandomProvider != null &&
+                        secureRandomProvider.length() > 0) {
+                    result = SecureRandom.getInstance(secureRandomAlgorithm,
+                            secureRandomProvider);
+                } else if (secureRandomAlgorithm != null &&
+                        secureRandomAlgorithm.length() > 0) {
+                    result = SecureRandom.getInstance(secureRandomAlgorithm);
+                }
+            } catch (NoSuchAlgorithmException e) {
+                log.error(sm.getString("sessionIdGenerator.randomAlgorithm",
+                        secureRandomAlgorithm), e);
+            } catch (NoSuchProviderException e) {
+                log.error(sm.getString("sessionIdGenerator.randomProvider",
+                        secureRandomProvider), e);
+            }
+        }
+
+        if (result == null) {
+            // Invalid provider / algorithm
+            try {
+                result = SecureRandom.getInstance("SHA1PRNG");
+            } catch (NoSuchAlgorithmException e) {
+                log.error(sm.getString("sessionIdGenerator.randomAlgorithm",
+                        secureRandomAlgorithm), e);
+            }
+        }
+        
+        if (result == null) {
+            // Nothing works - use platform default
+            result = new SecureRandom();
+        }
+
+        // Force seeding to take place
+        result.nextInt();
+        
+        long t2=System.currentTimeMillis();
+        if( (t2-t1) > 100 )
+            log.info(sm.getString("sessionIdGenerator.createRandom",
+                    result.getAlgorithm(), Long.valueOf(t2-t1)));
+        return result;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/Strftime.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/Strftime.java
new file mode 100644
index 0000000..cfcceba
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/Strftime.java
@@ -0,0 +1,263 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.util;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.TimeZone;
+
+/**
+ * Converts dates to strings using the same format specifiers as strftime
+ *
+ * Note: This does not mimic strftime perfectly.  Certain strftime commands, 
+ *       are not supported, and will convert as if they were literals.
+ *
+ *       Certain complicated commands, like those dealing with the week of the year
+ *       probably don't have exactly the same behavior as strftime.
+ *
+ *       These limitations are due to use SimpleDateTime.  If the conversion was done
+ *       manually, all these limitations could be eliminated.
+ *
+ *       The interface looks like a subset of DateFormat.  Maybe someday someone will make this class
+ *       extend DateFormat.
+ *
+ * @author Bip Thelin
+ * @author Dan Sandberg
+ * @version $Id: Strftime.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+public class Strftime {
+    protected static Properties translate;
+    protected SimpleDateFormat simpleDateFormat;
+
+    /**
+     * Initialize our pattern translation
+     */
+    static {
+        translate = new Properties();
+        translate.put("a","EEE");
+        translate.put("A","EEEE");
+        translate.put("b","MMM");
+        translate.put("B","MMMM");
+        translate.put("c","EEE MMM d HH:mm:ss yyyy");
+
+        //There's no way to specify the century in SimpleDateFormat.  We don't want to hard-code
+        //20 since this could be wrong for the pre-2000 files.
+        //translate.put("C", "20");
+        translate.put("d","dd");
+        translate.put("D","MM/dd/yy");
+        translate.put("e","dd"); //will show as '03' instead of ' 3'
+        translate.put("F","yyyy-MM-dd");
+        translate.put("g","yy");
+        translate.put("G","yyyy");
+        translate.put("H","HH");
+        translate.put("h","MMM");
+        translate.put("I","hh");
+        translate.put("j","DDD");
+        translate.put("k","HH"); //will show as '07' instead of ' 7'
+        translate.put("l","hh"); //will show as '07' instead of ' 7'
+        translate.put("m","MM");
+        translate.put("M","mm");
+        translate.put("n","\n");
+        translate.put("p","a");
+        translate.put("P","a");  //will show as pm instead of PM
+        translate.put("r","hh:mm:ss a");
+        translate.put("R","HH:mm");
+        //There's no way to specify this with SimpleDateFormat
+        //translate.put("s","seconds since epoch");
+        translate.put("S","ss");
+        translate.put("t","\t");
+        translate.put("T","HH:mm:ss");
+        //There's no way to specify this with SimpleDateFormat
+        //translate.put("u","day of week ( 1-7 )");
+
+        //There's no way to specify this with SimpleDateFormat
+        //translate.put("U","week in year with first Sunday as first day...");
+
+        translate.put("V","ww"); //I'm not sure this is always exactly the same
+
+        //There's no way to specify this with SimpleDateFormat
+        //translate.put("W","week in year with first Monday as first day...");
+
+        //There's no way to specify this with SimpleDateFormat
+        //translate.put("w","E");
+        translate.put("X","HH:mm:ss");
+        translate.put("x","MM/dd/yy");
+        translate.put("y","yy");
+        translate.put("Y","yyyy");
+        translate.put("Z","z");
+        translate.put("z","Z");
+        translate.put("%","%");
+    }
+
+
+    /**
+     * Create an instance of this date formatting class
+     *
+     * @see #Strftime( String, Locale )
+     */
+    public Strftime( String origFormat ) {
+        String convertedFormat = convertDateFormat( origFormat );
+        simpleDateFormat = new SimpleDateFormat( convertedFormat );
+    }
+
+    /**
+     * Create an instance of this date formatting class
+     * 
+     * @param origFormat the strftime-style formatting string
+     * @param locale the locale to use for locale-specific conversions
+     */
+    public Strftime( String origFormat, Locale locale ) {
+        String convertedFormat = convertDateFormat( origFormat );
+        simpleDateFormat = new SimpleDateFormat( convertedFormat, locale );
+    }
+
+    /**
+     * Format the date according to the strftime-style string given in the constructor.
+     *
+     * @param date the date to format
+     * @return the formatted date
+     */
+    public String format( Date date ) {
+        return simpleDateFormat.format( date );
+    }
+
+    /**
+     * Get the timezone used for formatting conversions
+     *
+     * @return the timezone
+     */
+    public TimeZone getTimeZone() {
+        return simpleDateFormat.getTimeZone();
+    }
+
+    /**
+     * Change the timezone used to format dates
+     *
+     * @see SimpleDateFormat#setTimeZone
+     */
+    public void setTimeZone( TimeZone timeZone ) {
+        simpleDateFormat.setTimeZone( timeZone );
+    }
+
+    /**
+     * Search the provided pattern and get the C standard
+     * Date/Time formatting rules and convert them to the
+     * Java equivalent.
+     *
+     * @param pattern The pattern to search
+     * @return The modified pattern
+     */
+    protected String convertDateFormat( String pattern ) {
+        boolean inside = false;
+        boolean mark = false;
+        boolean modifiedCommand = false;
+
+        StringBuilder buf = new StringBuilder();
+
+        for(int i = 0; i < pattern.length(); i++) {
+            char c = pattern.charAt(i);
+
+            if ( c=='%' && !mark ) {
+                mark=true;
+            } else {
+                if ( mark ) {
+                    if ( modifiedCommand ) {
+                        //don't do anything--we just wanted to skip a char
+                        modifiedCommand = false;
+                        mark = false;
+                    } else {
+                        inside = translateCommand( buf, pattern, i, inside );
+                        //It's a modifier code
+                        if ( c=='O' || c=='E' ) {
+                            modifiedCommand = true;
+                        } else {
+                            mark=false;
+                        }
+                    }
+                } else {
+                    if ( !inside && c != ' ' ) {
+                        //We start a literal, which we need to quote
+                        buf.append("'");
+                        inside = true;
+                    }
+                    
+                    buf.append(c);
+                }
+            }
+        }
+
+        if ( buf.length() > 0 ) {
+            char lastChar = buf.charAt( buf.length() - 1 );
+
+            if( lastChar!='\'' && inside ) {
+                buf.append('\'');
+            }
+        }
+        return buf.toString();
+    }
+
+    protected String quote( String str, boolean insideQuotes ) {
+        String retVal = str;
+        if ( !insideQuotes ) {
+            retVal = '\'' + retVal + '\'';
+        }
+        return retVal;
+    }
+
+    /**
+     * Try to get the Java Date/Time formatting associated with
+     * the C standard provided.
+     *
+     * @param buf The buffer
+     * @param pattern The date/time pattern
+     * @param index The char index
+     * @param oldInside Flag value
+     * @return True if new is inside buffer
+     */
+    protected boolean translateCommand( StringBuilder buf, String pattern, int index, boolean oldInside ) {
+        char firstChar = pattern.charAt( index );
+        boolean newInside = oldInside;
+
+        //O and E are modifiers, they mean to present an alternative representation of the next char
+        //we just handle the next char as if the O or E wasn't there
+        if ( firstChar == 'O' || firstChar == 'E' ) {
+            if ( index + 1 < pattern.length() ) {               
+                newInside = translateCommand( buf, pattern, index + 1, oldInside );
+            } else {
+                buf.append( quote("%" + firstChar, oldInside ) );
+            }
+        } else {
+            String command = translate.getProperty( String.valueOf( firstChar ) );
+            
+            //If we don't find a format, treat it as a literal--That's what apache does
+            if ( command == null ) {
+                buf.append( quote( "%" + firstChar, oldInside ) );
+            } else {
+                //If we were inside quotes, close the quotes
+                if ( oldInside ) {
+                    buf.append( '\'' );
+                }
+                buf.append( command );
+                newInside = false;
+            }
+        }
+        return newInside;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/StringParser.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/StringParser.java
new file mode 100644
index 0000000..feaf2f1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/StringParser.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.util;
+
+
+/**
+ * Utility class for string parsing that is higher performance than
+ * StringParser for simple delimited text cases.  Parsing is performed
+ * by setting the string, and then using the <code>findXxxx()</code> and
+ * <code>skipXxxx()</code> families of methods to remember significant
+ * offsets.  To retrieve the parsed substrings, call the <code>extract()</code>
+ * method with the appropriate saved offset values.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: StringParser.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public final class StringParser {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a string parser with no preset string to be parsed.
+     */
+    public StringParser() {
+
+        this(null);
+
+    }
+
+
+    /**
+     * Construct a string parser that is initialized to parse the specified
+     * string.
+     *
+     * @param string The string to be parsed
+     */
+    public StringParser(String string) {
+
+        super();
+        setString(string);
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The characters of the current string, as a character array.  Stored
+     * when the string is first specified to speed up access to characters
+     * being compared during parsing.
+     */
+    private char chars[] = null;
+
+
+    /**
+     * The zero-relative index of the current point at which we are
+     * positioned within the string being parsed.  <strong>NOTE</strong>:
+     * the value of this index can be one larger than the index of the last
+     * character of the string (i.e. equal to the string length) if you
+     * parse off the end of the string.  This value is useful for extracting
+     * substrings that include the end of the string.
+     */
+    private int index = 0;
+
+
+    /**
+     * The length of the String we are currently parsing.  Stored when the
+     * string is first specified to avoid repeated recalculations.
+     */
+    private int length = 0;
+
+
+    /**
+     * The String we are currently parsing.
+     */
+    private String string = null;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the zero-relative index of our current parsing position
+     * within the string being parsed.
+     */
+    public int getIndex() {
+
+        return (this.index);
+
+    }
+
+
+    /**
+     * Return the length of the string we are parsing.
+     */
+    public int getLength() {
+
+        return (this.length);
+
+    }
+
+
+    /**
+     * Return the String we are currently parsing.
+     */
+    public String getString() {
+
+        return (this.string);
+
+    }
+
+
+    /**
+     * Set the String we are currently parsing.  The parser state is also reset
+     * to begin at the start of this string.
+     *
+     * @param string The string to be parsed.
+     */
+    public void setString(String string) {
+
+        this.string = string;
+        if (string != null) {
+            this.length = string.length();
+            chars = this.string.toCharArray();
+        } else {
+            this.length = 0;
+            chars = new char[0];
+        }
+        reset();
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Advance the current parsing position by one, if we are not already
+     * past the end of the string.
+     */
+    public void advance() {
+
+        if (index < length)
+            index++;
+
+    }
+
+
+    /**
+     * Extract and return a substring that starts at the specified position,
+     * and extends to the end of the string being parsed.  If this is not
+     * possible, a zero-length string is returned.
+     *
+     * @param start Starting index, zero relative, inclusive
+     */
+    public String extract(int start) {
+
+        if ((start < 0) || (start >= length))
+            return ("");
+        else
+            return (string.substring(start));
+
+    }
+
+
+    /**
+     * Extract and return a substring that starts at the specified position,
+     * and ends at the character before the specified position.  If this is
+     * not possible, a zero-length string is returned.
+     *
+     * @param start Starting index, zero relative, inclusive
+     * @param end Ending index, zero relative, exclusive
+     */
+    public String extract(int start, int end) {
+
+        if ((start < 0) || (start >= end) || (end > length))
+            return ("");
+        else
+            return (string.substring(start, end));
+
+    }
+
+
+    /**
+     * Return the index of the next occurrence of the specified character,
+     * or the index of the character after the last position of the string
+     * if no more occurrences of this character are found.  The current
+     * parsing position is updated to the returned value.
+     *
+     * @param ch Character to be found
+     */
+    public int findChar(char ch) {
+
+        while ((index < length) && (ch != chars[index]))
+            index++;
+        return (index);
+
+    }
+
+
+    /**
+     * Return the index of the next occurrence of a non-whitespace character,
+     * or the index of the character after the last position of the string
+     * if no more non-whitespace characters are found.  The current
+     * parsing position is updated to the returned value.
+     */
+    public int findText() {
+
+        while ((index < length) && isWhite(chars[index]))
+            index++;
+        return (index);
+
+    }
+
+
+    /**
+     * Return the index of the next occurrence of a whitespace character,
+     * or the index of the character after the last position of the string
+     * if no more whitespace characters are found.  The current parsing
+     * position is updated to the returned value.
+     */
+    public int findWhite() {
+
+        while ((index < length) && !isWhite(chars[index]))
+            index++;
+        return (index);
+
+    }
+
+
+    /**
+     * Reset the current state of the parser to the beginning of the
+     * current string being parsed.
+     */
+    public void reset() {
+
+        index = 0;
+
+    }
+
+
+    /**
+     * Advance the current parsing position while it is pointing at the
+     * specified character, or until it moves past the end of the string.
+     * Return the final value.
+     *
+     * @param ch Character to be skipped
+     */
+    public int skipChar(char ch) {
+
+        while ((index < length) && (ch == chars[index]))
+            index++;
+        return (index);
+
+    }
+
+
+    /**
+     * Advance the current parsing position while it is pointing at a
+     * non-whitespace character, or until it moves past the end of the string.
+     * Return the final value.
+     */
+    public int skipText() {
+
+        while ((index < length) && !isWhite(chars[index]))
+            index++;
+        return (index);
+
+    }
+
+
+    /**
+     * Advance the current parsing position while it is pointing at a
+     * whitespace character, or until it moves past the end of the string.
+     * Return the final value.
+     */
+    public int skipWhite() {
+
+        while ((index < length) && isWhite(chars[index]))
+            index++;
+        return (index);
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Is the specified character considered to be whitespace?
+     *
+     * @param ch Character to be checked
+     */
+    protected boolean isWhite(char ch) {
+
+        if ((ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n'))
+            return (true);
+        else
+            return (false);
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/TomcatCSS.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/TomcatCSS.java
new file mode 100644
index 0000000..354878e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/TomcatCSS.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.catalina.util;
+
+
+public class TomcatCSS {
+
+    public static final String TOMCAT_CSS =
+        "H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} " +
+        "H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} " +
+        "H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} " +
+        "BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} " +
+        "B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} " +
+        "P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}" +
+        "A {color : black;}" +
+        "A.name {color : black;}" +
+        "HR {color : #525D76;}";
+
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/URLEncoder.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/URLEncoder.java
new file mode 100644
index 0000000..2a9ad43
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/URLEncoder.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.BitSet;
+
+/**
+ *
+ * This class is very similar to the java.net.URLEncoder class.
+ *
+ * Unfortunately, with java.net.URLEncoder there is no way to specify to the 
+ * java.net.URLEncoder which characters should NOT be encoded.
+ *
+ * This code was moved from DefaultServlet.java
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ */
+public class URLEncoder {
+    protected static final char[] hexadecimal =
+    {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+     'A', 'B', 'C', 'D', 'E', 'F'};
+
+    //Array containing the safe characters set.
+    protected BitSet safeCharacters = new BitSet(256);
+
+    public URLEncoder() {
+        for (char i = 'a'; i <= 'z'; i++) {
+            addSafeCharacter(i);
+        }
+        for (char i = 'A'; i <= 'Z'; i++) {
+            addSafeCharacter(i);
+        }
+        for (char i = '0'; i <= '9'; i++) {
+            addSafeCharacter(i);
+        }
+    }
+
+    public void addSafeCharacter( char c ) {
+        safeCharacters.set( c );
+    }
+
+    public String encode( String path ) {
+        int maxBytesPerChar = 10;
+        StringBuilder rewrittenPath = new StringBuilder(path.length());
+        ByteArrayOutputStream buf = new ByteArrayOutputStream(maxBytesPerChar);
+        OutputStreamWriter writer = null;
+        try {
+            writer = new OutputStreamWriter(buf, "UTF8");
+        } catch (Exception e) {
+            e.printStackTrace();
+            writer = new OutputStreamWriter(buf);
+        }
+
+        for (int i = 0; i < path.length(); i++) {
+            int c = path.charAt(i);
+            if (safeCharacters.get(c)) {
+                rewrittenPath.append((char)c);
+            } else {
+                // convert to external encoding before hex conversion
+                try {
+                    writer.write((char)c);
+                    writer.flush();
+                } catch(IOException e) {
+                    buf.reset();
+                    continue;
+                }
+                byte[] ba = buf.toByteArray();
+                for (int j = 0; j < ba.length; j++) {
+                    // Converting each byte in the buffer
+                    byte toEncode = ba[j];
+                    rewrittenPath.append('%');
+                    int low = toEncode & 0x0f;
+                    int high = (toEncode & 0xf0) >> 4;
+                    rewrittenPath.append(hexadecimal[high]);
+                    rewrittenPath.append(hexadecimal[low]);
+                }
+                buf.reset();
+            }
+        }
+        return rewrittenPath.toString();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/util/XMLWriter.java b/bundles/org.apache.tomcat/src/org/apache/catalina/util/XMLWriter.java
new file mode 100644
index 0000000..2a0f415
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/util/XMLWriter.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.util;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * XMLWriter helper class.
+ *
+ * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
+ */
+public class XMLWriter {
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    /**
+     * Opening tag.
+     */
+    public static final int OPENING = 0;
+
+
+    /**
+     * Closing tag.
+     */
+    public static final int CLOSING = 1;
+
+
+    /**
+     * Element with no content.
+     */
+    public static final int NO_CONTENT = 2;
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Buffer.
+     */
+    protected StringBuilder buffer = new StringBuilder();
+
+
+    /**
+     * Writer.
+     */
+    protected Writer writer = null;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Constructor.
+     */
+    public XMLWriter() {
+    }
+
+
+    /**
+     * Constructor.
+     */
+    public XMLWriter(Writer writer) {
+        this.writer = writer;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Retrieve generated XML.
+     *
+     * @return String containing the generated XML
+     */
+    @Override
+    public String toString() {
+        return buffer.toString();
+    }
+
+
+    /**
+     * Write property to the XML.
+     *
+     * @param namespace Namespace
+     * @param namespaceInfo Namespace info
+     * @param name Property name
+     * @param value Property value
+     */
+    public void writeProperty(String namespace, String namespaceInfo,
+                              String name, String value) {
+        writeElement(namespace, namespaceInfo, name, OPENING);
+        buffer.append(value);
+        writeElement(namespace, namespaceInfo, name, CLOSING);
+
+    }
+
+
+    /**
+     * Write property to the XML.
+     *
+     * @param namespace Namespace
+     * @param name Property name
+     * @param value Property value
+     */
+    public void writeProperty(String namespace, String name, String value) {
+        writeElement(namespace, name, OPENING);
+        buffer.append(value);
+        writeElement(namespace, name, CLOSING);
+    }
+
+
+    /**
+     * Write property to the XML.
+     *
+     * @param namespace Namespace
+     * @param name Property name
+     */
+    public void writeProperty(String namespace, String name) {
+        writeElement(namespace, name, NO_CONTENT);
+    }
+
+
+    /**
+     * Write an element.
+     *
+     * @param name Element name
+     * @param namespace Namespace abbreviation
+     * @param type Element type
+     */
+    public void writeElement(String namespace, String name, int type) {
+        writeElement(namespace, null, name, type);
+    }
+
+
+    /**
+     * Write an element.
+     *
+     * @param namespace Namespace abbreviation
+     * @param namespaceInfo Namespace info
+     * @param name Element name
+     * @param type Element type
+     */
+    public void writeElement(String namespace, String namespaceInfo,
+                             String name, int type) {
+        if ((namespace != null) && (namespace.length() > 0)) {
+            switch (type) {
+            case OPENING:
+                if (namespaceInfo != null) {
+                    buffer.append("<" + namespace + ":" + name + " xmlns:"
+                                  + namespace + "=\""
+                                  + namespaceInfo + "\">");
+                } else {
+                    buffer.append("<" + namespace + ":" + name + ">");
+                }
+                break;
+            case CLOSING:
+                buffer.append("</" + namespace + ":" + name + ">\n");
+                break;
+            case NO_CONTENT:
+            default:
+                if (namespaceInfo != null) {
+                    buffer.append("<" + namespace + ":" + name + " xmlns:"
+                                  + namespace + "=\""
+                                  + namespaceInfo + "\"/>");
+                } else {
+                    buffer.append("<" + namespace + ":" + name + "/>");
+                }
+                break;
+            }
+        } else {
+            switch (type) {
+            case OPENING:
+                buffer.append("<" + name + ">");
+                break;
+            case CLOSING:
+                buffer.append("</" + name + ">\n");
+                break;
+            case NO_CONTENT:
+            default:
+                buffer.append("<" + name + "/>");
+                break;
+            }
+        }
+    }
+
+
+    /**
+     * Write text.
+     *
+     * @param text Text to append
+     */
+    public void writeText(String text) {
+        buffer.append(text);
+    }
+
+
+    /**
+     * Write data.
+     *
+     * @param data Data to append
+     */
+    public void writeData(String data) {
+        buffer.append("<![CDATA[" + data + "]]>");
+    }
+
+
+    /**
+     * Write XML Header.
+     */
+    public void writeXMLHeader() {
+        buffer.append("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
+    }
+
+
+    /**
+     * Send data and reinitializes buffer.
+     */
+    public void sendData()
+        throws IOException {
+        if (writer != null) {
+            writer.write(buffer.toString());
+            buffer = new StringBuilder();
+        }
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/valves/AccessLogValve.java b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/AccessLogValve.java
new file mode 100644
index 0000000..9a006bb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/AccessLogValve.java
@@ -0,0 +1,1491 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.valves;
+
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TimeZone;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpSession;
+
+import org.apache.catalina.AccessLog;
+import org.apache.catalina.Globals;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.coyote.RequestInfo;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+
+
+/**
+ * <p>Implementation of the <b>Valve</b> interface that generates a web server
+ * access log with the detailed line contents matching a configurable pattern.
+ * The syntax of the available patterns is similar to that supported by the
+ * Apache <code>mod_log_config</code> module.  As an additional feature,
+ * automatic rollover of log files when the date changes is also supported.</p>
+ *
+ * <p>Patterns for the logged message may include constant text or any of the
+ * following replacement strings, for which the corresponding information
+ * from the specified Response is substituted:</p>
+ * <ul>
+ * <li><b>%a</b> - Remote IP address
+ * <li><b>%A</b> - Local IP address
+ * <li><b>%b</b> - Bytes sent, excluding HTTP headers, or '-' if no bytes
+ *     were sent
+ * <li><b>%B</b> - Bytes sent, excluding HTTP headers
+ * <li><b>%h</b> - Remote host name
+ * <li><b>%H</b> - Request protocol
+ * <li><b>%l</b> - Remote logical username from identd (always returns '-')
+ * <li><b>%m</b> - Request method
+ * <li><b>%p</b> - Local port
+ * <li><b>%q</b> - Query string (prepended with a '?' if it exists, otherwise
+ *     an empty string
+ * <li><b>%r</b> - First line of the request
+ * <li><b>%s</b> - HTTP status code of the response
+ * <li><b>%S</b> - User session ID
+ * <li><b>%t</b> - Date and time, in Common Log Format format
+ * <li><b>%u</b> - Remote user that was authenticated
+ * <li><b>%U</b> - Requested URL path
+ * <li><b>%v</b> - Local server name
+ * <li><b>%D</b> - Time taken to process the request, in millis
+ * <li><b>%T</b> - Time taken to process the request, in seconds
+ * <li><b>%I</b> - current Request thread name (can compare later with stacktraces)
+ * </ul>
+ * <p>In addition, the caller can specify one of the following aliases for
+ * commonly utilized patterns:</p>
+ * <ul>
+ * <li><b>common</b> - <code>%h %l %u %t "%r" %s %b</code>
+ * <li><b>combined</b> -
+ *   <code>%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-Agent}i"</code>
+ * </ul>
+ *
+ * <p>
+ * There is also support to write information from the cookie, incoming
+ * header, the Session or something else in the ServletRequest.<br>
+ * It is modeled after the apache syntax:
+ * <ul>
+ * <li><code>%{xxx}i</code> for incoming headers
+ * <li><code>%{xxx}o</code> for outgoing response headers
+ * <li><code>%{xxx}c</code> for a specific cookie
+ * <li><code>%{xxx}r</code> xxx is an attribute in the ServletRequest
+ * <li><code>%{xxx}s</code> xxx is an attribute in the HttpSession
+ * </ul>
+ * </p>
+ *
+ * <p>
+ * Conditional logging is also supported. This can be done with the
+ * <code>condition</code> property.
+ * If the value returned from ServletRequest.getAttribute(condition)
+ * yields a non-null value. The logging will be skipped.
+ * </p>
+ *
+ * @author Craig R. McClanahan
+ * @author Jason Brittain
+ * @author Remy Maucherat
+ * @author Takayuki Kaneko
+ * @author Peter Rossbach
+ * 
+ * @version $Id: AccessLogValve.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+
+public class AccessLogValve extends ValveBase implements AccessLog {
+
+    private static final Log log = LogFactory.getLog(AccessLogValve.class);
+
+    //------------------------------------------------------ Constructor
+    public AccessLogValve() {
+        super(true);
+    }
+
+    // ----------------------------------------------------- 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 dateStamp = "";
+
+
+    /**
+     * The directory in which log files are created.
+     */
+    private String directory = "logs";
+
+
+    /**
+     * The descriptive information about this implementation.
+     */
+    protected static final String info =
+        "org.apache.catalina.valves.AccessLogValve/2.1";
+
+
+    /**
+     * The set of month abbreviations for log messages.
+     */
+    protected static final String months[] =
+    { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+
+
+    /**
+     * enabled this component
+     */
+    protected boolean enabled = true;
+
+    /**
+     * The pattern used to format our access log lines.
+     */
+    protected String pattern = null;
+
+
+    /**
+     * The prefix that is added to log file filenames.
+     */
+    protected String prefix = "access_log.";
+
+
+    /**
+     * Should we rotate our log file? Default is true (like old behavior)
+     */
+    protected boolean rotatable = true;
+
+
+    /**
+     * Buffered logging.
+     */
+    private boolean buffered = true;
+
+
+    /**
+     * The suffix that is added to log file filenames.
+     */
+    protected String suffix = "";
+
+
+    /**
+     * The PrintWriter to which we are currently logging, if any.
+     */
+    protected PrintWriter writer = null;
+
+
+    /**
+     * A date formatter to format a Date into a date in the format
+     * "yyyy-MM-dd".
+     */
+    protected SimpleDateFormat fileDateFormatter = null;
+
+
+    /**
+     * The system timezone.
+     */
+    private volatile TimeZone timezone = null;
+
+    
+    /**
+     * The time zone offset relative to GMT in text form when daylight saving
+     * is not in operation.
+     */
+    private volatile String timeZoneNoDST = null;
+
+
+    /**
+     * The time zone offset relative to GMT in text form when daylight saving
+     * is in operation.
+     */
+    private volatile String timeZoneDST = null;
+    
+    
+    /**
+     * The current log file we are writing to. Helpful when checkExists
+     * is true.
+     */
+    protected File currentLogFile = null;
+    private static class AccessDateStruct {
+        private Date currentDate = new Date();
+        private String currentDateString = null;
+        private SimpleDateFormat dayFormatter = new SimpleDateFormat("dd");
+        private SimpleDateFormat monthFormatter = new SimpleDateFormat("MM");
+        private SimpleDateFormat yearFormatter = new SimpleDateFormat("yyyy");
+        private SimpleDateFormat timeFormatter = new SimpleDateFormat("HH:mm:ss");
+        public AccessDateStruct() {
+            TimeZone tz = TimeZone.getDefault();
+            dayFormatter.setTimeZone(tz);
+            monthFormatter.setTimeZone(tz);
+            yearFormatter.setTimeZone(tz);
+            timeFormatter.setTimeZone(tz);
+        }
+    }
+    
+    /**
+     * The system time when we last updated the Date that this valve
+     * uses for log lines.
+     */
+    private static final ThreadLocal<AccessDateStruct> currentDateStruct =
+            new ThreadLocal<AccessDateStruct>() {
+        @Override
+        protected AccessDateStruct initialValue() {
+            return new AccessDateStruct();
+        }
+    };
+    /**
+     * Resolve hosts.
+     */
+    private boolean resolveHosts = false;
+
+
+    /**
+     * Instant when the log daily rotation was last checked.
+     */
+    private volatile long rotationLastChecked = 0L;
+
+    /**
+     * Do we check for log file existence? Helpful if an external
+     * agent renames the log file so we can automagically recreate it.
+     */
+    private boolean checkExists = false;
+    
+    
+    /**
+     * Are we doing conditional logging. default false.
+     */
+    protected String condition = null;
+
+
+    /**
+     * Date format to place in log file name. Use at your own risk!
+     */
+    protected String fileDateFormat = null;
+    
+    /**
+     * Array of AccessLogElement, they will be used to make log message.
+     */
+    protected AccessLogElement[] logElements = null;
+
+    /**
+     * @see #setRequestAttributesEnabled(boolean)
+     */
+    protected boolean requestAttributesEnabled = false;
+
+    // ------------------------------------------------------------- Properties
+
+    /**
+     * @return Returns the enabled.
+     */
+    public boolean getEnabled() {
+        return enabled;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setRequestAttributesEnabled(boolean requestAttributesEnabled) {
+        this.requestAttributesEnabled = requestAttributesEnabled;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean getRequestAttributesEnabled() {
+        return requestAttributesEnabled;
+    }
+
+    /**
+     * @param enabled
+     *            The enabled to set.
+     */
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    /**
+     * Return the directory in which we create log files.
+     */
+    public String getDirectory() {
+        return (directory);
+    }
+
+
+    /**
+     * Set the directory in which we create log files.
+     *
+     * @param directory The new log file directory
+     */
+    public void setDirectory(String directory) {
+        this.directory = directory;
+    }
+
+
+    /**
+     * Return descriptive information about this implementation.
+     */
+    @Override
+    public String getInfo() {
+        return (info);
+    }
+
+
+    /**
+     * Return the format pattern.
+     */
+    public String getPattern() {
+        return (this.pattern);
+    }
+
+
+    /**
+     * Set the format pattern, first translating any recognized alias.
+     *
+     * @param pattern The new pattern
+     */
+    public void setPattern(String pattern) {
+        if (pattern == null)
+            this.pattern = "";
+        else if (pattern.equals(Constants.AccessLog.COMMON_ALIAS))
+            this.pattern = Constants.AccessLog.COMMON_PATTERN;
+        else if (pattern.equals(Constants.AccessLog.COMBINED_ALIAS))
+            this.pattern = Constants.AccessLog.COMBINED_PATTERN;
+        else
+            this.pattern = pattern;
+        logElements = createLogElements();
+    }
+
+
+    /**
+     * Check for file existence before logging.
+     */
+    public boolean isCheckExists() {
+
+        return checkExists;
+
+    }
+
+
+    /**
+     * Set whether to check for log file existence before logging.
+     *
+     * @param checkExists true meaning to check for file existence.
+     */
+    public void setCheckExists(boolean checkExists) {
+
+        this.checkExists = checkExists;
+
+    }
+    
+    
+    /**
+     * Return the log file prefix.
+     */
+    public String getPrefix() {
+        return (prefix);
+    }
+
+
+    /**
+     * Set the log file prefix.
+     *
+     * @param prefix The new log file prefix
+     */
+    public void setPrefix(String prefix) {
+        this.prefix = prefix;
+    }
+
+
+    /**
+     * Should we rotate the logs
+     */
+    public boolean isRotatable() {
+        return rotatable;
+    }
+
+
+    /**
+     * Set the value is we should we rotate the logs
+     *
+     * @param rotatable true is we should rotate.
+     */
+    public void setRotatable(boolean rotatable) {
+        this.rotatable = rotatable;
+    }
+
+
+    /**
+     * Is the logging buffered
+     */
+    public boolean isBuffered() {
+        return buffered;
+    }
+
+
+    /**
+     * Set the value if the logging should be buffered
+     *
+     * @param buffered true if buffered.
+     */
+    public void setBuffered(boolean buffered) {
+        this.buffered = buffered;
+    }
+
+
+    /**
+     * Return the log file suffix.
+     */
+    public String getSuffix() {
+        return (suffix);
+    }
+
+
+    /**
+     * Set the log file suffix.
+     *
+     * @param suffix The new log file suffix
+     */
+    public void setSuffix(String suffix) {
+        this.suffix = suffix;
+    }
+
+
+    /**
+     * Set the resolve hosts flag.
+     *
+     * @param resolveHosts The new resolve hosts value
+     */
+    public void setResolveHosts(boolean resolveHosts) {
+        this.resolveHosts = resolveHosts;
+    }
+
+
+    /**
+     * Get the value of the resolve hosts flag.
+     */
+    public boolean isResolveHosts() {
+        return resolveHosts;
+    }
+
+
+    /**
+     * Return whether the attribute name to look for when
+     * performing conditional logging. If null, every
+     * request is logged.
+     */
+    public String getCondition() {
+        return condition;
+    }
+
+
+    /**
+     * Set the ServletRequest.attribute to look for to perform
+     * conditional logging. Set to null to log everything.
+     *
+     * @param condition Set to null to log everything
+     */
+    public void setCondition(String condition) {
+        this.condition = condition;
+    }
+
+    /**
+     *  Return the date format date based log rotation.
+     */
+    public String getFileDateFormat() {
+        return fileDateFormat;
+    }
+
+
+    /**
+     *  Set the date format date based log rotation.
+     */
+    public void setFileDateFormat(String fileDateFormat) {
+        this.fileDateFormat =  fileDateFormat;
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * 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 synchronized void backgroundProcess() {
+        if (getState().isAvailable() && getEnabled() && writer != null &&
+                buffered) {
+            writer.flush();
+        }
+    }    
+
+    /**
+     * Log a message summarizing the specified request and response, according
+     * to the format specified by the <code>pattern</code> property.
+     *
+     * @param request Request being processed
+     * @param response Response being processed
+     *
+     * @exception IOException if an input/output error has occurred
+     * @exception ServletException if a servlet error has occurred
+     */
+    @Override
+    public void invoke(Request request, Response response) throws IOException,
+            ServletException {
+        getNext().invoke(request, response);       
+    }
+
+    
+    @Override
+    public void log(Request request, Response response, long time) {
+        if (!getState().isAvailable() || !getEnabled() ||
+                logElements == null || condition != null
+                && null != request.getRequest().getAttribute(condition)) {
+            return;
+        }
+
+        Date date = getDate();
+        StringBuilder result = new StringBuilder(128);
+
+        for (int i = 0; i < logElements.length; i++) {
+            logElements[i].addElement(result, date, request, response, time);
+        }
+
+        log(result.toString());
+    }
+
+
+    /**
+     * Rename the existing log file to something else. Then open the
+     * old log file name up once again. Intended to be called by a JMX
+     * agent.
+     *
+     *
+     * @param newFileName The file name to move the log file entry to
+     * @return true if a file was rotated with no error
+     */
+    public synchronized boolean rotate(String newFileName) {
+
+        if (currentLogFile != null) {
+            File holder = currentLogFile;
+            close();
+            try {
+                holder.renameTo(new File(newFileName));
+            } catch (Throwable e) {
+                ExceptionUtils.handleThrowable(e);
+                log.error(sm.getString("accessLogValve.rotateFail"), e);
+            }
+
+            /* Make sure date is correct */
+            dateStamp = fileDateFormatter.format(
+                    new Date(System.currentTimeMillis()));
+
+            open();
+            return true;
+        } else {
+            return false;
+        }
+
+    }
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Close the currently open log file (if any)
+     */
+    private synchronized void close() {
+        if (writer == null) {
+            return;
+        }
+        writer.flush();
+        writer.close();
+        writer = null;
+        dateStamp = "";
+        currentLogFile = null;
+    }
+
+
+    /**
+     * Log the specified message to the log file, switching files if the date
+     * has changed since the previous log call.
+     *
+     * @param message Message to be logged
+     */
+    public void log(String message) {
+        if (rotatable) {
+            // Only do a logfile switch check once a second, max.
+            long systime = System.currentTimeMillis();
+            if ((systime - rotationLastChecked) > 1000) {
+                synchronized(this) {
+                    if ((systime - rotationLastChecked) > 1000) {
+                        rotationLastChecked = systime;
+    
+                        String tsDate;
+                        // Check for a change of date
+                        tsDate = fileDateFormatter.format(new Date(systime));
+    
+                        // If the date has changed, switch log files
+                        if (!dateStamp.equals(tsDate)) {
+                            close();
+                            dateStamp = tsDate;
+                            open();
+                        }
+                    }
+                }
+            }
+        }
+        
+        /* In case something external rotated the file instead */
+        if (checkExists) {
+            synchronized (this) {
+                if (currentLogFile != null && !currentLogFile.exists()) {
+                    try {
+                        close();
+                    } catch (Throwable e) {
+                        ExceptionUtils.handleThrowable(e);
+                        log.info(sm.getString("accessLogValve.closeFail"), e);
+                    }
+
+                    /* Make sure date is correct */
+                    dateStamp = fileDateFormatter.format(
+                            new Date(System.currentTimeMillis()));
+
+                    open();
+                }
+            }
+        }
+
+        // Log this message
+        synchronized(this) {
+            if (writer != null) {
+                writer.println(message);
+                if (!buffered) {
+                    writer.flush();
+                }
+            }
+        }
+
+    }
+
+
+    /**
+     * 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 (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            index = 0;  // Can not happen, in theory
+        }
+        return (months[index]);
+    }
+
+
+    /**
+     * Open the new log file for the date specified by <code>dateStamp</code>.
+     */
+    protected synchronized void open() {
+        // Create the directory if necessary
+        File dir = new File(directory);
+        if (!dir.isAbsolute())
+            dir = new File(System.getProperty(Globals.CATALINA_BASE_PROP), directory);
+        if (!dir.exists()) {
+            if (!dir.mkdirs()) {
+                log.error(sm.getString("accessLogValve.openDirFail", dir));
+            }
+        }
+
+        // Open the current log file
+        try {
+            String pathname;
+            // If no rotate - no need for dateStamp in fileName
+            if (rotatable) {
+                pathname = dir.getAbsolutePath() + File.separator + prefix
+                        + dateStamp + suffix;
+            } else {
+                pathname = dir.getAbsolutePath() + File.separator + prefix
+                        + suffix;
+            }
+            writer = new PrintWriter(new BufferedWriter(new FileWriter(
+                    pathname, true), 128000), false);
+            
+            currentLogFile = new File(pathname);
+        } catch (IOException e) {
+            writer = null;
+            currentLogFile = null;
+        }
+    }
+ 
+    /**
+     * This method returns a Date object that is accurate to within one second.
+     * If a thread calls this method to get a Date and it's been less than 1
+     * second since a new Date was created, this method simply gives out the
+     * same Date again so that the system doesn't spend time creating Date
+     * objects unnecessarily.
+     * 
+     * @return Date
+     */
+    private Date getDate() {
+        // Only create a new Date once per second, max.
+        long systime = System.currentTimeMillis();
+        AccessDateStruct struct = currentDateStruct.get(); 
+        if ((systime - struct.currentDate.getTime()) > 1000) {
+            struct.currentDate.setTime(systime);
+            struct.currentDateString = null;
+        }
+        return struct.currentDate;
+    }
+
+
+    private String getTimeZone(Date date) {
+        if (timezone.inDaylightTime(date)) {
+            return timeZoneDST;
+        } else {
+            return timeZoneNoDST;
+        }
+    }
+    
+    
+    private String calculateTimeZoneOffset(long offset) {
+        StringBuilder tz = new StringBuilder();
+        if ((offset < 0)) {
+            tz.append("-");
+            offset = -offset;
+        } else {
+            tz.append("+");
+        }
+
+        long hourOffset = offset / (1000 * 60 * 60);
+        long minuteOffset = (offset / (1000 * 60)) % 60;
+
+        if (hourOffset < 10)
+            tz.append("0");
+        tz.append(hourOffset);
+
+        if (minuteOffset < 10)
+            tz.append("0");
+        tz.append(minuteOffset);
+
+        return tz.toString();
+    }
+
+
+    /**
+     * 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 {
+
+        // Initialize the timeZone, Date formatters, and currentDate
+        TimeZone tz = TimeZone.getDefault();
+        timezone = tz;
+        timeZoneNoDST = calculateTimeZoneOffset(tz.getRawOffset());
+        int offset = tz.getDSTSavings();
+        timeZoneDST = calculateTimeZoneOffset(tz.getRawOffset() + offset);
+
+        String format = getFileDateFormat();
+        if (format == null || format.length() == 0) {
+            format = "yyyy-MM-dd";
+            setFileDateFormat(format);
+        }
+        fileDateFormatter = new SimpleDateFormat(format);
+        fileDateFormatter.setTimeZone(tz);
+        dateStamp = fileDateFormatter.format(currentDateStruct.get().currentDate);
+        open();
+        
+        setState(LifecycleState.STARTING);
+    }
+
+
+    /**
+     * 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 {
+        
+        setState(LifecycleState.STOPPING);
+        close();
+    }
+    
+    /**
+     * AccessLogElement writes the partial message into the buffer.
+     */
+    protected interface AccessLogElement {
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time);
+
+    }
+    
+    /**
+     * write thread name - %I
+     */
+    protected static class ThreadNameElement implements AccessLogElement {
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            RequestInfo info = request.getCoyoteRequest().getRequestProcessor();
+            if(info != null) {
+                buf.append(info.getWorkerThreadName());
+            } else {
+                buf.append("-");
+            }
+        }
+    }
+    
+    /**
+     * write local IP address - %A
+     */
+    protected static class LocalAddrElement implements AccessLogElement {
+        
+        private static final String LOCAL_ADDR_VALUE;
+
+        static {
+            String init;
+            try {
+                init = InetAddress.getLocalHost().getHostAddress();
+            } catch (Throwable e) {
+                ExceptionUtils.handleThrowable(e);
+                init = "127.0.0.1";
+            }
+            LOCAL_ADDR_VALUE = init;
+        }
+        
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            buf.append(LOCAL_ADDR_VALUE);
+        }
+    }
+    
+    /**
+     * write remote IP address - %a
+     */
+    protected class RemoteAddrElement implements AccessLogElement {
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            if (requestAttributesEnabled) {
+                Object addr = request.getAttribute(REMOTE_ADDR_ATTRIBUTE);
+                if (addr == null) {
+                    buf.append(request.getRemoteAddr());
+                } else {
+                    buf.append(addr);
+                }
+            } else {
+                buf.append(request.getRemoteAddr());
+            }
+        }
+    }
+    
+    /**
+     * write remote host name - %h
+     */
+    protected class HostElement implements AccessLogElement {
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            if (requestAttributesEnabled) {
+                Object host = request.getAttribute(REMOTE_HOST_ATTRIBUTE);
+                if (host == null) {
+                    buf.append(request.getRemoteHost());
+                } else {
+                    buf.append(host);
+                }
+            } else {
+                buf.append(request.getRemoteHost());
+            }
+        }
+    }
+    
+    /**
+     * write remote logical username from identd (always returns '-') - %l
+     */
+    protected static class LogicalUserNameElement implements AccessLogElement {
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            buf.append('-');
+        }
+    }
+    
+    /**
+     * write request protocol - %H
+     */
+    protected class ProtocolElement implements AccessLogElement {
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            if (requestAttributesEnabled) {
+                Object proto = request.getAttribute(PROTOCOL_ATTRIBUTE);
+                if (proto == null) {
+                    buf.append(request.getProtocol());
+                } else {
+                    buf.append(proto);
+                }
+            } else {
+                buf.append(request.getProtocol());
+            }
+        }
+    }
+
+    /**
+     * write remote user that was authenticated (if any), else '-' - %u
+     */
+    protected static class UserElement implements AccessLogElement {
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            if (request != null) {
+                String value = request.getRemoteUser();
+                if (value != null) {
+                    buf.append(value);
+                } else {
+                    buf.append('-');
+                }
+            } else {
+                buf.append('-');
+            }
+        }
+    }
+
+    /**
+     * write date and time, in Common Log Format - %t
+     */
+    protected class DateAndTimeElement implements AccessLogElement {
+
+
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            AccessDateStruct struct = currentDateStruct.get();
+            if (struct.currentDateString == null) {
+                StringBuilder current = new StringBuilder(32);
+                current.append('[');
+                current.append(struct.dayFormatter.format(date));
+                current.append('/');
+                current.append(lookup(struct.monthFormatter.format(date)));
+                current.append('/');
+                current.append(struct.yearFormatter.format(date));
+                current.append(':');
+                current.append(struct.timeFormatter.format(date));
+                current.append(' ');
+                current.append(getTimeZone(date));
+                current.append(']');
+                struct.currentDateString = current.toString();
+            }
+            buf.append(struct.currentDateString);
+        }
+    }
+
+    /**
+     * write first line of the request (method and request URI) - %r
+     */
+    protected static class RequestElement implements AccessLogElement {
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            if (request != null) {
+                buf.append(request.getMethod());
+                buf.append(' ');
+                buf.append(request.getRequestURI());
+                if (request.getQueryString() != null) {
+                    buf.append('?');
+                    buf.append(request.getQueryString());
+                }
+                buf.append(' ');
+                buf.append(request.getProtocol());
+            } else {
+                buf.append("- - ");
+            }
+        }
+    }
+
+    /**
+     * write HTTP status code of the response - %s
+     */
+    protected static class HttpStatusCodeElement implements AccessLogElement {
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            if (response != null) {
+                buf.append(response.getStatus());
+            } else {
+                buf.append('-');
+            }
+        }
+    }
+
+    /**
+     * write local port on which this request was received - %p
+     */
+    protected class LocalPortElement implements AccessLogElement {
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            if (requestAttributesEnabled) {
+                Object port = request.getAttribute(SERVER_PORT_ATTRIBUTE);
+                if (port == null) {
+                    buf.append(request.getServerPort());
+                } else {
+                    buf.append(port);
+                }
+            } else {
+                buf.append(request.getServerPort());
+            }
+        }
+    }
+
+    /**
+     * write bytes sent, excluding HTTP headers - %b, %B
+     */
+    protected static class ByteSentElement implements AccessLogElement {
+        private boolean conversion;
+
+        /**
+         * if conversion is true, write '-' instead of 0 - %b
+         */
+        public ByteSentElement(boolean conversion) {
+            this.conversion = conversion;
+        }
+
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            // Don't need to flush since trigger for log message is after the
+            // response has been committed
+            long length = response.getBytesWritten(false);
+            if (length <= 0 && conversion) {
+                buf.append('-');
+            } else {
+                buf.append(length);
+            }
+        }
+    }
+
+    /**
+     * write request method (GET, POST, etc.) - %m
+     */
+    protected static class MethodElement implements AccessLogElement {
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            if (request != null) {
+                buf.append(request.getMethod());
+            }
+        }
+    }
+
+    /**
+     * write time taken to process the request - %D, %T
+     */
+    protected static class ElapsedTimeElement implements AccessLogElement {
+        private boolean millis;
+
+        /**
+         * if millis is true, write time in millis - %D
+         * if millis is false, write time in seconds - %T
+         */
+        public ElapsedTimeElement(boolean millis) {
+            this.millis = millis;
+        }
+
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            if (millis) {
+                buf.append(time);
+            } else {
+                // second
+                buf.append(time / 1000);
+                buf.append('.');
+                int remains = (int) (time % 1000);
+                buf.append(remains / 100);
+                remains = remains % 100;
+                buf.append(remains / 10);
+                buf.append(remains % 10);
+            }
+        }
+    }
+    
+    /**
+     * write Query string (prepended with a '?' if it exists) - %q
+     */
+    protected static class QueryElement implements AccessLogElement {
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            String query = null;
+            if (request != null)
+                query = request.getQueryString();
+            if (query != null) {
+                buf.append('?');
+                buf.append(query);
+            }
+        }
+    }
+
+    /**
+     * write user session ID - %S
+     */
+    protected static class SessionIdElement implements AccessLogElement {
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            if (request != null) {
+                if (request.getSession(false) != null) {
+                    buf.append(request.getSessionInternal(false)
+                            .getIdInternal());
+                } else {
+                    buf.append('-');
+                }
+            } else {
+                buf.append('-');
+            }
+        }
+    }
+
+    /**
+     * write requested URL path - %U
+     */
+    protected static class RequestURIElement implements AccessLogElement {
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            if (request != null) {
+                buf.append(request.getRequestURI());
+            } else {
+                buf.append('-');
+            }
+        }
+    }
+
+    /**
+     * write local server name - %v
+     */
+    protected static class LocalServerNameElement implements AccessLogElement {
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            buf.append(request.getServerName());
+        }
+    }
+    
+    /**
+     * write any string
+     */
+    protected static class StringElement implements AccessLogElement {
+        private String str;
+
+        public StringElement(String str) {
+            this.str = str;
+        }
+
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            buf.append(str);
+        }
+    }
+
+    /**
+     * write incoming headers - %{xxx}i
+     */
+    protected static class HeaderElement implements AccessLogElement {
+        private String header;
+
+        public HeaderElement(String header) {
+            this.header = header;
+        }
+
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            String value = request.getHeader(header);
+            if (value == null) {
+                buf.append('-');
+            } else {
+                buf.append(value);
+            }
+        }
+    }
+
+    /**
+     * write a specific cookie - %{xxx}c
+     */
+    protected static class CookieElement implements AccessLogElement {
+        private String header;
+
+        public CookieElement(String header) {
+            this.header = header;
+        }
+
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            String value = "-";
+            Cookie[] c = request.getCookies();
+            if (c != null) {
+                for (int i = 0; i < c.length; i++) {
+                    if (header.equals(c[i].getName())) {
+                        value = c[i].getValue();
+                        break;
+                    }
+                }
+            }
+            buf.append(value);
+        }
+    }
+
+    /**
+     * write a specific response header - %{xxx}o
+     */
+    protected static class ResponseHeaderElement implements AccessLogElement {
+        private String header;
+
+        public ResponseHeaderElement(String header) {
+            this.header = header;
+        }
+        
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+           if (null != response) {
+                Iterator<String> iter = response.getHeaders(header).iterator();
+                boolean first = true;
+                while (iter.hasNext()) {
+                    if (!first) {
+                        buf.append(",");
+                    }
+                    buf.append(iter.next());
+                }
+                return ;
+            }
+            buf.append("-");
+        }
+    }
+    
+    /**
+     * write an attribute in the ServletRequest - %{xxx}r
+     */
+    protected static class RequestAttributeElement implements AccessLogElement {
+        private String header;
+
+        public RequestAttributeElement(String header) {
+            this.header = header;
+        }
+
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            Object value = null;
+            if (request != null) {
+                value = request.getAttribute(header);
+            } else {
+                value = "??";
+            }
+            if (value != null) {
+                if (value instanceof String) {
+                    buf.append((String) value);
+                } else {
+                    buf.append(value.toString());
+                }
+            } else {
+                buf.append('-');
+            }
+        }
+    }
+
+    /**
+     * write an attribute in the HttpSession - %{xxx}s
+     */
+    protected static class SessionAttributeElement implements AccessLogElement {
+        private String header;
+
+        public SessionAttributeElement(String header) {
+            this.header = header;
+        }
+
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            Object value = null;
+            if (null != request) {
+                HttpSession sess = request.getSession(false);
+                if (null != sess)
+                    value = sess.getAttribute(header);
+            } else {
+                value = "??";
+            }
+            if (value != null) {
+                if (value instanceof String) {
+                    buf.append((String) value);
+                } else {
+                    buf.append(value.toString());
+                }
+            } else {
+                buf.append('-');
+            }
+        }
+    }
+
+
+    /**
+     * parse pattern string and create the array of AccessLogElement
+     */
+    protected AccessLogElement[] createLogElements() {
+        List<AccessLogElement> list = new ArrayList<AccessLogElement>();
+        boolean replace = false;
+        StringBuilder buf = new StringBuilder();
+        for (int i = 0; i < pattern.length(); i++) {
+            char ch = pattern.charAt(i);
+            if (replace) {
+                /*
+                 * For code that processes {, the behavior will be ... if I do
+                 * not encounter a closing } - then I ignore the {
+                 */
+                if ('{' == ch) {
+                    StringBuilder name = new StringBuilder();
+                    int j = i + 1;
+                    for (; j < pattern.length() && '}' != pattern.charAt(j); j++) {
+                        name.append(pattern.charAt(j));
+                    }
+                    if (j + 1 < pattern.length()) {
+                        /* the +1 was to account for } which we increment now */
+                        j++;
+                        list.add(createAccessLogElement(name.toString(),
+                                pattern.charAt(j)));
+                        i = j; /* Since we walked more than one character */
+                    } else {
+                        // D'oh - end of string - pretend we never did this
+                        // and do processing the "old way"
+                        list.add(createAccessLogElement(ch));
+                    }
+                } else {
+                    list.add(createAccessLogElement(ch));
+                }
+                replace = false;
+            } else if (ch == '%') {
+                replace = true;
+                list.add(new StringElement(buf.toString()));
+                buf = new StringBuilder();
+            } else {
+                buf.append(ch);
+            }
+        }
+        if (buf.length() > 0) {
+            list.add(new StringElement(buf.toString()));
+        }
+        return list.toArray(new AccessLogElement[0]);
+    }
+
+    /**
+     * create an AccessLogElement implementation which needs header string
+     */
+    private AccessLogElement createAccessLogElement(String header, char pattern) {
+        switch (pattern) {
+        case 'i':
+            return new HeaderElement(header);
+        case 'c':
+            return new CookieElement(header);
+        case 'o':
+            return new ResponseHeaderElement(header);
+        case 'r':
+            return new RequestAttributeElement(header);
+        case 's':
+            return new SessionAttributeElement(header);            
+        default:
+            return new StringElement("???");
+        }
+    }
+
+    /**
+     * create an AccessLogElement implementation
+     */
+    private AccessLogElement createAccessLogElement(char pattern) {
+        switch (pattern) {
+        case 'a':
+            return new RemoteAddrElement();
+        case 'A':
+            return new LocalAddrElement();
+        case 'b':
+            return new ByteSentElement(true);
+        case 'B':
+            return new ByteSentElement(false);
+        case 'D':
+            return new ElapsedTimeElement(true);
+        case 'h':
+            return new HostElement();
+        case 'H':
+            return new ProtocolElement();
+        case 'l':
+            return new LogicalUserNameElement();
+        case 'm':
+            return new MethodElement();
+        case 'p':
+            return new LocalPortElement();
+        case 'q':
+            return new QueryElement();
+        case 'r':
+            return new RequestElement();
+        case 's':
+            return new HttpStatusCodeElement();
+        case 'S':
+            return new SessionIdElement();
+        case 't':
+            return new DateAndTimeElement();
+        case 'T':
+            return new ElapsedTimeElement(false);
+        case 'u':
+            return new UserElement();
+        case 'U':
+            return new RequestURIElement();
+        case 'v':
+            return new LocalServerNameElement();
+        case 'I':
+            return new ThreadNameElement();
+        default:
+            return new StringElement("???" + pattern + "???");
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/valves/CometConnectionManagerValve.java b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/CometConnectionManagerValve.java
new file mode 100644
index 0000000..1a57dee
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/CometConnectionManagerValve.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.valves;
+
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.comet.CometEvent;
+import org.apache.catalina.comet.CometProcessor;
+import org.apache.catalina.connector.CometEventImpl;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+
+
+/**
+ * <p>Implementation of a Valve that tracks Comet connections, and closes them
+ * when the associated session expires or the webapp is reloaded.</p>
+ *
+ * <p>This Valve should be attached to a Context.</p>
+ *
+ * @author Remy Maucherat
+ * @version $Id: CometConnectionManagerValve.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+
+public class CometConnectionManagerValve extends ValveBase
+    implements HttpSessionListener, LifecycleListener {
+    
+    //------------------------------------------------------ Constructor
+    public CometConnectionManagerValve() {
+        super(false);
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The descriptive information related to this implementation.
+     */
+    protected static final String info =
+        "org.apache.catalina.valves.CometConnectionManagerValve/1.0";
+
+
+    /**
+     * List of current Comet connections.
+     */
+    protected List<Request> cometRequests =
+        Collections.synchronizedList(new ArrayList<Request>());
+    
+
+    /**
+     * Name of session attribute used to store list of comet connections.
+     */
+    protected String cometRequestsAttribute =
+        "org.apache.tomcat.comet.connectionList";
+
+
+    /**
+     * 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 {
+
+        if (container instanceof Context) {
+            container.addLifecycleListener(this);
+        }
+
+        setState(LifecycleState.STARTING);
+    }
+
+
+    /**
+     * 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 {
+
+        setState(LifecycleState.STOPPING);
+
+        if (container instanceof Context) {
+            container.removeLifecycleListener(this);
+        }
+    }
+
+    
+    @Override
+    public void lifecycleEvent(LifecycleEvent event) {
+        if (Lifecycle.BEFORE_STOP_EVENT.equals(event.getType())) {
+            // The container is getting stopped, close all current connections 
+            Iterator<Request> iterator = cometRequests.iterator();
+            while (iterator.hasNext()) {
+                Request request = iterator.next();
+                // Remove the session tracking attribute as it isn't
+                // serializable or required.
+                HttpSession session = request.getSession(false);
+                if (session != null) {
+                    session.removeAttribute(cometRequestsAttribute);
+                }
+                // Close the comet connection
+                try {
+                    CometEventImpl cometEvent = request.getEvent();
+                    cometEvent.setEventType(CometEvent.EventType.END);
+                    cometEvent.setEventSubType(
+                            CometEvent.EventSubType.WEBAPP_RELOAD);
+                    getNext().event(request, request.getResponse(), cometEvent);
+                    cometEvent.close();
+                } catch (Exception e) {
+                    container.getLogger().warn(
+                            sm.getString("cometConnectionManagerValve.event"),
+                            e);
+                }
+            }
+            cometRequests.clear();
+        }
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return descriptive information about this Valve implementation.
+     */
+    @Override
+    public String getInfo() {
+        return (info);
+    }
+
+
+    /**
+     * Register requests for tracking, whenever needed.
+     *
+     * @param request The servlet request to be processed
+     * @param response The servlet response to be created
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    @Override
+    public void invoke(Request request, Response response)
+        throws IOException, ServletException {
+        // Perform the request
+        getNext().invoke(request, response);
+        
+        if (request.isComet() && !response.isClosed()) {
+            // Start tracking this connection, since this is a 
+            // begin event, and Comet mode is on
+            HttpSession session = request.getSession(true);
+            
+            // Track the connection for webapp reload
+            cometRequests.add(request);
+            
+            // Track the connection for session expiration
+            synchronized (session) {
+                Request[] requests = (Request[])
+                        session.getAttribute(cometRequestsAttribute);
+                if (requests == null) {
+                    requests = new Request[1];
+                    requests[0] = request;
+                    session.setAttribute(cometRequestsAttribute,
+                            requests);
+                } else {
+                    Request[] newRequests = 
+                        new Request[requests.length + 1];
+                    for (int i = 0; i < requests.length; i++) {
+                        newRequests[i] = requests[i];
+                    }
+                    newRequests[requests.length] = request;
+                    session.setAttribute(cometRequestsAttribute, newRequests);
+                }
+            }
+        }
+        
+    }
+
+    
+    /**
+     * Use events to update the connection state.
+     *
+     * @param request The servlet request to be processed
+     * @param response The servlet response to be created
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    @Override
+    public void event(Request request, Response response, CometEvent event)
+        throws IOException, ServletException {
+        
+        // Perform the request
+        boolean ok = false;
+        try {
+            getNext().event(request, response, event);
+            ok = true;
+        } finally {
+            if (!ok || response.isClosed() 
+                    || (event.getEventType() == CometEvent.EventType.END)
+                    || (event.getEventType() == CometEvent.EventType.ERROR
+                            && !(event.getEventSubType() ==
+                                CometEvent.EventSubType.TIMEOUT))) {
+                
+                // Remove the connection from webapp reload tracking
+                cometRequests.remove(request);
+                
+                // Remove connection from session expiration tracking
+                // Note: can't get the session if it has been invalidated but
+                // OK since session listener will have done clean-up
+                HttpSession session = request.getSession(false);
+                if (session != null) {
+                    synchronized (session) {
+                        Request[] reqs = null;
+                        try {
+                             reqs = (Request[])
+                                session.getAttribute(cometRequestsAttribute);
+                        } catch (IllegalStateException ise) {
+                            // Ignore - session has been invalidated
+                            // Listener will have cleaned up
+                        }
+                        if (reqs != null) {
+                            boolean found = false;
+                            for (int i = 0; !found && (i < reqs.length); i++) {
+                                found = (reqs[i] == request);
+                            }
+                            if (found) {
+                                if (reqs.length > 1) {
+                                    Request[] newConnectionInfos = 
+                                        new Request[reqs.length - 1];
+                                    int pos = 0;
+                                    for (int i = 0; i < reqs.length; i++) {
+                                        if (reqs[i] != request) {
+                                            newConnectionInfos[pos++] = reqs[i];
+                                        }
+                                    }
+                                    try {
+                                        session.setAttribute(
+                                                cometRequestsAttribute,
+                                                newConnectionInfos);
+                                    } catch (IllegalStateException ise) {
+                                        // Ignore - session has been invalidated
+                                        // Listener will have cleaned up
+                                    }
+                                } else {
+                                    try {
+                                        session.removeAttribute(
+                                                cometRequestsAttribute);
+                                    } catch (IllegalStateException ise) {
+                                        // Ignore - session has been invalidated
+                                        // Listener will have cleaned up
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+    }
+
+
+    @Override
+    public void sessionCreated(HttpSessionEvent se) {
+        // NOOP
+    }
+
+
+    @Override
+    public void sessionDestroyed(HttpSessionEvent se) {
+        // Close all Comet connections associated with this session
+        Request[] reqs = (Request[])
+            se.getSession().getAttribute(cometRequestsAttribute);
+        if (reqs != null) {
+            for (int i = 0; i < reqs.length; i++) {
+                Request req = reqs[i];
+                try {
+                    CometEventImpl event = req.getEvent();
+                    event.setEventType(CometEvent.EventType.END);
+                    event.setEventSubType(CometEvent.EventSubType.SESSION_END);
+                    ((CometProcessor)
+                            req.getWrapper().getServlet()).event(event);
+                    event.close();
+                } catch (Exception e) {
+                    req.getWrapper().getParent().getLogger().warn(sm.getString(
+                            "cometConnectionManagerValve.listenerEvent"), e);
+                }
+            }
+        }
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/valves/Constants.java b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/Constants.java
new file mode 100644
index 0000000..90ef582
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/Constants.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 org.apache.catalina.valves;
+
+
+/**
+ * Manifest constants for the <code>org.apache.catalina.valves</code>
+ * package.
+ *
+ * @author Craig R. McClanahan
+ */
+
+public final class Constants {
+
+    public static final String Package = "org.apache.catalina.valves";
+
+    // Constants for the AccessLogValve class
+    public static final class AccessLog {
+        public static final String COMMON_ALIAS = "common";
+        public static final String COMMON_PATTERN = "%h %l %u %t \"%r\" %s %b";
+        public static final String COMBINED_ALIAS = "combined";
+        public static final String COMBINED_PATTERN = "%h %l %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\"";
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/valves/CrawlerSessionManagerValve.java b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/CrawlerSessionManagerValve.java
new file mode 100644
index 0000000..645864c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/CrawlerSessionManagerValve.java
@@ -0,0 +1,233 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.valves;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Pattern;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpSession;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * Web crawlers can trigger the creation of many thousands of sessions as they
+ * crawl a site which may result in significant memory consumption. This Valve
+ * ensures that crawlers are associated with a single session - just like normal
+ * users - regardless of whether or not they provide a session token with their
+ * requests.
+ */
+public class CrawlerSessionManagerValve extends ValveBase {
+
+    private static final Log log =
+        LogFactory.getLog(CrawlerSessionManagerValve.class);
+
+    private Map<String,SessionInfo> uaIpSessionInfo =
+        new ConcurrentHashMap<String, SessionInfo>();
+
+    private String crawlerUserAgents =
+        ".*[bB]ot.*|.*Yahoo! Slurp.*|.*Feedfetcher-Google.*";
+    private Pattern uaPattern = null;
+    private int sessionInactiveInterval = 60;
+
+
+    /**
+     * Specify the regular expression (using {@link Pattern}) that will be used
+     * to identify crawlers based in the User-Agent header provided. The default
+     * is ".*GoogleBot.*|.*bingbot.*|.*Yahoo! Slurp.*"
+     *  
+     * @param crawlerUserAgents The regular expression using {@link Pattern}
+     */
+    public void setCrawlerUserAgents(String crawlerUserAgents) {
+        this.crawlerUserAgents = crawlerUserAgents;
+        if (crawlerUserAgents == null || crawlerUserAgents.length() == 0) {
+            uaPattern = null;
+        } else {
+            uaPattern = Pattern.compile(crawlerUserAgents);
+        }
+    }
+
+    /**
+     * @see #setCrawlerUserAgents(String)
+     * @return  The current regular expression being used to match user agents. 
+     */
+    public String getCrawlerUserAgents() {
+        return crawlerUserAgents;
+    }
+
+
+    /**
+     * Specify the session timeout (in seconds) for a crawler's session. This is
+     * typically lower than that for a user session. The default is 60 seconds.
+     *  
+     * @param sessionInactiveInterval   The new timeout for crawler sessions
+     */
+    public void setSessionInactiveInterval(int sessionInactiveInterval) {
+        this.sessionInactiveInterval = sessionInactiveInterval;
+    }
+
+    /**
+     * @see #setSessionInactiveInterval(int)
+     * @return  The current timeout in seconds
+     */
+    public int getSessionInactiveInterval() {
+        return sessionInactiveInterval;
+    }
+
+
+    @Override
+    protected void initInternal() throws LifecycleException {
+        super.initInternal();
+        
+        uaPattern = Pattern.compile(crawlerUserAgents);
+    }
+
+
+    @Override
+    public void invoke(Request request, Response response) throws IOException,
+            ServletException {
+
+        boolean isBot = false;
+        SessionInfo sessionInfo = null;
+        String clientIp = null;
+
+        if (log.isDebugEnabled()) {
+            log.debug(request.hashCode() + ": ClientIp=" +
+                    request.getRemoteAddr() + ", RequestedSessionId=" +
+                    request.getRequestedSessionId());
+        }
+
+        // If the incoming request has a valid session ID, no action is required
+        if (request.getSession(false) == null) {
+
+            // Is this a crawler - check the UA headers
+            Enumeration<String> uaHeaders = request.getHeaders("user-agent");
+            String uaHeader = null;
+            if (uaHeaders.hasMoreElements()) {
+                uaHeader = uaHeaders.nextElement();
+            }
+            
+            // If more than one UA header - assume not a bot
+            if (uaHeader != null && !uaHeaders.hasMoreElements()) {
+
+                if (log.isDebugEnabled()) {
+                    log.debug(request.hashCode() + ": UserAgent=" + uaHeader);
+                }
+                
+                if (uaPattern.matcher(uaHeader).matches()) {
+                    isBot = true;
+                    
+                    if (log.isDebugEnabled()) {
+                        log.debug(request.hashCode() +
+                                ": Bot found. UserAgent=" + uaHeader);
+                    }
+                }
+            }
+            
+            // If this is a bot, is the session ID known?
+            if (isBot) {
+                clientIp = request.getRemoteAddr();
+                sessionInfo = uaIpSessionInfo.get(clientIp);
+                if (sessionInfo != null) {
+                    request.setRequestedSessionId(sessionInfo.getSessionId());
+                    if (log.isDebugEnabled()) {
+                        log.debug(request.hashCode() +
+                                ": SessionID=" + sessionInfo.getSessionId());
+                    }
+                }
+            }
+        }
+
+        getNext().invoke(request, response);
+        
+        if (isBot) {
+            if (sessionInfo == null) {
+                // Has bot just created a session, if so make a note of it
+                HttpSession s = request.getSession(false);
+                if (s != null) {
+                    uaIpSessionInfo.put(clientIp, new SessionInfo(s.getId()));
+                    s.setMaxInactiveInterval(sessionInactiveInterval);
+
+                    if (log.isDebugEnabled()) {
+                        log.debug(request.hashCode() +
+                                ": New bot session. SessionID=" + s.getId());
+                    }
+                }
+            } else {
+                sessionInfo.access();
+
+                if (log.isDebugEnabled()) {
+                    log.debug(request.hashCode() +
+                            ": Bot session accessed. SessionID=" +
+                            sessionInfo.getSessionId());
+                }
+            }
+        }
+    }
+
+
+    @Override
+    public void backgroundProcess() {
+        super.backgroundProcess();
+        
+        long expireTime = System.currentTimeMillis() -
+                (sessionInactiveInterval + 60) * 1000;
+
+        Iterator<Entry<String,SessionInfo>> iter =
+            uaIpSessionInfo.entrySet().iterator();
+
+        // Remove any sessions in the cache that have expired. 
+        while (iter.hasNext()) {
+            Entry<String,SessionInfo> entry = iter.next();
+            if (entry.getValue().getLastAccessed() < expireTime) {
+                iter.remove();
+            }
+        }
+    }
+
+
+    private static final class SessionInfo {
+        private final String sessionId;
+        private volatile long lastAccessed;
+        
+        public SessionInfo(String sessionId) {
+            this.sessionId = sessionId;
+            this.lastAccessed = System.currentTimeMillis();
+        }
+
+        public String getSessionId() {
+            return sessionId;
+        }
+
+        public long getLastAccessed() {
+            return lastAccessed;
+        }
+
+        public void access() {
+            lastAccessed = System.currentTimeMillis();
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/valves/ErrorReportValve.java b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/ErrorReportValve.java
new file mode 100644
index 0000000..377c047
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/ErrorReportValve.java
@@ -0,0 +1,292 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.valves;
+
+
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.util.RequestUtil;
+import org.apache.catalina.util.ServerInfo;
+import org.apache.tomcat.util.ExceptionUtils;
+
+/**
+ * <p>Implementation of a Valve that outputs HTML error pages.</p>
+ *
+ * <p>This Valve should be attached at the Host level, although it will work
+ * if attached to a Context.</p>
+ *
+ * <p>HTML code from the Cocoon 2 project.</p>
+ *
+ * @author Remy Maucherat
+ * @author Craig R. McClanahan
+ * @author <a href="mailto:nicolaken@supereva.it">Nicola Ken Barozzi</a> Aisa
+ * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
+ * @author Yoav Shapira
+ * @version $Id: ErrorReportValve.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+
+public class ErrorReportValve extends ValveBase {
+
+    //------------------------------------------------------ Constructor
+    public ErrorReportValve() {
+        super(true);
+    }
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The descriptive information related to this implementation.
+     */
+    private static final String info =
+        "org.apache.catalina.valves.ErrorReportValve/1.0";
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return descriptive information about this Valve implementation.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Invoke the next Valve in the sequence. When the invoke returns, check
+     * the response state, and output an error report is necessary.
+     *
+     * @param request The servlet request to be processed
+     * @param response The servlet response to be created
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    @Override
+    public void invoke(Request request, Response response)
+        throws IOException, ServletException {
+
+        // Perform the request
+        getNext().invoke(request, response);
+        
+        if (response.isCommitted()) {
+            return;
+        }
+
+        if (request.isAsyncStarted()) {
+            return;
+        }
+        
+        Throwable throwable =
+            (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
+
+        if (throwable != null) {
+
+            // The response is an error
+            response.setError();
+
+            // Reset the response (if possible)
+            try {
+                response.reset();
+            } catch (IllegalStateException e) {
+                // Ignore
+            }
+
+            response.sendError
+                (HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+
+        }
+
+        response.setSuspended(false);
+
+        try {
+            report(request, response, throwable);
+        } catch (Throwable tt) {
+            ExceptionUtils.handleThrowable(tt);
+        }
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Prints out an error report.
+     *
+     * @param request The request being processed
+     * @param response The response being generated
+     * @param throwable The exception that occurred (which possibly wraps
+     *  a root cause exception
+     */
+    protected void report(Request request, Response response,
+                          Throwable throwable) {
+
+        // Do nothing on non-HTTP responses
+        int statusCode = response.getStatus();
+
+        // Do nothing on a 1xx, 2xx and 3xx status
+        // Do nothing if anything has been written already
+        if ((statusCode < 400) || (response.getContentWritten() > 0))
+            return;
+
+        String message = RequestUtil.filter(response.getMessage());
+        if (message == null)
+            message = "";
+
+        // Do nothing if there is no report for the specified status code
+        String report = null;
+        try {
+            report = sm.getString("http." + statusCode, message);
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+        }
+        if (report == null)
+            return;
+
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("<html><head><title>");
+        sb.append(ServerInfo.getServerInfo()).append(" - ");
+        sb.append(sm.getString("errorReportValve.errorReport"));
+        sb.append("</title>");
+        sb.append("<style><!--");
+        sb.append(org.apache.catalina.util.TomcatCSS.TOMCAT_CSS);
+        sb.append("--></style> ");
+        sb.append("</head><body>");
+        sb.append("<h1>");
+        sb.append(sm.getString("errorReportValve.statusHeader",
+                               "" + statusCode, message)).append("</h1>");
+        sb.append("<HR size=\"1\" noshade=\"noshade\">");
+        sb.append("<p><b>type</b> ");
+        if (throwable != null) {
+            sb.append(sm.getString("errorReportValve.exceptionReport"));
+        } else {
+            sb.append(sm.getString("errorReportValve.statusReport"));
+        }
+        sb.append("</p>");
+        sb.append("<p><b>");
+        sb.append(sm.getString("errorReportValve.message"));
+        sb.append("</b> <u>");
+        sb.append(message).append("</u></p>");
+        sb.append("<p><b>");
+        sb.append(sm.getString("errorReportValve.description"));
+        sb.append("</b> <u>");
+        sb.append(report);
+        sb.append("</u></p>");
+
+        if (throwable != null) {
+
+            String stackTrace = getPartialServletStackTrace(throwable);
+            sb.append("<p><b>");
+            sb.append(sm.getString("errorReportValve.exception"));
+            sb.append("</b> <pre>");
+            sb.append(RequestUtil.filter(stackTrace));
+            sb.append("</pre></p>");
+
+            int loops = 0;
+            Throwable rootCause = throwable.getCause();
+            while (rootCause != null && (loops < 10)) {
+                stackTrace = getPartialServletStackTrace(rootCause);
+                sb.append("<p><b>");
+                sb.append(sm.getString("errorReportValve.rootCause"));
+                sb.append("</b> <pre>");
+                sb.append(RequestUtil.filter(stackTrace));
+                sb.append("</pre></p>");
+                // In case root cause is somehow heavily nested
+                rootCause = rootCause.getCause();
+                loops++;
+            }
+
+            sb.append("<p><b>");
+            sb.append(sm.getString("errorReportValve.note"));
+            sb.append("</b> <u>");
+            sb.append(sm.getString("errorReportValve.rootCauseInLogs",
+                                   ServerInfo.getServerInfo()));
+            sb.append("</u></p>");
+
+        }
+
+        sb.append("<HR size=\"1\" noshade=\"noshade\">");
+        sb.append("<h3>").append(ServerInfo.getServerInfo()).append("</h3>");
+        sb.append("</body></html>");
+
+        try {
+            try {
+                response.setContentType("text/html");
+                response.setCharacterEncoding("utf-8");
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                if (container.getLogger().isDebugEnabled())
+                    container.getLogger().debug("status.setContentType", t);
+            }
+            Writer writer = response.getReporter();
+            if (writer != null) {
+                // If writer is null, it's an indication that the response has
+                // been hard committed already, which should never happen
+                writer.write(sb.toString());
+            }
+        } catch (IOException e) {
+            // Ignore
+        } catch (IllegalStateException e) {
+            // Ignore
+        }
+        
+    }
+
+
+    /**
+     * Print out a partial servlet stack trace (truncating at the last 
+     * occurrence of javax.servlet.).
+     */
+    protected String getPartialServletStackTrace(Throwable t) {
+        StringBuilder trace = new StringBuilder();
+        trace.append(t.toString()).append('\n');
+        StackTraceElement[] elements = t.getStackTrace();
+        int pos = elements.length;
+        for (int i = 0; i < elements.length; i++) {
+            if ((elements[i].getClassName().startsWith
+                 ("org.apache.catalina.core.ApplicationFilterChain"))
+                && (elements[i].getMethodName().equals("internalDoFilter"))) {
+                pos = i;
+            }
+        }
+        for (int i = 0; i < pos; i++) {
+            if (!(elements[i].getClassName().startsWith
+                  ("org.apache.catalina.core."))) {
+                trace.append('\t').append(elements[i].toString()).append('\n');
+            }
+        }
+        return trace.toString();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/valves/ExtendedAccessLogValve.java b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/ExtendedAccessLogValve.java
new file mode 100644
index 0000000..b2500ba
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/ExtendedAccessLogValve.java
@@ -0,0 +1,882 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.valves;
+
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.net.InetAddress;
+import java.net.URLEncoder;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TimeZone;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpSession;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.util.ServerInfo;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+
+
+
+/**
+ * An implementation of the W3c Extended Log File Format. See
+ * http://www.w3.org/TR/WD-logfile.html for more information about the format.
+ *
+ * The following fields are supported:
+ * <ul>
+ * <li><code>c-dns</code>:  Client hostname</li>
+ * <li><code>c-ip</code>:  Client ip address</li>
+ * <li><code>bytes</code>:  bytes served</li>
+ * <li><code>cs-method</code>:  request method</li>
+ * <li><code>cs-uri</code>:  The full uri requested</li>
+ * <li><code>cs-uri-query</code>:  The query string</li>
+ * <li><code>cs-uri-stem</code>:  The uri without query string</li>
+ * <li><code>date</code>:  The date in yyyy-mm-dd  format for GMT</li>
+ * <li><code>s-dns</code>: The server dns entry </li>
+ * <li><code>s-ip</code>:  The server ip address</li>
+ * <li><code>cs(XXX)</code>:  The value of header XXX from client to server</li>
+ * <li><code>sc(XXX)</code>: The value of header XXX from server to client </li>
+ * <li><code>sc-status</code>:  The status code</li>
+ * <li><code>time</code>:  Time the request was served</li>
+ * <li><code>time-taken</code>:  Time (in seconds) taken to serve the request</li>
+ * <li><code>x-A(XXX)</code>: Pull XXX attribute from the servlet context </li>
+ * <li><code>x-C(XXX)</code>: Pull the first cookie of the name XXX </li>
+ * <li><code>x-O(XXX)</code>: Pull the all response header values XXX </li>
+ * <li><code>x-R(XXX)</code>: Pull XXX attribute from the servlet request </li>
+ * <li><code>x-S(XXX)</code>: Pull XXX attribute from the session </li>
+ * <li><code>x-P(...)</code>:  Call request.getParameter(...)
+ *                             and URLencode it. Helpful to capture
+ *                             certain POST parameters.
+ * </li>
+ * <li>For any of the x-H(...) the following method will be called from the
+ *                HttpServletRequestObject </li>
+ * <li><code>x-H(authType)</code>: getAuthType </li>
+ * <li><code>x-H(characterEncoding)</code>: getCharacterEncoding </li>
+ * <li><code>x-H(contentLength)</code>: getContentLength </li>
+ * <li><code>x-H(locale)</code>:  getLocale</li>
+ * <li><code>x-H(protocol)</code>: getProtocol </li>
+ * <li><code>x-H(remoteUser)</code>:  getRemoteUser</li>
+ * <li><code>x-H(requestedSessionId)</code>: getGequestedSessionId</li>
+ * <li><code>x-H(requestedSessionIdFromCookie)</code>:
+ *                  isRequestedSessionIdFromCookie </li>
+ * <li><code>x-H(requestedSessionIdValid)</code>:
+ *                  isRequestedSessionIdValid</li>
+ * <li><code>x-H(scheme)</code>:  getScheme</li>
+ * <li><code>x-H(secure)</code>:  isSecure</li>
+ * </ul>
+ *
+ *
+ *
+ * <p>
+ * Log rotation can be on or off. This is dictated by the rotatable
+ * property.
+ * </p>
+ *
+ * <p>
+ * For UvNIX users, another field called <code>checkExists</code>is also
+ * available. If set to true, the log file's existence will be checked before
+ * each logging. This way an external log rotator can move the file
+ * somewhere and tomcat will start with a new file.
+ * </p>
+ *
+ * <p>
+ * For JMX junkies, a public method called </code>rotate</code> has
+ * been made available to allow you to tell this instance to move
+ * the existing log file to somewhere else start writing a new log file.
+ * </p>
+ *
+ * <p>
+ * Conditional logging is also supported. This can be done with the
+ * <code>condition</code> property.
+ * If the value returned from ServletRequest.getAttribute(condition)
+ * yields a non-null value. The logging will be skipped.
+ * </p>
+ *
+ * <p>
+ * For extended attributes coming from a getAttribute() call,
+ * it is you responsibility to ensure there are no newline or
+ * control characters.
+ * </p>
+ *
+ *
+ * @author Tim Funk
+ * @author Peter Rossbach
+ * 
+ * @version $Id: ExtendedAccessLogValve.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+
+public class ExtendedAccessLogValve extends AccessLogValve {
+
+    private static final Log log = LogFactory.getLog(ExtendedAccessLogValve.class);
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The descriptive information about this implementation.
+     */
+    protected static final String extendedAccessLogInfo =
+        "org.apache.catalina.valves.ExtendedAccessLogValve/2.1";
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return descriptive information about this implementation.
+     */
+    @Override
+    public String getInfo() {
+        return (extendedAccessLogInfo);
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    // -------------------------------------------------------- Private Methods
+
+    /**
+     *  Wrap the incoming value into quotes and escape any inner
+     *  quotes with double quotes.
+     *
+     *  @param value - The value to wrap quotes around
+     *  @return '-' if empty of null. Otherwise, toString() will
+     *     be called on the object and the value will be wrapped
+     *     in quotes and any quotes will be escaped with 2
+     *     sets of quotes.
+     */
+    private String wrap(Object value) {
+        String svalue;
+        // Does the value contain a " ? If so must encode it
+        if (value == null || "-".equals(value))
+            return "-";
+
+        try {
+            svalue = value.toString();
+            if ("".equals(svalue))
+                return "-";
+        } catch (Throwable e) {
+            ExceptionUtils.handleThrowable(e);
+            /* Log error */
+            return "-";
+        }
+
+        /* Wrap all quotes in double quotes. */
+        StringBuilder buffer = new StringBuilder(svalue.length() + 2);
+        buffer.append('\'');
+        int i = 0;
+        while (i < svalue.length()) {
+            int j = svalue.indexOf('\'', i);
+            if (j == -1) {
+                buffer.append(svalue.substring(i));
+                i = svalue.length();
+            } else {
+                buffer.append(svalue.substring(i, j + 1));
+                buffer.append('"');
+                i = j + 2;
+            }
+        }
+
+        buffer.append('\'');
+        return buffer.toString();
+    }
+
+    /**
+     * Open the new log file for the date specified by <code>dateStamp</code>.
+     */
+    @Override
+    protected synchronized void open() {
+        super.open();
+        if (currentLogFile.length()==0) {
+            writer.println("#Fields: " + pattern);
+            writer.println("#Version: 2.0");
+            writer.println("#Software: " + ServerInfo.getServerInfo());
+        }
+    }
+
+
+    // ------------------------------------------------------ Lifecycle Methods
+
+
+    protected static class DateElement implements AccessLogElement {
+        // Milli-seconds in 24 hours
+        private static final long INTERVAL = (1000 * 60 * 60 * 24);
+        
+        private static final ThreadLocal<ElementTimestampStruct> currentDate =
+                new ThreadLocal<ElementTimestampStruct>() {
+            @Override
+            protected ElementTimestampStruct initialValue() {
+                return new ElementTimestampStruct("yyyy-MM-dd");
+            }
+        };
+                
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            ElementTimestampStruct eds = currentDate.get();
+            long millis = eds.currentTimestamp.getTime();
+            if (date.getTime() > (millis + INTERVAL -1) ||
+                    date.getTime() < millis) {
+                eds.currentTimestamp.setTime(
+                        date.getTime() - (date.getTime() % INTERVAL));
+                eds.currentTimestampString =
+                    eds.currentTimestampFormat.format(eds.currentTimestamp);
+            }
+            buf.append(eds.currentTimestampString);            
+        }
+    }
+    
+    protected static class TimeElement implements AccessLogElement {
+        // Milli-seconds in a second 
+        private static final long INTERVAL = 1000;
+        
+        private static final ThreadLocal<ElementTimestampStruct> currentTime =
+                new ThreadLocal<ElementTimestampStruct>() {
+            @Override
+            protected ElementTimestampStruct initialValue() {
+                return new ElementTimestampStruct("HH:mm:ss");
+            }
+        };
+            
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            ElementTimestampStruct eds = currentTime.get();
+            long millis = eds.currentTimestamp.getTime();
+            if (date.getTime() > (millis + INTERVAL -1) ||
+                    date.getTime() < millis) {
+                eds.currentTimestamp.setTime(
+                        date.getTime() - (date.getTime() % INTERVAL));
+                eds.currentTimestampString =
+                    eds.currentTimestampFormat.format(eds.currentTimestamp);
+            }
+            buf.append(eds.currentTimestampString);            
+        }
+    }
+    
+    protected class RequestHeaderElement implements AccessLogElement {
+        private String header;
+        
+        public RequestHeaderElement(String header) {
+            this.header = header;
+        }
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            buf.append(wrap(request.getHeader(header)));
+        }
+    }
+    
+    protected class ResponseHeaderElement implements AccessLogElement {
+        private String header;
+        
+        public ResponseHeaderElement(String header) {
+            this.header = header;
+        }
+        
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            buf.append(wrap(response.getHeader(header)));
+        }
+    }
+    
+    protected class ServletContextElement implements AccessLogElement {
+        private String attribute;
+        
+        public ServletContextElement(String attribute) {
+            this.attribute = attribute;
+        }
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            buf.append(wrap(request.getContext().getServletContext()
+                    .getAttribute(attribute)));
+        }
+    }
+    
+    protected class CookieElement implements AccessLogElement {
+        private String name;
+        
+        public CookieElement(String name) {
+            this.name = name;
+        }
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            Cookie[] c = request.getCookies();
+            for (int i = 0; c != null && i < c.length; i++) {
+                if (name.equals(c[i].getName())) {
+                    buf.append(wrap(c[i].getValue()));
+                }
+            }
+        }
+    }
+    
+    /**
+     * write a specific response header - x-O(xxx)
+     */
+    protected class ResponseAllHeaderElement implements AccessLogElement {
+        private String header;
+
+        public ResponseAllHeaderElement(String header) {
+            this.header = header;
+        }
+        
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            if (null != response) {
+                Iterator<String> iter = response.getHeaders(header).iterator();
+                if (iter.hasNext()) {
+                    StringBuilder buffer = new StringBuilder();
+                    boolean first = true;
+                    while (iter.hasNext()) {
+                        if (!first) {
+                            buffer.append(",");
+                        }
+                        buffer.append(iter.next());
+                    }
+                    buf.append(wrap(buffer.toString()));
+                }
+                return ;
+            }
+            buf.append("-");
+        }
+    }
+    
+    protected class RequestAttributeElement implements AccessLogElement { 
+        private String attribute;
+        
+        public RequestAttributeElement(String attribute) {
+            this.attribute = attribute;
+        }
+        
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            buf.append(wrap(request.getAttribute(attribute)));
+        }        
+    }
+    
+    protected class SessionAttributeElement implements AccessLogElement {
+        private String attribute;
+        
+        public SessionAttributeElement(String attribute) {
+            this.attribute = attribute;
+        }
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            HttpSession session = null;
+            if (request != null) {
+                session = request.getSession(false);
+                if (session != null)
+                    buf.append(wrap(session.getAttribute(attribute)));
+            }
+        }
+    }
+    
+    protected class RequestParameterElement implements AccessLogElement {
+        private String parameter;
+        
+        public RequestParameterElement(String parameter) {
+            this.parameter = parameter;
+        }
+        /**
+         *  urlEncode the given string. If null or empty, return null.
+         */
+        private String urlEncode(String value) {
+            if (null==value || value.length()==0) {
+                return null;
+            }
+            return URLEncoder.encode(value);
+        }   
+        
+        @Override
+        public void addElement(StringBuilder buf, Date date, Request request,
+                Response response, long time) {
+            buf.append(wrap(urlEncode(request.getParameter(parameter))));
+        }
+    }
+    
+    protected static class PatternTokenizer {
+        private StringReader sr = null;
+        private StringBuilder buf = new StringBuilder();
+        private boolean ended = false;
+        private boolean subToken;
+        private boolean parameter;
+        
+        public PatternTokenizer(String str) {
+            sr = new StringReader(str);
+        }
+        
+        public boolean hasSubToken() {
+            return subToken;
+        }
+        
+        public boolean hasParameter() {
+            return parameter;
+        }
+        
+        public String getToken() throws IOException {
+            if(ended)
+                return null ;
+            
+            String result = null;
+            subToken = false;
+            parameter = false;
+            
+            int c = sr.read();
+            while (c != -1) {
+                switch (c) {
+                case ' ':
+                    result = buf.toString();
+                    buf = new StringBuilder();
+                    buf.append((char) c);
+                    return result;
+                case '-':
+                    result = buf.toString();
+                    buf = new StringBuilder();
+                    subToken = true;
+                    return result;
+                case '(':
+                    result = buf.toString();
+                    buf = new StringBuilder();
+                    parameter = true;
+                    return result;
+                case ')':
+                    result = buf.toString();
+                    buf = new StringBuilder();
+                    break;
+                default:
+                    buf.append((char) c);
+                }
+                c = sr.read();
+            }
+            ended = true;
+            if (buf.length() != 0) {
+                return buf.toString();
+            } else {
+                return null;
+            }
+        }
+        
+        public String getParameter()throws IOException {
+            String result;
+            if (!parameter) {
+                return null;
+            }
+            parameter = false;
+            int c = sr.read();
+            while (c != -1) {
+                if (c == ')') {
+                    result = buf.toString();
+                    buf = new StringBuilder();
+                    return result;
+                }
+                buf.append((char) c);
+                c = sr.read();
+            }
+            return null;
+        }
+        
+        public String getWhiteSpaces() throws IOException {
+            if(isEnded())
+                return "" ;
+            StringBuilder whiteSpaces = new StringBuilder();
+            if (buf.length() > 0) {
+                whiteSpaces.append(buf);
+                buf = new StringBuilder();
+            }
+            int c = sr.read();
+            while (Character.isWhitespace((char) c)) {
+                whiteSpaces.append((char) c);
+                c = sr.read();
+            }
+            if (c == -1) {
+                ended = true;
+            } else {
+                buf.append((char) c);
+            }
+            return whiteSpaces.toString();
+        }
+        
+        public boolean isEnded() {
+            return ended;
+        }
+        
+        public String getRemains() throws IOException {
+            StringBuilder remains = new StringBuilder();
+            for(int c = sr.read(); c != -1; c = sr.read()) {
+                remains.append((char) c);
+            }
+            return remains.toString();
+        }
+        
+    }
+    
+    @Override
+    protected AccessLogElement[] createLogElements() {
+        if (log.isDebugEnabled()) {
+            log.debug("decodePattern, pattern =" + pattern);
+        }
+        List<AccessLogElement> list = new ArrayList<AccessLogElement>();
+
+        PatternTokenizer tokenizer = new PatternTokenizer(pattern);
+        try {
+
+            // Ignore leading whitespace.
+            tokenizer.getWhiteSpaces();
+
+            if (tokenizer.isEnded()) {
+                log.info("pattern was just empty or whitespace");
+                return null;
+            }
+
+            String token = tokenizer.getToken();
+            while (token != null) {
+                if (log.isDebugEnabled()) {
+                    log.debug("token = " + token);
+                }
+                AccessLogElement element = getLogElement(token, tokenizer);
+                if (element == null) {
+                    break;
+                }
+                list.add(element);
+                String whiteSpaces = tokenizer.getWhiteSpaces();
+                if (whiteSpaces.length() > 0) {
+                    list.add(new StringElement(whiteSpaces));
+                }
+                if (tokenizer.isEnded()) {
+                    break;
+                }
+                token = tokenizer.getToken();
+            }
+            if (log.isDebugEnabled()) {
+                log.debug("finished decoding with element size of: " + list.size());
+            }
+            return list.toArray(new AccessLogElement[0]);
+        } catch (IOException e) {
+            log.error("parse error", e);
+            return null;
+        }
+    }
+    
+    protected AccessLogElement getLogElement(String token, PatternTokenizer tokenizer) throws IOException {
+        if ("date".equals(token)) {
+            return new DateElement();
+        } else if ("time".equals(token)) {
+            if (tokenizer.hasSubToken()) {
+                String nextToken = tokenizer.getToken();
+                if ("taken".equals(nextToken)) {
+                    return new ElapsedTimeElement(false);                
+                }
+            } else {
+                return new TimeElement();
+            }
+        } else if ("bytes".equals(token)) {
+            return new ByteSentElement(true);
+        } else if ("cached".equals(token)) {
+            /* I don't know how to evaluate this! */
+            return new StringElement("-");
+        } else if ("c".equals(token)) {
+            String nextToken = tokenizer.getToken();
+            if ("ip".equals(nextToken)) {
+                return new RemoteAddrElement();
+            } else if ("dns".equals(nextToken)) {
+                return new HostElement();
+            }
+        } else if ("s".equals(token)) {
+            String nextToken = tokenizer.getToken();
+            if ("ip".equals(nextToken)) {
+                return new LocalAddrElement();
+            } else if ("dns".equals(nextToken)) {
+                return new AccessLogElement() {
+                    @Override
+                    public void addElement(StringBuilder buf, Date date,
+                            Request request, Response response, long time) {
+                        String value;
+                        try {
+                            value = InetAddress.getLocalHost().getHostName();
+                        } catch (Throwable e) {
+                            ExceptionUtils.handleThrowable(e);
+                            value = "localhost";
+                        }
+                        buf.append(value);
+                    }
+                };
+            }
+        } else if ("cs".equals(token)) {
+            return getClientToServerElement(tokenizer);
+        } else if ("sc".equals(token)) {
+            return getServerToClientElement(tokenizer);
+        } else if ("sr".equals(token) || "rs".equals(token)) {
+            return getProxyElement(tokenizer);
+        } else if ("x".equals(token)) {
+            return getXParameterElement(tokenizer);
+        }
+        log.error("unable to decode with rest of chars starting: " + token);
+        return null;
+    }
+    
+    protected AccessLogElement getClientToServerElement(
+            PatternTokenizer tokenizer) throws IOException {
+        if (tokenizer.hasSubToken()) {
+            String token = tokenizer.getToken();
+            if ("method".equals(token)) {
+                return new MethodElement();
+            } else if ("uri".equals(token)) {
+                if (tokenizer.hasSubToken()) {
+                    token = tokenizer.getToken();
+                    if ("stem".equals(token)) {
+                        return new RequestURIElement();
+                    } else if ("query".equals(token)) {
+                        return new AccessLogElement() {
+                            @Override
+                            public void addElement(StringBuilder buf, Date date,
+                                    Request request, Response response,
+                                    long time) {
+                                String query = request.getQueryString();
+                                if (query != null) {
+                                    buf.append(query);
+                                } else {
+                                    buf.append('-');
+                                }
+                            }
+                        };
+                    }
+                } else {
+                    return new AccessLogElement() {
+                        @Override
+                        public void addElement(StringBuilder buf, Date date,
+                                Request request, Response response, long time) {
+                            String query = request.getQueryString();
+                            if (query == null) {
+                                buf.append(request.getRequestURI());
+                            } else {
+                                buf.append(request.getRequestURI());
+                                buf.append('?');
+                                buf.append(request.getQueryString());
+                            }
+                        }
+                    };
+                }
+            }
+        } else if (tokenizer.hasParameter()) {
+            String parameter = tokenizer.getParameter();
+            if (parameter == null) {
+                log.error("No closing ) found for in decode");
+                return null;
+            }
+            return new RequestHeaderElement(parameter);
+        }
+        log.error("The next characters couldn't be decoded: "
+                + tokenizer.getRemains());
+        return null;
+    }
+    
+    protected AccessLogElement getServerToClientElement(
+            PatternTokenizer tokenizer) throws IOException {
+        if (tokenizer.hasSubToken()) {
+            String token = tokenizer.getToken();
+            if ("status".equals(token)) {
+                return new HttpStatusCodeElement();
+            } else if ("comment".equals(token)) {
+                return new StringElement("?");
+            }
+        } else if (tokenizer.hasParameter()) {
+            String parameter = tokenizer.getParameter();
+            if (parameter == null) {
+                log.error("No closing ) found for in decode");
+                return null;
+            }
+            return new ResponseHeaderElement(parameter);
+        }
+        log.error("The next characters couldn't be decoded: "
+                + tokenizer.getRemains());
+        return null;
+    }
+    
+    protected AccessLogElement getProxyElement(PatternTokenizer tokenizer)
+        throws IOException {
+        String token = null;
+        if (tokenizer.hasSubToken()) {
+            tokenizer.getToken();
+            return new StringElement("-");
+        } else if (tokenizer.hasParameter()) {
+            tokenizer.getParameter();
+            return new StringElement("-");
+        }
+        log.error("The next characters couldn't be decoded: " + token);
+        return null;
+    }
+    
+    protected AccessLogElement getXParameterElement(PatternTokenizer tokenizer)
+            throws IOException {
+        if (!tokenizer.hasSubToken()) {
+            log.error("x param in wrong format. Needs to be 'x-#(...)' read the docs!");
+            return null;
+        }
+        String token = tokenizer.getToken();
+        if (!tokenizer.hasParameter()) {
+            log.error("x param in wrong format. Needs to be 'x-#(...)' read the docs!");
+            return null;
+        }
+        String parameter = tokenizer.getParameter();
+        if (parameter == null) {
+            log.error("No closing ) found for in decode");
+            return null;
+        }
+        if ("A".equals(token)) {
+            return new ServletContextElement(parameter);
+        } else if ("C".equals(token)) {
+            return new CookieElement(parameter);
+        } else if ("R".equals(token)) {
+            return new RequestAttributeElement(parameter);
+        } else if ("S".equals(token)) {
+            return new SessionAttributeElement(parameter);
+        } else if ("H".equals(token)) {
+            return getServletRequestElement(parameter);
+        } else if ("P".equals(token)) {
+            return new RequestParameterElement(parameter);
+        } else if ("O".equals(token)) {
+            return new ResponseAllHeaderElement(parameter);
+        }
+        log.error("x param for servlet request, couldn't decode value: "
+                + token);
+        return null;
+    }
+    
+    protected AccessLogElement getServletRequestElement(String parameter) {
+        if ("authType".equals(parameter)) {
+            return new AccessLogElement() {
+                @Override
+                public void addElement(StringBuilder buf, Date date,
+                        Request request, Response response, long time) {
+                    buf.append(wrap(request.getAuthType()));
+                }
+            };
+        } else if ("remoteUser".equals(parameter)) {
+            return new AccessLogElement() {
+                @Override
+                public void addElement(StringBuilder buf, Date date,
+                        Request request, Response response, long time) {
+                    buf.append(wrap(request.getRemoteUser()));
+                }
+            };
+        } else if ("requestedSessionId".equals(parameter)) {
+            return new AccessLogElement() {
+                @Override
+                public void addElement(StringBuilder buf, Date date,
+                        Request request, Response response, long time) {
+                    buf.append(wrap(request.getRequestedSessionId()));
+                }
+            };
+        } else if ("requestedSessionIdFromCookie".equals(parameter)) {
+            return new AccessLogElement() {
+                @Override
+                public void addElement(StringBuilder buf, Date date,
+                        Request request, Response response, long time) {
+                    buf.append(wrap(""
+                            + request.isRequestedSessionIdFromCookie()));
+                }
+            };
+        } else if ("requestedSessionIdValid".equals(parameter)) {
+            return new AccessLogElement() {
+                @Override
+                public void addElement(StringBuilder buf, Date date,
+                        Request request, Response response, long time) {
+                    buf.append(wrap("" + request.isRequestedSessionIdValid()));
+                }
+            };
+        } else if ("contentLength".equals(parameter)) {
+            return new AccessLogElement() {
+                @Override
+                public void addElement(StringBuilder buf, Date date,
+                        Request request, Response response, long time) {
+                    buf.append(wrap("" + request.getContentLength()));
+                }
+            };
+        } else if ("characterEncoding".equals(parameter)) {
+            return new AccessLogElement() {
+                @Override
+                public void addElement(StringBuilder buf, Date date,
+                        Request request, Response response, long time) {
+                    buf.append(wrap(request.getCharacterEncoding()));
+                }
+            };
+        } else if ("locale".equals(parameter)) {
+            return new AccessLogElement() {
+                @Override
+                public void addElement(StringBuilder buf, Date date,
+                        Request request, Response response, long time) {
+                    buf.append(wrap(request.getLocale()));
+                }
+            };
+        } else if ("protocol".equals(parameter)) {
+            return new AccessLogElement() {
+                @Override
+                public void addElement(StringBuilder buf, Date date,
+                        Request request, Response response, long time) {
+                    buf.append(wrap(request.getProtocol()));
+                }
+            };
+        } else if ("scheme".equals(parameter)) {
+            return new AccessLogElement() {
+                @Override
+                public void addElement(StringBuilder buf, Date date,
+                        Request request, Response response, long time) {
+                    buf.append(request.getScheme());
+                }
+            };
+        } else if ("secure".equals(parameter)) {
+            return new AccessLogElement() {
+                @Override
+                public void addElement(StringBuilder buf, Date date,
+                        Request request, Response response, long time) {
+                    buf.append(wrap("" + request.isSecure()));
+                }
+            };
+        }
+        log.error("x param for servlet request, couldn't decode value: "
+                + parameter);
+        return null;
+    }
+
+    private static class ElementTimestampStruct {
+        private Date currentTimestamp = new Date(0);
+        private SimpleDateFormat currentTimestampFormat;
+        private String currentTimestampString;
+        
+        ElementTimestampStruct(String format) {
+            currentTimestampFormat = new SimpleDateFormat(format);
+            currentTimestampFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/valves/JDBCAccessLogValve.java b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/JDBCAccessLogValve.java
new file mode 100644
index 0000000..767c30f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/JDBCAccessLogValve.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.valves;
+
+
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.Driver;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.Properties;
+
+import javax.servlet.ServletException;
+
+import org.apache.catalina.AccessLog;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.tomcat.util.ExceptionUtils;
+
+/**
+ * <p>
+ * This Tomcat extension logs server access directly to a database, and can 
+ * be used instead of the regular file-based access log implemented in 
+ * AccessLogValve.
+ * To use, copy into the server/classes directory of the Tomcat installation
+ * and configure in server.xml as:
+ * <pre>
+ *      &lt;Valve className="org.apache.catalina.valves.JDBCAccessLogValve"
+ *          driverName="<i>your_jdbc_driver</i>"
+ *          connectionURL="<i>your_jdbc_url</i>"
+ *          pattern="combined" resolveHosts="false"
+ *      /&gt;
+ * </pre>
+ * </p>
+ * <p>
+ * Many parameters can be configured, such as the database connection (with
+ * <code>driverName</code> and <code>connectionURL</code>),
+ * the table name (<code>tableName</code>)
+ * and the field names (corresponding to the get/set method names).
+ * The same options as AccessLogValve are supported, such as
+ * <code>resolveHosts</code> and <code>pattern</code> ("common" or "combined" 
+ * only).
+ * </p>
+ * <p>
+ * When Tomcat is started, a database connection (with autoReconnect option)
+ * is created and used for all the log activity. When Tomcat is shutdown, the
+ * database connection is closed.
+ * This logger can be used at the level of the Engine context (being shared
+ * by all the defined hosts) or the Host context (one instance of the logger 
+ * per host, possibly using different databases).
+ * </p>
+ * <p>
+ * The database table can be created with the following command:
+ * </p>
+ * <pre>
+ * CREATE TABLE access (
+ * id INT UNSIGNED AUTO_INCREMENT NOT NULL,
+ * remoteHost CHAR(15) NOT NULL,
+ * userName CHAR(15),
+ * timestamp TIMESTAMP NOT NULL,
+ * virtualHost VARCHAR(64) NOT NULL,
+ * method VARCHAR(8) NOT NULL,
+ * query VARCHAR(255) NOT NULL,
+ * status SMALLINT UNSIGNED NOT NULL,
+ * bytes INT UNSIGNED NOT NULL,
+ * referer VARCHAR(128),
+ * userAgent VARCHAR(128),
+ * PRIMARY KEY (id),
+ * INDEX (timestamp),
+ * INDEX (remoteHost),
+ * INDEX (virtualHost),
+ * INDEX (query),
+ * INDEX (userAgent)
+ * );
+ * </pre>
+ * <p>Set JDBCAccessLogValve attribute useLongContentLength="true" as you have more then 4GB outputs. 
+ * Please, use long SQL datatype at access.bytes attribute.
+ * The datatype of bytes at oracle is <i>number</i> and other databases use <i>bytes BIGINT NOT NULL</i>.
+ * </p>
+ * 
+ * <p>
+ * If the table is created as above, its name and the field names don't need 
+ * to be defined.
+ * </p>
+ * <p>
+ * If the request method is "common", only these fields are used:
+ * <code>remoteHost, user, timeStamp, query, status, bytes</code>
+ * </p>
+ * <p>
+ * <i>TO DO: provide option for excluding logging of certain MIME types.</i>
+ * </p>
+ * 
+ * @author Andre de Jesus
+ * @author Peter Rossbach
+ */
+
+public final class JDBCAccessLogValve extends ValveBase implements AccessLog {
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Class constructor. Initializes the fields with the default values.
+     * The defaults are:
+     * <pre>
+     *      driverName = null;
+     *      connectionURL = null;
+     *      tableName = "access";
+     *      remoteHostField = "remoteHost";
+     *      userField = "userName";
+     *      timestampField = "timestamp";
+     *      virtualHostField = "virtualHost";
+     *      methodField = "method";
+     *      queryField = "query";
+     *      statusField = "status";
+     *      bytesField = "bytes";
+     *      refererField = "referer";
+     *      userAgentField = "userAgent";
+     *      pattern = "common";
+     *      resolveHosts = false;
+     * </pre>
+     */
+    public JDBCAccessLogValve() {
+        super(false);
+        driverName = null;
+        connectionURL = null;
+        tableName = "access";
+        remoteHostField = "remoteHost";
+        userField = "userName";
+        timestampField = "timestamp";
+        virtualHostField = "virtualHost";
+        methodField = "method";
+        queryField = "query";
+        statusField = "status";
+        bytesField = "bytes";
+        refererField = "referer";
+        userAgentField = "userAgent";
+        pattern = "common";
+        resolveHosts = false;
+        conn = null;
+        ps = null;
+        currentTimeMillis = new java.util.Date().getTime();
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+   /**
+    * Use long contentLength as you have more 4 GB output.
+    * @since 6.0.15
+    */
+    protected boolean useLongContentLength = false ;
+    
+   /**
+     * The connection username to use when trying to connect to the database.
+     */
+    protected String connectionName = null;
+
+
+    /**
+     * The connection URL to use when trying to connect to the database.
+     */
+    protected String connectionPassword = null;
+
+   /**
+     * Instance of the JDBC Driver class we use as a connection factory.
+     */
+    protected Driver driver = null;
+
+
+    private String driverName;
+    private String connectionURL;
+    private String tableName;
+    private String remoteHostField;
+    private String userField;
+    private String timestampField;
+    private String virtualHostField;
+    private String methodField;
+    private String queryField;
+    private String statusField;
+    private String bytesField;
+    private String refererField;
+    private String userAgentField;
+    private String pattern;
+    private boolean resolveHosts;
+
+
+    private Connection conn;
+    private PreparedStatement ps;
+
+
+    private long currentTimeMillis;
+
+    /**
+     * @see #setRequestAttributesEnabled(boolean)
+     */
+    protected boolean requestAttributesEnabled = true;
+
+    /**
+     * The descriptive information about this implementation.
+     */
+    protected static final String info = 
+        "org.apache.catalina.valves.JDBCAccessLogValve/1.1";
+
+
+    // ------------------------------------------------------------- Properties
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setRequestAttributesEnabled(boolean requestAttributesEnabled) {
+        this.requestAttributesEnabled = requestAttributesEnabled;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean getRequestAttributesEnabled() {
+        return requestAttributesEnabled;
+    }
+
+    /**
+     * Return the username to use to connect to the database.
+     *
+     */
+    public String getConnectionName() {
+        return connectionName;
+    }
+
+    /**
+     * Set the username to use to connect to the database.
+     *
+     * @param connectionName Username
+     */
+    public void setConnectionName(String connectionName) {
+        this.connectionName = connectionName;
+    }
+
+    /**
+     * Sets the database driver name.
+     * 
+     * @param driverName The complete name of the database driver class.
+     */
+    public void setDriverName(String driverName) {
+        this.driverName = driverName;
+    }
+
+   /**
+     * Return the password to use to connect to the database.
+     *
+     */
+    public String getConnectionPassword() {
+        return connectionPassword;
+    }
+
+    /**
+     * Set the password to use to connect to the database.
+     *
+     * @param connectionPassword User password
+     */
+    public void setConnectionPassword(String connectionPassword) {
+        this.connectionPassword = connectionPassword;
+    }
+
+    /**
+     * Sets the JDBC URL for the database where the log is stored.
+     * 
+     * @param connectionURL The JDBC URL of the database.
+     */
+    public void setConnectionURL(String connectionURL) {
+        this.connectionURL = connectionURL;
+    }
+
+
+    /**
+     * Sets the name of the table where the logs are stored.
+     * 
+     * @param tableName The name of the table.
+     */
+    public void setTableName(String tableName) {
+        this.tableName = tableName;
+    }
+
+
+    /**
+     * Sets the name of the field containing the remote host.
+     * 
+     * @param remoteHostField The name of the remote host field.
+     */
+    public void setRemoteHostField(String remoteHostField) {
+        this.remoteHostField = remoteHostField;
+    }
+
+
+    /**
+     * Sets the name of the field containing the remote user name.
+     * 
+     * @param userField The name of the remote user field.
+     */
+    public void setUserField(String userField) {
+        this.userField = userField;
+    }
+
+
+    /**
+     * Sets the name of the field containing the server-determined timestamp.
+     * 
+     * @param timestampField The name of the server-determined timestamp field.
+     */
+    public void setTimestampField(String timestampField) {
+        this.timestampField = timestampField;
+    }
+
+
+    /**
+     * Sets the name of the field containing the virtual host information 
+     * (this is in fact the server name).
+     * 
+     * @param virtualHostField The name of the virtual host field.
+     */
+    public void setVirtualHostField(String virtualHostField) {
+        this.virtualHostField = virtualHostField;
+    }
+
+
+    /**
+     * Sets the name of the field containing the HTTP request method.
+     * 
+     * @param methodField The name of the HTTP request method field.
+     */
+    public void setMethodField(String methodField) {
+        this.methodField = methodField;
+    }
+
+
+    /**
+     * Sets the name of the field containing the URL part of the HTTP query.
+     * 
+     * @param queryField The name of the field containing the URL part of 
+     * the HTTP query.
+     */
+    public void setQueryField(String queryField) {
+        this.queryField = queryField;
+    }
+
+
+  /**
+   * Sets the name of the field containing the HTTP response status code.
+   * 
+   * @param statusField The name of the HTTP response status code field.
+   */  
+    public void setStatusField(String statusField) {
+        this.statusField = statusField;
+    }
+
+
+    /**
+     * Sets the name of the field containing the number of bytes returned.
+     * 
+     * @param bytesField The name of the returned bytes field.
+     */
+    public void setBytesField(String bytesField) {
+        this.bytesField = bytesField;
+    }
+
+
+    /**
+     * Sets the name of the field containing the referer.
+     * 
+     * @param refererField The referer field name.
+     */
+    public void setRefererField(String refererField) {
+        this.refererField = refererField;
+    }
+
+
+    /**
+     * Sets the name of the field containing the user agent.
+     * 
+     * @param userAgentField The name of the user agent field.
+     */
+    public void setUserAgentField(String userAgentField) {
+        this.userAgentField = userAgentField;
+    }
+
+
+    /**
+     * Sets the logging pattern. The patterns supported correspond to the 
+     * file-based "common" and "combined". These are translated into the use 
+     * of tables containing either set of fields.
+     * <P><I>TO DO: more flexible field choices.</I></P>
+     * 
+     * @param pattern The name of the logging pattern.
+     */
+    public void setPattern(String pattern) {
+        this.pattern = pattern;
+    }
+
+
+    /**
+     * Determines whether IP host name resolution is done.
+     * 
+     * @param resolveHosts "true" or "false", if host IP resolution 
+     * is desired or not.
+     */
+    public void setResolveHosts(String resolveHosts) {
+        this.resolveHosts = Boolean.valueOf(resolveHosts).booleanValue();
+    }
+
+    /**
+     * get useLongContentLength
+     */
+    public  boolean getUseLongContentLength() {
+        return this.useLongContentLength ;
+    }
+    
+    /**
+     * @param useLongContentLength the useLongContentLength to set
+     */
+    public void setUseLongContentLength(boolean useLongContentLength) {
+        this.useLongContentLength = useLongContentLength;
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * This method is invoked by Tomcat on each query.
+     * 
+     * @param request The Request object.
+     * @param response The Response object.
+     *
+     * @exception IOException Should not be thrown.
+     * @exception ServletException Database SQLException is wrapped 
+     * in a ServletException.
+     */
+    @Override
+    public void invoke(Request request, Response response) throws IOException,
+            ServletException {
+        getNext().invoke(request, response);
+    }
+
+
+    @Override
+    public void log(Request request, Response response, long time) {
+        if (!getState().isAvailable()) {
+            return;
+        }
+
+        final String EMPTY = "" ;
+        
+        String remoteHost;
+        if(resolveHosts) {
+            if (requestAttributesEnabled) {
+                Object host = request.getAttribute(REMOTE_HOST_ATTRIBUTE);
+                if (host == null) {
+                    remoteHost = request.getRemoteHost();
+                } else {
+                    remoteHost = (String) host;
+                }
+            } else {
+                remoteHost = request.getRemoteHost();
+            }
+        } else {
+            if (requestAttributesEnabled) {
+                Object addr = request.getAttribute(REMOTE_ADDR_ATTRIBUTE);
+                if (addr == null) {
+                    remoteHost = request.getRemoteAddr();
+                } else {
+                    remoteHost = (String) addr;
+                }
+            } else {
+                remoteHost = request.getRemoteAddr();
+            }
+        }
+        String user = request.getRemoteUser();
+        String query=request.getRequestURI();
+        
+        long bytes = response.getBytesWritten(true);
+        if(bytes < 0)
+            bytes = 0;
+        int status = response.getStatus();
+        String virtualHost = EMPTY;
+        String method = EMPTY;
+        String referer = EMPTY;
+        String userAgent = EMPTY;
+        String logPattern = pattern;
+        if (logPattern.equals("combined")) {
+            virtualHost = request.getServerName();
+            method = request.getMethod();
+            referer = request.getHeader("referer");
+            userAgent = request.getHeader("user-agent");
+        }
+        synchronized (this) {
+          int numberOfTries = 2;
+          while (numberOfTries>0) {
+            try {
+                open();
+    
+                ps.setString(1, remoteHost);
+                ps.setString(2, user);
+                ps.setTimestamp(3, new Timestamp(getCurrentTimeMillis()));
+                ps.setString(4, query);
+                ps.setInt(5, status);
+                
+                if(useLongContentLength) {
+                    ps.setLong(6, bytes);                
+                } else {
+                    if (bytes > Integer.MAX_VALUE)
+                        bytes = -1 ;
+                    ps.setInt(6, (int) bytes);
+                }               
+                if (logPattern.equals("combined")) {
+                      ps.setString(7, virtualHost);
+                      ps.setString(8, method);
+                      ps.setString(9, referer);
+                      ps.setString(10, userAgent);
+                }
+                ps.executeUpdate();
+                return;
+              } catch (SQLException e) {
+                // Log the problem for posterity
+                  container.getLogger().error(sm.getString("jdbcAccessLogValve.exception"), e);
+
+                // Close the connection so that it gets reopened next time
+                if (conn != null)
+                    close();
+              }
+              numberOfTries--;
+           }
+        }
+
+    }
+
+
+    /**
+     * Open (if necessary) and return a database connection for use by
+     * this AccessLogValve.
+     *
+     * @exception SQLException if a database error occurs
+     */
+    protected void open() throws SQLException {
+
+        // Do nothing if there is a database connection already open
+        if (conn != null)
+            return ;
+
+        // Instantiate our database driver if necessary
+        if (driver == null) {
+            try {
+                Class<?> clazz = Class.forName(driverName);
+                driver = (Driver) clazz.newInstance();
+            } catch (Throwable e) {
+                ExceptionUtils.handleThrowable(e);
+                throw new SQLException(e.getMessage(), e);
+            }
+        }
+
+        // Open a new connection
+        Properties props = new Properties();
+        props.put("autoReconnect", "true");
+        if (connectionName != null)
+            props.put("user", connectionName);
+        if (connectionPassword != null)
+            props.put("password", connectionPassword);
+        conn = driver.connect(connectionURL, props);
+        conn.setAutoCommit(true);
+        String logPattern = pattern;
+        if (logPattern.equals("common")) {
+                ps = conn.prepareStatement
+                    ("INSERT INTO " + tableName + " (" 
+                     + remoteHostField + ", " + userField + ", "
+                     + timestampField +", " + queryField + ", "
+                     + statusField + ", " + bytesField 
+                     + ") VALUES(?, ?, ?, ?, ?, ?)");
+        } else if (logPattern.equals("combined")) {
+                ps = conn.prepareStatement
+                    ("INSERT INTO " + tableName + " (" 
+                     + remoteHostField + ", " + userField + ", "
+                     + timestampField + ", " + queryField + ", " 
+                     + statusField + ", " + bytesField + ", " 
+                     + virtualHostField + ", " + methodField + ", "
+                     + refererField + ", " + userAgentField
+                     + ") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
+        }
+    }
+
+    /**
+     * Close the specified database connection.
+     */
+    protected void close() {
+
+        // Do nothing if the database connection is already closed
+        if (conn == null)
+            return;
+
+        // Close our prepared statements (if any)
+        try {
+            ps.close();
+        } catch (Throwable f) {
+            ExceptionUtils.handleThrowable(f);
+        }
+        this.ps = null;
+
+
+
+        // Close this database connection, and log any errors
+        try {
+            conn.close();
+        } catch (SQLException e) {
+            container.getLogger().error(sm.getString("jdbcAccessLogValeve.close"), e); // Just log it here            
+        } finally {
+           this.conn = null;
+        }
+
+    }
+    
+    
+    /**
+     * 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 {
+            open() ;        
+        } catch (SQLException e) {
+            throw new LifecycleException(e);
+        }
+
+        setState(LifecycleState.STARTING);
+    }
+
+
+    /**
+     * 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 {
+
+        setState(LifecycleState.STOPPING);
+        
+        close() ;
+    }
+
+
+    public long getCurrentTimeMillis() {
+        long systime  =  System.currentTimeMillis();
+        if ((systime - currentTimeMillis) > 1000) {
+            currentTimeMillis  =  new java.util.Date(systime).getTime();
+        }
+        return currentTimeMillis;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/valves/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/LocalStrings.properties
new file mode 100644
index 0000000..56e5c51
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/LocalStrings.properties
@@ -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.
+
+requestFilterValve.next=No ''next'' valve has been configured
+requestFilterValve.syntax=Syntax error in request filter pattern {0}
+valveBase.noNext=Configuration error: No ''next'' valve configured
+jdbcAccessLogValve.exception=Exception performing insert access entry
+jdbcAccessLogValve.close=Exception closing database connection
+cometConnectionManagerValve.event=Exception processing event
+cometConnectionManagerValve.listenerEvent=Exception processing session listener event
+
+# Access log valve
+accessLogValve.closeFail=Failed to close log file
+accessLogValve.openDirFail=Failed to create directory [{0}] for access logs
+accessLogValve.rotateFail=Failed to rotate access log
+
+# Error report valve
+errorReportValve.errorReport=Error report
+errorReportValve.statusHeader=HTTP Status {0} - {1}
+errorReportValve.exceptionReport=Exception report
+errorReportValve.statusReport=Status report
+errorReportValve.message=message
+errorReportValve.description=description
+errorReportValve.exception=exception
+errorReportValve.rootCause=root cause
+errorReportValve.note=note
+errorReportValve.rootCauseInLogs=The full stack trace of the root cause is available in the {0} logs.
+
+# Remote IP valve
+remoteIpValve.syntax=Invalid regular expressions [{0}] provided.
+
+sslValve.certError=Failed to process certificate string [{0}] to create a java.security.cert.X509Certificate object
+sslValve.invalidProvider=The SSL provider specified on the connector associated with this request of [{0}] is invalid. The certificate data could not be processed.
+
+# HTTP status reports
+http.100=The client may continue ({0}).
+http.101=The server is switching protocols according to the "Upgrade" header ({0}).
+http.201=The request succeeded and a new resource ({0}) has been created on the server.
+http.202=This request was accepted for processing, but has not been completed ({0}).
+http.203=The meta information presented by the client did not originate from the server ({0}).
+http.204=The request succeeded but there is no information to return ({0}).
+http.205=The client should reset the document view which caused this request to be sent ({0}).
+http.206=The server has fulfilled a partial GET request for this resource ({0}).
+http.207=Multiple status values have been returned ({0}).
+http.300=The requested resource ({0}) corresponds to any one of a set of representations, each with its own specific location.
+http.301=The requested resource ({0}) has moved permanently to a new location.
+http.302=The requested resource ({0}) has moved temporarily to a new location.
+http.303=The response to this request can be found under a different URI ({0}).
+http.304=The requested resource ({0}) is available and has not been modified.
+http.305=The requested resource ({0}) must be accessed through the proxy given by the "Location" header.
+http.400=The request sent by the client was syntactically incorrect ({0}).
+http.401=This request requires HTTP authentication ({0}).
+http.402=Payment is required for access to this resource ({0}).
+http.403=Access to the specified resource ({0}) has been forbidden.
+http.404=The requested resource ({0}) is not available.
+http.405=The specified HTTP method is not allowed for the requested resource ({0}).
+http.406=The resource identified by this request is only capable of generating responses with characteristics not acceptable according to the request "accept" headers ({0}).
+http.407=The client must first authenticate itself with the proxy ({0}).
+http.408=The client did not produce a request within the time that the server was prepared to wait ({0}).
+http.409=The request could not be completed due to a conflict with the current state of the resource ({0}).
+http.410=The requested resource ({0}) is no longer available, and no forwarding address is known.
+http.411=This request cannot be handled without a defined content length ({0}).
+http.412=A specified precondition has failed for this request ({0}).
+http.413=The request entity is larger than the server is willing or able to process.
+http.414=The server refused this request because the request URI was too long ({0}).
+http.415=The server refused this request because the request entity is in a format not supported by the requested resource for the requested method ({0}).
+http.416=The requested byte range cannot be satisfied ({0}).
+http.417=The expectation given in the "Expect" request header ({0}) could not be fulfilled.
+http.422=The server understood the content type and syntax of the request but was unable to process the contained instructions ({0}).
+http.423=The source or destination resource of a method is locked ({0}).
+http.500=The server encountered an internal error ({0}) that prevented it from fulfilling this request.
+http.501=The server does not support the functionality needed to fulfill this request ({0}).
+http.502=This server received an invalid response from a server it consulted when acting as a proxy or gateway ({0}).
+http.503=The requested service ({0}) is not currently available.
+http.504=The server received a timeout from an upstream server while acting as a gateway or proxy ({0}).
+http.505=The server does not support the requested HTTP protocol version ({0}).
+http.507=The resource does not have sufficient space to record the state of the resource after execution of this method ({0}).
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/valves/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/LocalStrings_es.properties
new file mode 100644
index 0000000..0e1b5dd
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/LocalStrings_es.properties
@@ -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.
+requestFilterValve.next = No hay ''siguiente'' v\u00E1lvula configurada
+requestFilterValve.syntax = Error de sint\u00E1xis en petici\u00F3n de filtro patr\u00F3n {0}
+valveBase.noNext = Error de configuraci\u00F3n\: No hay ''siguiente'' v\u00E1lvula configurada
+jdbcAccessLogValve.exception = Excepci\u00F3n realizando entrada de acceso a inserci\u00F3n
+jdbcAccessLogValve.close = Excepci\u00F3n cerrando conexi\u00F3n a base de datos
+cometConnectionManagerValve.event = Excepci\u00F3n procesando evento
+cometConnectionManagerValve.listenerEvent = Excepci\u00F3n procesando evento de oyente de sesi\u00F3n
+# Error report valve
+errorReportValve.errorReport = Informe de Error
+errorReportValve.statusHeader = Estado HTTP {0} - {1}
+errorReportValve.exceptionReport = Informe de Excepci\u00F3n
+errorReportValve.statusReport = Informe de estado
+errorReportValve.message = mensaje
+errorReportValve.description = descripci\u00F3n
+errorReportValve.exception = excepci\u00F3n
+errorReportValve.rootCause = causa ra\u00EDz
+errorReportValve.note = nota
+errorReportValve.rootCauseInLogs = La traza completa de la causa de este error se encuentra en los archivos de diario de {0}.
+# HTTP status reports
+http.100 = El cliente puede continuar ({0}).
+http.101 = El servidor est\u00E1 conmutando protocolos con arreglo a la cabecera "Upgrade" ({0}).
+http.201 = El requerimiento tuvo \u00E9xito y un nuevo recurso ({0}) ha sido creado en el servidor.
+http.202 = Este requerimiento ha sido aceptado para ser procesado, pero no ha sido completado ({0}).
+http.203 = La informaci\u00F3n meta presentada por el cliente no se origin\u00F3 desde el servidor ({0}).
+http.204 = El requerimiento tuvo \u00E9xito pero no hay informaci\u00F3n que devolver ({0}).
+http.205 = El cliente no deber\u00EDa de limpiar la vista del documento que caus\u00F3 que este requerimiento fuera enviado ({0}).
+http.206 = El servidor ha rellenado paci\u00E1lmente un requerimiento GET para este recurso ({0}).
+http.207 = Se han devuelto valores m\u00FAltiples de estado ({0}).
+http.300 = El recurso requerido ({0}) corresponde a una cualquiera de un conjunto de representaciones, cada una con su propia localizaci\u00F3n espec\u00EDfica.
+http.301 = El recurso requerido ({0}) ha sido movido perman\u00E9ntemente a una nueva localizaci\u00F3n.
+http.302 = El recurso requerido ({0}) ha sido movido tempor\u00E1lmente a una nueva localizaci\u00F3n.
+http.303 = La respuesta a este requerimiento se puede hallar bajo una URI diferente ({0}).
+http.304 = El recurso requerido ({0}) est\u00E1 disponible y no ha sido modificado.
+http.305 = El recurso requerido ({0}) debe de ser accedido a trav\u00E9s del apoderado (proxy) dado mediante la cabecera "Location".
+http.400 = El requerimiento enviado por el cliente era sint\u00E1cticamente incorrecto ({0}).
+http.401 = Este requerimiento requiere autenticaci\u00F3n HTTP ({0}).
+http.402 = Se requiere pago para acceder a este recurso ({0}).
+http.403 = El acceso al recurso especificado ({0}) ha sido prohibido.
+http.404 = El recurso requerido ({0}) no est\u00E1 disponible.
+http.405 = El m\u00E9todo HTTP especificado no est\u00E1 permitido para el recurso requerido ({0}).
+http.406 = El recurso identificado por este requerimiento s\u00F3lo es capaz de generar respuestas con caracter\u00EDsticas no aceptables con arreglo a las cabeceras "accept" de requerimiento ({0}).
+http.407 = El cliente debe de ser primero autenticado en el apoderado ({0}).
+http.408 = El cliente no produjo un requerimiento dentro del tiempo en que el servidor estaba preparado esperando ({0}).
+http.409 = El requerimiento no pudo ser completado debido a un conflicto con el estado actual del recurso ({0}).
+http.410 = El recurso requerido ({0}) ya no est\u00E1 disponible y no se conoce direcci\u00F3n de reenv\u00EDo.
+http.411 = Este requerimiento no puede ser manejado sin un tama\u00F1o definido de contenido ({0}).
+http.412 = Una precondici\u00F3n especificada ha fallado para este requerimiento ({0}).
+http.413 = La entidad de requerimiento es mayor de lo que el servidor quiere o puede procesar.
+http.414 = El servidor rechaz\u00F3 este requerimiento porque la URI requerida era demasiado larga ({0}).
+http.415 = El servidor rechaz\u00F3 este requerimiento porque la entidad requerida se encuentra en un formato no soportado por el recurso requerido para el m\u00E9todo requerido ({0}).
+http.416 = El rango de byte requerido no puede ser satisfecho ({0}).
+http.417 = Lo que se espera dado por la cabecera "Expect" de requerimiento ({0}) no pudo ser completado.
+http.422 = El servidor entendi\u00F3 el tipo de contenido y la sint\u00E1xis del requerimiento pero no pudo procesar las instrucciones contenidas ({0}).
+http.423 = La fuente o recurso de destino de un m\u00E9todo est\u00E1 bloqueada ({0}).
+http.500 = El servidor encontr\u00F3 un error interno ({0}) que hizo que no pudiera rellenar este requerimiento.
+http.501 = El servidor no soporta la funcionalidad necesaria para rellenar este requerimiento ({0}).
+http.502 = Este servidor recibi\u00F3 una respuesta inv\u00E1lida desde un servidor que consult\u00F3 cuando actuaba como apoderado o pasarela ({0}).
+http.503 = El servicio requerido ({0}) no est\u00E1 disponible en este momento.
+http.504 = El servidor recibi\u00F3 un Tiempo Agotado desde un servidor superior cuando actuaba como pasarela o apoderado ({0}).
+http.505 = El servidor no soporta la versi\u00F3n de protocolo HTTP requerida ({0}).
+http.507 = El recurso no tiene espacio suficiente para registrar el estado del recurso tras la ejecuci\u00F3n de este m\u00E9todo ({0}).
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/valves/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/LocalStrings_fr.properties
new file mode 100644
index 0000000..ad9ad38
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/LocalStrings_fr.properties
@@ -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.
+
+requestFilterValve.next=Aucune Valve ''suivante'' n''a \u00e9t\u00e9 configur\u00e9e
+requestFilterValve.syntax=Erreur de syntaxe dans le pattern de filtre de requ\u00eate {0}
+valveBase.noNext=Erreur de configuration: aucune Valve ''suivante'' n''a \u00e9t\u00e9 configur\u00e9e
+
+# Error report valve
+errorReportValve.errorReport=Rapport d''erreur
+errorReportValve.statusHeader=Etat HTTP {0} - {1}
+errorReportValve.exceptionReport=Rapport d''exception
+errorReportValve.statusReport=Rapport d''\u00e9tat
+errorReportValve.message=message
+errorReportValve.description=description
+errorReportValve.exception=exception
+errorReportValve.rootCause=cause m\u00e8re
+errorReportValve.note=note
+errorReportValve.rootCauseInLogs=La trace compl\u00e8te de la cause m\u00e8re de cette erreur est disponible dans les fichiers journaux de {0}.
+
+# HTTP status reports
+http.100=Le client peut continuer ({0}).
+http.101=Le serveur change de protocoles suivant la directive "Upgrade" de l''ent\u00eate ({0}).
+http.201=La requ\u00eate a r\u00e9ussi et une nouvelle ressource ({0}) a \u00e9t\u00e9 cr\u00e9\u00e9e sur le serveur.
+http.202=La requ\u00eate a \u00e9t\u00e9 accept\u00e9e pour traitement, mais n''a pas \u00e9t\u00e9 termin\u00e9e ({0}).
+http.203=L''information meta pr\u00e9sent\u00e9e par le client n''a pas pour origine ce serveur ({0}).
+http.204=La requ\u00eate a r\u00e9ussi mais il n''y a aucune information \u00e0 retourner ({0}).
+http.205=Le client doit remettre \u00e0 z\u00e9ro la vue de document qui a caus\u00e9 l''envoi de cette requ\u00eate ({0}).
+http.206=Le serveur a satisfait une requ\u00eate GET partielle pour cette ressource ({0}).
+http.207=Plusieurs valeurs d''\u00e9tats ont \u00e9t\u00e9 retourn\u00e9es ({0}).
+http.300=La ressource demand\u00e9e ({0}) correspond \u00e0 plusieurs repr\u00e9sentations, chacune avec sa propre localisation.
+http.301=La ressource demand\u00e9e ({0}) a \u00e9t\u00e9 d\u00e9plac\u00e9e de fa\u00e7on permanente vers une nouvelle localisation.
+http.302=La ressource demand\u00e9e ({0}) a \u00e9t\u00e9 d\u00e9plac\u00e9e de fa\u00e7on temporaire vers une nouvelle localisation.
+http.303=La r\u00e9ponse \u00e0 cette requ\u00eate peut \u00eatre trouv\u00e9e \u00e0 une URI diff\u00e9rente ({0}).
+http.304=La ressource demand\u00e9e ({0}) est disponible et n''a pas \u00e9t\u00e9 modifi\u00e9e.
+http.305=La ressource demand\u00e9e ({0}) doit \u00eatre acc\u00e9d\u00e9e au travers du relais indiqu\u00e9 par la directive "Location" de l''ent\u00eate.
+http.400=La requ\u00eate envoy\u00e9e par le client \u00e9tait syntaxiquement incorrecte ({0}).
+http.401=La requ\u00eate n\u00e9cessite une authentification HTTP ({0}).
+http.402=Un paiement est demand\u00e9 pour acc\u00e9der \u00e0 cette ressource ({0}).
+http.403=L''acc\u00e8s \u00e0 la ressource demand\u00e9e ({0}) a \u00e9t\u00e9 interdit.
+http.404=La ressource demand\u00e9e ({0}) n''est pas disponible.
+http.405=La m\u00e9thode HTTP sp\u00e9cifi\u00e9e n''est pas autoris\u00e9e pour la ressource demand\u00e9e ({0}).
+http.406=La ressource identifi\u00e9e par cette requ\u00eate n''est capable de g\u00e9n\u00e9rer des r\u00e9ponses qu''avec des caract\u00e9ristiques incompatible avec la directive "accept" pr\u00e9sente dans l''ent\u00eate de requ\u00eate ({0}).
+http.407=Le client doit d''abord s''authentifier aupr\u00e8s du relais ({0}).
+http.408=Le client n''a pas produit de requ\u00eate pendant le temps d''attente du serveur ({0}).
+http.409=La requ\u00eate ne peut \u00eatre finalis\u00e9e suite \u00e0 un conflit li\u00e9 \u00e0 l''\u00e9tat de la ressource ({0}).
+http.410=La ressource demand\u00e9e ({0}) n''est pas disponible, et aucune addresse de rebond (forwarding) n''est connue.
+http.411=La requ\u00eate ne peut \u00eatre trait\u00e9e sans d\u00e9finition d''une taille de contenu (content length) ({0}).
+http.412=Une condition pr\u00e9alable demand\u00e9e n''est pas satisfaite pour cette requ\u00eate ({0}).
+http.413=L''entit\u00e9 de requ\u00eate est plus importante que ce que le serveur veut ou peut traiter.
+http.414=Le serveur a refus\u00e9 cette requ\u00eate car l''URI de requ\u00eate est trop longue ({0}).
+http.415=Le serveur a refus\u00e9 cette requ\u00eate car l''entit\u00e9 de requ\u00eate est dans un format non support\u00e9 par la ressource demand\u00e9e avec la m\u00e9thode sp\u00e9cifi\u00e9e ({0}).
+http.416=La plage d''octets demand\u00e9e (byte range) ne peut \u00eatre satisfaite ({0}).
+http.417=L''attente indiqu\u00e9e dans la directive "Expect" de l''ent\u00eate de requ\u00eate ({0}) ne peut \u00eatre satisfaite.
+http.422=Le serveur a compris le type de contenu (content type) ainsi que la syntaxe de la requ\u00eate mais a \u00e9t\u00e9 incapable de traiter les instructions contenues ({0}).
+http.423=La ressource source ou destination de la m\u00e9thode est verrouill\u00e9e ({0}).
+http.500=Le serveur a rencontr\u00e9 une erreur interne ({0}) qui l''a emp\u00each\u00e9 de satisfaire la requ\u00eate.
+http.501=Le serveur ne supporte pas la fonctionnalit\u00e9 demand\u00e9e pour satisfaire cette requ\u00eate ({0}).
+http.502=Le serveur a re\u00e7u une r\u00e9ponse invalide d''un serveur qu''il consultait en tant que relais ou passerelle ({0}).
+http.503=Le service demand\u00e9 ({0}) n''est pas disponible actuellement.
+http.504=Le serveur a re\u00e7u un d\u00e9passement de delai (timeout) d''un serveur amont qu''il consultait en tant que relais ou passerelle ({0}).
+http.505=Le serveur ne supporte pas la version demand\u00e9e du protocole HTTP ({0}).
+http.507=L''espace disponible est insuffisant pour enregistrer l''\u00e9tat de la ressource apr\u00e8s ex\u00e9cution de cette m\u00e9thode ({0}).
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/valves/LocalStrings_ja.properties b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/LocalStrings_ja.properties
new file mode 100644
index 0000000..533a2ee
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/LocalStrings_ja.properties
@@ -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.
+
+requestFilterValve.next=\u6b21\u306e\u30d0\u30eb\u30d6\u304c\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+requestFilterValve.syntax=\u30ea\u30af\u30a8\u30b9\u30c8\u30d5\u30a3\u30eb\u30bf\u30d1\u30bf\u30fc\u30f3 {0} \u306b\u69cb\u6587\u30a8\u30e9\u30fc\u304c\u3042\u308a\u307e\u3059
+jdbcAccessLogValve.exception=\u30a2\u30af\u30bb\u30b9\u30a8\u30f3\u30c8\u30ea\u306e\u633f\u5165\u3092\u5b9f\u884c\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+jdbcAccessLogValve.close=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u63a5\u7d9a\u3092\u30af\u30ed\u30fc\u30ba\u4e2d\u306e\u4f8b\u5916\u3067\u3059
+
+# Error report valve
+valveBase.noNext=\u8a2d\u5b9a\u30a8\u30e9\u30fc: \u6b21\u306e\u30d0\u30eb\u30d6\u304c\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+errorReportValve.statusHeader=HTTP\u30b9\u30c6\u30fc\u30bf\u30b9 {0} - {1}
+errorReportValve.exceptionReport=\u4f8b\u5916\u30ec\u30dd\u30fc\u30c8
+errorReportValve.statusReport=\u30b9\u30c6\u30fc\u30bf\u30b9\u30ec\u30dd\u30fc\u30c8
+errorReportValve.message=\u30e1\u30c3\u30bb\u30fc\u30b8
+errorReportValve.description=\u8aac\u660e
+errorReportValve.exception=\u4f8b\u5916
+errorReportValve.rootCause=\u539f\u56e0
+errorReportValve.note=\u6ce8\u610f
+errorReportValve.rootCauseInLogs=\u539f\u56e0\u306e\u3059\u3079\u3066\u306e\u30b9\u30bf\u30c3\u30af\u30c8\u30ec\u30fc\u30b9\u306f\u3001{0}\u306e\u30ed\u30b0\u306b\u8a18\u9332\u3055\u308c\u3066\u3044\u307e\u3059
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/valves/PersistentValve.java b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/PersistentValve.java
new file mode 100644
index 0000000..a1e4b8d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/PersistentValve.java
@@ -0,0 +1,214 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.valves;
+
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Manager;
+import org.apache.catalina.Session;
+import org.apache.catalina.Store;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.session.PersistentManager;
+
+
+/**
+ * Valve that implements per-request session persistence. It is intended to be
+ * used with non-sticky load-balancers.
+ * <p>
+ * <b>USAGE CONSTRAINT</b>: To work correctly it requires a  PersistentManager.
+ * <p>
+ * <b>USAGE CONSTRAINT</b>: To work correctly it assumes only one request exists
+ *                              per session at any one time.
+ *
+ * @author Jean-Frederic Clere
+ * @version $Id: PersistentValve.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+
+public class PersistentValve extends ValveBase {
+
+    //------------------------------------------------------ Constructor
+    public PersistentValve() {
+        super(false);
+    }
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The descriptive information related to this implementation.
+     */
+    private static final String info =
+        "org.apache.catalina.valves.PersistentValve/1.0";
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return descriptive information about this Valve implementation.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Select the appropriate child Context to process this request,
+     * based on the specified request URI.  If no matching Context can
+     * be found, return an appropriate HTTP error.
+     *
+     * @param request Request to be processed
+     * @param response Response to be produced
+     *
+     * @exception IOException if an input/output error occurred
+     * @exception ServletException if a servlet error occurred
+     */
+    @Override
+    public void invoke(Request request, Response response)
+        throws IOException, ServletException {
+
+        // Select the Context to be used for this Request
+        Context context = request.getContext();
+        if (context == null) {
+            response.sendError
+                (HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                 sm.getString("standardHost.noContext"));
+            return;
+        }
+
+        // Bind the context CL to the current thread
+        Thread.currentThread().setContextClassLoader
+            (context.getLoader().getClassLoader());
+
+        // Update the session last access time for our session (if any)
+        String sessionId = request.getRequestedSessionId();
+        Manager manager = context.getManager();
+        if (sessionId != null && manager != null) {
+            if (manager instanceof PersistentManager) {
+                Store store = ((PersistentManager) manager).getStore();
+                if (store != null) {
+                    Session session = null;
+                    try {
+                        session = store.load(sessionId);
+                    } catch (Exception e) {
+                        container.getLogger().error("deserializeError");
+                    }
+                    if (session != null) {
+                        if (!session.isValid() ||
+                            isSessionStale(session, System.currentTimeMillis())) {
+                            if (container.getLogger().isDebugEnabled())
+                                container.getLogger().debug("session swapped in is invalid or expired");
+                            session.expire();
+                            store.remove(sessionId);
+                        } else {
+                            session.setManager(manager);
+                            // session.setId(sessionId); Only if new ???
+                            manager.add(session);
+                            // ((StandardSession)session).activate();
+                            session.access();
+                            session.endAccess();
+                        }
+                    }
+                }
+            }
+        }
+        if (container.getLogger().isDebugEnabled())
+            container.getLogger().debug("sessionId: " + sessionId);
+
+        // Ask the next valve to process the request.
+        getNext().invoke(request, response);
+
+        // Read the sessionid after the response.
+        // HttpSession hsess = hreq.getSession(false);
+        Session hsess;
+        try {
+            hsess = request.getSessionInternal();
+        } catch (Exception ex) {
+            hsess = null;
+        }
+        String newsessionId = null;
+        if (hsess!=null)
+            newsessionId = hsess.getIdInternal();
+
+        if (container.getLogger().isDebugEnabled())
+            container.getLogger().debug("newsessionId: " + newsessionId);
+        if (newsessionId!=null) {
+            /* store the session in the store and remove it from the manager */
+            if (manager instanceof PersistentManager) {
+                Session session = manager.findSession(newsessionId);
+                Store store = ((PersistentManager) manager).getStore();
+                if (store != null && session!=null &&
+                    session.isValid() &&
+                    !isSessionStale(session, System.currentTimeMillis())) {
+                    // ((StandardSession)session).passivate();
+                    store.save(session);
+                    ((PersistentManager) manager).removeSuper(session);
+                    session.recycle();
+                } else {
+                    if (container.getLogger().isDebugEnabled())
+                        container.getLogger().debug("newsessionId store: " +
+                                store + " session: " + session + " valid: " +
+                                (session == null ? "N/A" : Boolean.toString(
+                                        session.isValid())) +
+                                " stale: " + isSessionStale(session,
+                                        System.currentTimeMillis()));
+
+                }
+            } else {
+                if (container.getLogger().isDebugEnabled())
+                    container.getLogger().debug("newsessionId Manager: " + manager);
+            }
+        }
+    }
+
+    /**
+     * Indicate whether the session has been idle for longer
+     * than its expiration date as of the supplied time.
+     *
+     * FIXME: Probably belongs in the Session class.
+     */
+    protected boolean isSessionStale(Session session, long timeNow) {
+ 
+        if (session != null) {
+            int maxInactiveInterval = session.getMaxInactiveInterval();
+            if (maxInactiveInterval >= 0) {
+                int timeIdle = // Truncate, do not round up
+                    (int) ((timeNow - session.getThisAccessedTime()) / 1000L);
+                if (timeIdle >= maxInactiveInterval)
+                    return true;
+            }
+        }
+ 
+        return false;
+ 
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/valves/RemoteAddrValve.java b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/RemoteAddrValve.java
new file mode 100644
index 0000000..d4985c1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/RemoteAddrValve.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.valves;
+
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+
+
+/**
+ * Concrete implementation of <code>RequestFilterValve</code> that filters
+ * based on the string representation of the remote client's IP address.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: RemoteAddrValve.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+
+public final class RemoteAddrValve
+    extends RequestFilterValve {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The descriptive information related to this implementation.
+     */
+    private static final String info =
+        "org.apache.catalina.valves.RemoteAddrValve/1.0";
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return descriptive information about this Valve implementation.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Extract the desired request property, and pass it (along with the
+     * specified request and response objects) to the protected
+     * <code>process()</code> method to perform the actual filtering.
+     * This method must be implemented by a concrete subclass.
+     *
+     * @param request The servlet request to be processed
+     * @param response The servlet response to be created
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    @Override
+    public void invoke(Request request, Response response)
+        throws IOException, ServletException {
+
+        process(request.getRequest().getRemoteAddr(), request, response);
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/valves/RemoteHostValve.java b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/RemoteHostValve.java
new file mode 100644
index 0000000..4ee1502
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/RemoteHostValve.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.valves;
+
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+
+
+/**
+ * Concrete implementation of <code>RequestFilterValve</code> that filters
+ * based on the remote client's host name.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: RemoteHostValve.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+
+public final class RemoteHostValve
+    extends RequestFilterValve {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The descriptive information related to this implementation.
+     */
+    private static final String info =
+        "org.apache.catalina.valves.RemoteHostValve/1.0";
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return descriptive information about this Valve implementation.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Extract the desired request property, and pass it (along with the
+     * specified request and response objects) to the protected
+     * <code>process()</code> method to perform the actual filtering.
+     * This method must be implemented by a concrete subclass.
+     *
+     * @param request The servlet request to be processed
+     * @param response The servlet response to be created
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    @Override
+    public void invoke(Request request, Response response)
+        throws IOException, ServletException {
+
+        process(request.getRequest().getRemoteHost(), request, response);
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/valves/RemoteIpValve.java b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/RemoteIpValve.java
new file mode 100644
index 0000000..bb4b4e7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/RemoteIpValve.java
@@ -0,0 +1,790 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.valves;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import javax.servlet.ServletException;
+
+import org.apache.catalina.AccessLog;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * <p>
+ * Tomcat port of <a href="http://httpd.apache.org/docs/trunk/mod/mod_remoteip.html">mod_remoteip</a>, this valve replaces the apparent
+ * client remote IP address and hostname for the request with the IP address list presented by a proxy or a load balancer via a request
+ * headers (e.g. "X-Forwarded-For").
+ * </p>
+ * <p>
+ * Another feature of this valve is to replace the apparent scheme (http/https) and server port with the scheme presented by a proxy or a
+ * load balancer via a request header (e.g. "X-Forwarded-Proto").
+ * </p>
+ * <p>
+ * This valve proceeds as follows:
+ * </p>
+ * <p>
+ * If the incoming <code>request.getRemoteAddr()</code> matches the valve's list of internal proxies :
+ * <ul>
+ * <li>Loop on the comma delimited list of IPs and hostnames passed by the preceding load balancer or proxy in the given request's Http
+ * header named <code>$remoteIpHeader</code> (default value <code>x-forwarded-for</code>). Values are processed in right-to-left order.</li>
+ * <li>For each ip/host of the list:
+ * <ul>
+ * <li>if it matches the internal proxies list, the ip/host is swallowed</li>
+ * <li>if it matches the trusted proxies list, the ip/host is added to the created proxies header</li>
+ * <li>otherwise, the ip/host is declared to be the remote ip and looping is stopped.</li>
+ * </ul>
+ * </li>
+ * <li>If the request http header named <code>$protocolHeader</code> (e.g. <code>x-forwarded-for</code>) equals to the value of
+ * <code>protocolHeaderHttpsValue</code> configuration parameter (default <code>https</code>) then <code>request.isSecure = true</code>,
+ * <code>request.scheme = https</code> and <code>request.serverPort = 443</code>. Note that 443 can be overwritten with the
+ * <code>$httpsServerPort</code> configuration parameter.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * <strong>Configuration parameters:</strong>
+ * <table border="1">
+ * <tr>
+ * <th>RemoteIpValve property</th>
+ * <th>Description</th>
+ * <th>Equivalent mod_remoteip directive</th>
+ * <th>Format</th>
+ * <th>Default Value</th>
+ * </tr>
+ * <tr>
+ * <td>remoteIpHeader</td>
+ * <td>Name of the Http Header read by this valve that holds the list of traversed IP addresses starting from the requesting client</td>
+ * <td>RemoteIPHeader</td>
+ * <td>Compliant http header name</td>
+ * <td>x-forwarded-for</td>
+ * </tr>
+ * <tr>
+ * <td>internalProxies</td>
+ * <td>Regular expression that matches the IP addresses of internal proxies.
+ * If they appear in the <code>remoteIpHeader</code> value, they will be
+ * trusted and will not appear
+ * in the <code>proxiesHeader</code> value</td>
+ * <td>RemoteIPInternalProxy</td>
+ * <td>Regular expression (in the syntax supported by
+ * {@link java.util.regex.Pattern java.util.regex})</td>
+ * <td>10\.\d{1,3}\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}|169\.254\.\d{1,3}\.\d{1,3}|127\.\d{1,3}\.\d{1,3}\.\d{1,3}<br/>
+ * By default, 10/8, 192.168/16, 169.254/16 and 127/8 are allowed ; 172.16/12 has not been enabled by default because it is complex to
+ * describe with regular expressions</td>
+ * </tr>
+ * </tr>
+ * <tr>
+ * <td>proxiesHeader</td>
+ * <td>Name of the http header created by this valve to hold the list of proxies that have been processed in the incoming
+ * <code>remoteIpHeader</code></td>
+ * <td>RemoteIPProxiesHeader</td>
+ * <td>Compliant http header name</td>
+ * <td>x-forwarded-by</td>
+ * </tr>
+ * <tr>
+ * <td>trustedProxies</td>
+ * <td>Regular expression that matches the IP addresses of trusted proxies.
+ * If they appear in the <code>remoteIpHeader</code> value, they will be
+ * trusted and will appear in the <code>proxiesHeader</code> value</td>
+ * <td>RemoteIPTrustedProxy</td>
+ * <td>Regular expression (in the syntax supported by
+ * {@link java.util.regex.Pattern java.util.regex})</td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>protocolHeader</td>
+ * <td>Name of the http header read by this valve that holds the flag that this request </td>
+ * <td>N/A</td>
+ * <td>Compliant http header name like <code>X-Forwarded-Proto</code>, <code>X-Forwarded-Ssl</code> or <code>Front-End-Https</code></td>
+ * <td><code>null</code></td>
+ * </tr>
+ * <tr>
+ * <td>protocolHeaderHttpsValue</td>
+ * <td>Value of the <code>protocolHeader</code> to indicate that it is an Https request</td>
+ * <td>N/A</td>
+ * <td>String like <code>https</code> or <code>ON</code></td>
+ * <td><code>https</code></td>
+ * </tr>
+ * <tr>
+ * <td>httpServerPort</td>
+ * <td>Value returned by {@link javax.servlet.ServletRequest#getServerPort()} when the <code>protocolHeader</code> indicates <code>http</code> protocol</td>
+ * <td>N/A</td>
+ * <td>integer</td>
+ * <td>80</td>
+ * </tr>
+ * <tr>
+ * <td>httpsServerPort</td>
+ * <td>Value returned by {@link javax.servlet.ServletRequest#getServerPort()} when the <code>protocolHeader</code> indicates <code>https</code> protocol</td>
+ * <td>N/A</td>
+ * <td>integer</td>
+ * <td>443</td>
+ * </tr>
+ * </table>
+ * </p>
+ * <p>
+ * <p>
+ * This Valve may be attached to any Container, depending on the granularity of the filtering you wish to perform.
+ * </p>
+ * <p>
+ * <strong>Regular expression vs. IP address blocks:</strong> <code>mod_remoteip</code> allows to use address blocks (e.g.
+ * <code>192.168/16</code>) to configure <code>RemoteIPInternalProxy</code> and <code>RemoteIPTrustedProxy</code> ; as Tomcat doesn't have a
+ * library similar to <a
+ * href="http://apr.apache.org/docs/apr/1.3/group__apr__network__io.html#gb74d21b8898b7c40bf7fd07ad3eb993d">apr_ipsubnet_test</a>,
+ * <code>RemoteIpValve</code> uses regular expression to configure <code>internalProxies</code> and <code>trustedProxies</code> in the same
+ * fashion as {@link RequestFilterValve} does.
+ * </p>
+ * <hr/>
+ * <p>
+ * <strong>Sample with internal proxies</strong>
+ * </p>
+ * <p>
+ * RemoteIpValve configuration:
+ * </p>
+ * <code><pre>
+ * &lt;Valve 
+ *   className="org.apache.catalina.valves.RemoteIpValve"
+ *   internalProxies="192\.168\.0\.10|192\.168\.0\.11"
+ *   remoteIpHeader="x-forwarded-for"
+ *   remoteIpProxiesHeader="x-forwarded-by"
+ *   protocolHeader="x-forwarded-proto"
+ *   /&gt;</pre></code>
+ * <p>
+ * Request values:
+ * <table border="1">
+ * <tr>
+ * <th>property</th>
+ * <th>Value Before RemoteIpValve</th>
+ * <th>Value After RemoteIpValve</th>
+ * </tr>
+ * <tr>
+ * <td>request.remoteAddr</td>
+ * <td>192.168.0.10</td>
+ * <td>140.211.11.130</td>
+ * </tr>
+ * <tr>
+ * <td>request.header['x-forwarded-for']</td>
+ * <td>140.211.11.130, 192.168.0.10</td>
+ * <td>null</td>
+ * </tr>
+ * <tr>
+ * <td>request.header['x-forwarded-by']</td>
+ * <td>null</td>
+ * <td>null</td>
+ * </tr>
+ * <tr>
+ * <td>request.header['x-forwarded-proto']</td>
+ * <td>https</td>
+ * <td>https</td>
+ * </tr>
+ * <tr>
+ * <td>request.scheme</td>
+ * <td>http</td>
+ * <td>https</td>
+ * </tr>
+ * <tr>
+ * <td>request.secure</td>
+ * <td>false</td>
+ * <td>true</td>
+ * </tr>
+ * <tr>
+ * <td>request.serverPort</td>
+ * <td>80</td>
+ * <td>443</td>
+ * </tr>
+ * </table>
+ * Note : <code>x-forwarded-by</code> header is null because only internal proxies as been traversed by the request.
+ * <code>x-forwarded-by</code> is null because all the proxies are trusted or internal.
+ * </p>
+ * <hr/>
+ * <p>
+ * <strong>Sample with trusted proxies</strong>
+ * </p>
+ * <p>
+ * RemoteIpValve configuration:
+ * </p>
+ * <code><pre>
+ * &lt;Valve 
+ *   className="org.apache.catalina.valves.RemoteIpValve"
+ *   internalProxies="192\.168\.0\.10|192\.168\.0\.11"
+ *   remoteIpHeader="x-forwarded-for"
+ *   remoteIpProxiesHeader="x-forwarded-by"
+ *   trustedProxies="proxy1|proxy2"
+ *   /&gt;</pre></code>
+ * <p>
+ * Request values:
+ * <table border="1">
+ * <tr>
+ * <th>property</th>
+ * <th>Value Before RemoteIpValve</th>
+ * <th>Value After RemoteIpValve</th>
+ * </tr>
+ * <tr>
+ * <td>request.remoteAddr</td>
+ * <td>192.168.0.10</td>
+ * <td>140.211.11.130</td>
+ * </tr>
+ * <tr>
+ * <td>request.header['x-forwarded-for']</td>
+ * <td>140.211.11.130, proxy1, proxy2</td>
+ * <td>null</td>
+ * </tr>
+ * <tr>
+ * <td>request.header['x-forwarded-by']</td>
+ * <td>null</td>
+ * <td>proxy1, proxy2</td>
+ * </tr>
+ * </table>
+ * Note : <code>proxy1</code> and <code>proxy2</code> are both trusted proxies that come in <code>x-forwarded-for</code> header, they both
+ * are migrated in <code>x-forwarded-by</code> header. <code>x-forwarded-by</code> is null because all the proxies are trusted or internal.
+ * </p>
+ * <hr/>
+ * <p>
+ * <strong>Sample with internal and trusted proxies</strong>
+ * </p>
+ * <p>
+ * RemoteIpValve configuration:
+ * </p>
+ * <code><pre>
+ * &lt;Valve 
+ *   className="org.apache.catalina.valves.RemoteIpValve"
+ *   internalProxies="192\.168\.0\.10|192\.168\.0\.11"
+ *   remoteIpHeader="x-forwarded-for"
+ *   remoteIpProxiesHeader="x-forwarded-by"
+ *   trustedProxies="proxy1|proxy2"
+ *   /&gt;</pre></code>
+ * <p>
+ * Request values:
+ * <table border="1">
+ * <tr>
+ * <th>property</th>
+ * <th>Value Before RemoteIpValve</th>
+ * <th>Value After RemoteIpValve</th>
+ * </tr>
+ * <tr>
+ * <td>request.remoteAddr</td>
+ * <td>192.168.0.10</td>
+ * <td>140.211.11.130</td>
+ * </tr>
+ * <tr>
+ * <td>request.header['x-forwarded-for']</td>
+ * <td>140.211.11.130, proxy1, proxy2, 192.168.0.10</td>
+ * <td>null</td>
+ * </tr>
+ * <tr>
+ * <td>request.header['x-forwarded-by']</td>
+ * <td>null</td>
+ * <td>proxy1, proxy2</td>
+ * </tr>
+ * </table>
+ * Note : <code>proxy1</code> and <code>proxy2</code> are both trusted proxies that come in <code>x-forwarded-for</code> header, they both
+ * are migrated in <code>x-forwarded-by</code> header. As <code>192.168.0.10</code> is an internal proxy, it does not appear in
+ * <code>x-forwarded-by</code>. <code>x-forwarded-by</code> is null because all the proxies are trusted or internal.
+ * </p>
+ * <hr/>
+ * <p>
+ * <strong>Sample with an untrusted proxy</strong>
+ * </p>
+ * <p>
+ * RemoteIpValve configuration:
+ * </p>
+ * <code><pre>
+ * &lt;Valve 
+ *   className="org.apache.catalina.valves.RemoteIpValve"
+ *   internalProxies="192\.168\.0\.10|192\.168\.0\.11"
+ *   remoteIpHeader="x-forwarded-for"
+ *   remoteIpProxiesHeader="x-forwarded-by"
+ *   trustedProxies="proxy1|proxy2"
+ *   /&gt;</pre></code>
+ * <p>
+ * Request values:
+ * <table border="1">
+ * <tr>
+ * <th>property</th>
+ * <th>Value Before RemoteIpValve</th>
+ * <th>Value After RemoteIpValve</th>
+ * </tr>
+ * <tr>
+ * <td>request.remoteAddr</td>
+ * <td>192.168.0.10</td>
+ * <td>untrusted-proxy</td>
+ * </tr>
+ * <tr>
+ * <td>request.header['x-forwarded-for']</td>
+ * <td>140.211.11.130, untrusted-proxy, proxy1</td>
+ * <td>140.211.11.130</td>
+ * </tr>
+ * <tr>
+ * <td>request.header['x-forwarded-by']</td>
+ * <td>null</td>
+ * <td>proxy1</td>
+ * </tr>
+ * </table>
+ * Note : <code>x-forwarded-by</code> holds the trusted proxy <code>proxy1</code>. <code>x-forwarded-by</code> holds
+ * <code>140.211.11.130</code> because <code>untrusted-proxy</code> is not trusted and thus, we can not trust that
+ * <code>untrusted-proxy</code> is the actual remote ip. <code>request.remoteAddr</code> is <code>untrusted-proxy</code> that is an IP
+ * verified by <code>proxy1</code>.
+ * </p>
+ */
+public class RemoteIpValve extends ValveBase {
+    
+    /**
+     * {@link Pattern} for a comma delimited string that support whitespace characters
+     */
+    private static final Pattern commaSeparatedValuesPattern = Pattern.compile("\\s*,\\s*");
+    
+    /**
+     * The descriptive information related to this implementation.
+     */
+    private static final String info = "org.apache.catalina.valves.RemoteIpValve/1.0";
+    
+    /**
+     * Logger
+     */
+    private static final Log log = LogFactory.getLog(RemoteIpValve.class);
+    
+    /**
+     * Convert a given comma delimited String into an array of String
+     * 
+     * @return array of String (non <code>null</code>)
+     */
+    protected static String[] commaDelimitedListToStringArray(String commaDelimitedStrings) {
+        return (commaDelimitedStrings == null || commaDelimitedStrings.length() == 0) ? new String[0] : commaSeparatedValuesPattern
+            .split(commaDelimitedStrings);
+    }
+    
+    /**
+     * Convert an array of strings in a comma delimited string
+     */
+    protected static String listToCommaDelimitedString(List<String> stringList) {
+        if (stringList == null) {
+            return "";
+        }
+        StringBuilder result = new StringBuilder();
+        for (Iterator<String> it = stringList.iterator(); it.hasNext();) {
+            Object element = it.next();
+            if (element != null) {
+                result.append(element);
+                if (it.hasNext()) {
+                    result.append(", ");
+                }
+            }
+        }
+        return result.toString();
+    }
+    
+    /**
+     * @see #setHttpServerPort(int)
+     */
+    private int httpServerPort = 80;
+    
+    /**
+     * @see #setHttpsServerPort(int)
+     */
+    private int httpsServerPort = 443;
+    
+    /**
+     * @see #setInternalProxies(String)
+     */
+    private Pattern internalProxies = Pattern.compile(
+            "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" +
+            "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" +
+            "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" +
+            "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}");
+    
+    /**
+     * @see #setProtocolHeader(String)
+     */
+    private String protocolHeader = null;
+    
+    /**
+     * @see #setProtocolHeaderHttpsValue(String)
+     */
+    private String protocolHeaderHttpsValue = "https";
+    
+    /**
+     * @see #setProxiesHeader(String)
+     */
+    private String proxiesHeader = "X-Forwarded-By";
+    
+    /**
+     * @see #setRemoteIpHeader(String)
+     */
+    private String remoteIpHeader = "X-Forwarded-For";
+
+    /**
+     * @see #setRequestAttributesEnabled(boolean)
+     */
+    private boolean requestAttributesEnabled = true;
+
+    /**
+     * @see RemoteIpValve#setTrustedProxies(String)
+     */
+    private Pattern trustedProxies = null;
+    
+    public int getHttpsServerPort() {
+        return httpsServerPort;
+    }
+    
+    public int getHttpServerPort() {
+        return httpServerPort;
+    }
+    
+    /**
+     * Return descriptive information about this Valve implementation.
+     */
+    @Override
+    public String getInfo() {
+        return info;
+    }
+    
+    /**
+     * @see #setInternalProxies(String)
+     * @return Regular expression that defines the internal proxies
+     */
+    public String getInternalProxies() {
+        if (internalProxies == null) {
+            return null;
+        }
+        return internalProxies.toString();
+    }
+    
+    /**
+     * @see #setProtocolHeader(String)
+     * @return the protocol header (e.g. "X-Forwarded-Proto")
+     */
+    public String getProtocolHeader() {
+        return protocolHeader;
+    }
+    
+    /**
+     * @see RemoteIpValve#setProtocolHeaderHttpsValue(String)
+     * @return the value of the protocol header for incoming https request (e.g. "https")
+     */
+    public String getProtocolHeaderHttpsValue() {
+        return protocolHeaderHttpsValue;
+    }
+    
+    /**
+     * @see #setProxiesHeader(String)
+     * @return the proxies header name (e.g. "X-Forwarded-By")
+     */
+    public String getProxiesHeader() {
+        return proxiesHeader;
+    }
+    
+    /**
+     * @see #setRemoteIpHeader(String)
+     * @return the remote IP header name (e.g. "X-Forwarded-For")
+     */
+    public String getRemoteIpHeader() {
+        return remoteIpHeader;
+    }
+
+    /**
+     * @see #setRequestAttributesEnabled(boolean)
+     * @return <code>true</code> if the attributes will be logged, otherwise
+     *         <code>false</code>
+     */
+    public boolean getRequestAttributesEnabled() {
+        return requestAttributesEnabled;
+    }
+
+    /**
+     * @see #setTrustedProxies(String)
+     * @return Regular expression that defines the trusted proxies
+     */
+    public String getTrustedProxies() {
+        if (trustedProxies == null) {
+            return null;
+        }
+        return trustedProxies.toString();
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void invoke(Request request, Response response) throws IOException, ServletException {
+        final String originalRemoteAddr = request.getRemoteAddr();
+        final String originalRemoteHost = request.getRemoteHost();
+        final String originalScheme = request.getScheme();
+        final boolean originalSecure = request.isSecure();
+        final int originalServerPort = request.getServerPort();
+        
+        if (internalProxies !=null &&
+                internalProxies.matcher(originalRemoteAddr).matches()) {
+            String remoteIp = null;
+            // In java 6, proxiesHeaderValue should be declared as a java.util.Deque
+            LinkedList<String> proxiesHeaderValue = new LinkedList<String>();
+            StringBuilder concatRemoteIpHeaderValue = new StringBuilder();
+            
+            for (Enumeration<String> e = request.getHeaders(remoteIpHeader); e.hasMoreElements();) {
+                if (concatRemoteIpHeaderValue.length() > 0) {
+                    concatRemoteIpHeaderValue.append(", ");
+                }
+
+                concatRemoteIpHeaderValue.append(e.nextElement());
+            }
+
+            String[] remoteIpHeaderValue = commaDelimitedListToStringArray(concatRemoteIpHeaderValue.toString());
+            int idx;
+            // loop on remoteIpHeaderValue to find the first trusted remote ip and to build the proxies chain
+            for (idx = remoteIpHeaderValue.length - 1; idx >= 0; idx--) {
+                String currentRemoteIp = remoteIpHeaderValue[idx];
+                remoteIp = currentRemoteIp;
+                if (internalProxies.matcher(currentRemoteIp).matches()) {
+                    // do nothing, internalProxies IPs are not appended to the
+                } else if (trustedProxies != null &&
+                        trustedProxies.matcher(currentRemoteIp).matches()) {
+                    proxiesHeaderValue.addFirst(currentRemoteIp);
+                } else {
+                    idx--; // decrement idx because break statement doesn't do it
+                    break;
+                }
+            }
+            // continue to loop on remoteIpHeaderValue to build the new value of the remoteIpHeader
+            LinkedList<String> newRemoteIpHeaderValue = new LinkedList<String>();
+            for (; idx >= 0; idx--) {
+                String currentRemoteIp = remoteIpHeaderValue[idx];
+                newRemoteIpHeaderValue.addFirst(currentRemoteIp);
+            }
+            if (remoteIp != null) {
+                
+                request.setRemoteAddr(remoteIp);
+                request.setRemoteHost(remoteIp);
+                
+                // use request.coyoteRequest.mimeHeaders.setValue(str).setString(str) because request.addHeader(str, str) is no-op in Tomcat
+                // 6.0
+                if (proxiesHeaderValue.size() == 0) {
+                    request.getCoyoteRequest().getMimeHeaders().removeHeader(proxiesHeader);
+                } else {
+                    String commaDelimitedListOfProxies = listToCommaDelimitedString(proxiesHeaderValue);
+                    request.getCoyoteRequest().getMimeHeaders().setValue(proxiesHeader).setString(commaDelimitedListOfProxies);
+                }
+                if (newRemoteIpHeaderValue.size() == 0) {
+                    request.getCoyoteRequest().getMimeHeaders().removeHeader(remoteIpHeader);
+                } else {
+                    String commaDelimitedRemoteIpHeaderValue = listToCommaDelimitedString(newRemoteIpHeaderValue);
+                    request.getCoyoteRequest().getMimeHeaders().setValue(remoteIpHeader).setString(commaDelimitedRemoteIpHeaderValue);
+                }
+            }
+            
+            if (protocolHeader != null) {
+                String protocolHeaderValue = request.getHeader(protocolHeader);
+                if (protocolHeaderValue == null) {
+                    // don't modify the secure,scheme and serverPort attributes
+                    // of the request
+                } else if (protocolHeaderHttpsValue.equalsIgnoreCase(protocolHeaderValue)) {
+                    request.setSecure(true);
+                    // use request.coyoteRequest.scheme instead of request.setScheme() because request.setScheme() is no-op in Tomcat 6.0
+                    request.getCoyoteRequest().scheme().setString("https");
+                    
+                    request.setServerPort(httpsServerPort);
+                } else {
+                    request.setSecure(false);
+                    // use request.coyoteRequest.scheme instead of request.setScheme() because request.setScheme() is no-op in Tomcat 6.0
+                    request.getCoyoteRequest().scheme().setString("http");
+                    
+                    request.setServerPort(httpServerPort);
+                }
+            }
+            
+            if (log.isDebugEnabled()) {
+                log.debug("Incoming request " + request.getRequestURI() + " with originalRemoteAddr '" + originalRemoteAddr
+                          + "', originalRemoteHost='" + originalRemoteHost + "', originalSecure='" + originalSecure + "', originalScheme='"
+                          + originalScheme + "' will be seen as newRemoteAddr='" + request.getRemoteAddr() + "', newRemoteHost='"
+                          + request.getRemoteHost() + "', newScheme='" + request.getScheme() + "', newSecure='" + request.isSecure() + "'");
+            }
+        } else {
+            if (log.isDebugEnabled()) {
+                log.debug("Skip RemoteIpValve for request " + request.getRequestURI() + " with originalRemoteAddr '"
+                        + request.getRemoteAddr() + "'");
+            }
+        }
+        if (requestAttributesEnabled) {
+            request.setAttribute(AccessLog.REMOTE_ADDR_ATTRIBUTE,
+                    request.getRemoteAddr());
+            request.setAttribute(AccessLog.REMOTE_HOST_ATTRIBUTE,
+                    request.getRemoteHost());
+            request.setAttribute(AccessLog.PROTOCOL_ATTRIBUTE,
+                    request.getProtocol());
+            request.setAttribute(AccessLog.SERVER_PORT_ATTRIBUTE,
+                    Integer.valueOf(request.getServerPort()));
+        }
+        try {
+            getNext().invoke(request, response);
+        } finally {
+            request.setRemoteAddr(originalRemoteAddr);
+            request.setRemoteHost(originalRemoteHost);
+            
+            request.setSecure(originalSecure);
+            
+            // use request.coyoteRequest.scheme instead of request.setScheme() because request.setScheme() is no-op in Tomcat 6.0
+            request.getCoyoteRequest().scheme().setString(originalScheme);
+            
+            request.setServerPort(originalServerPort);
+        }
+    }
+    
+    /**
+     * <p>
+     * Server Port value if the {@link #protocolHeader} is not <code>null</code> and does not indicate HTTP
+     * </p>
+     * <p>
+     * Default value : 80
+     * </p>
+     */
+    public void setHttpServerPort(int httpServerPort) {
+        this.httpServerPort = httpServerPort;
+    }
+    
+    /**
+     * <p>
+     * Server Port value if the {@link #protocolHeader} indicates HTTPS
+     * </p>
+     * <p>
+     * Default value : 443
+     * </p>
+     */
+    public void setHttpsServerPort(int httpsServerPort) {
+        this.httpsServerPort = httpsServerPort;
+    }
+    
+    /**
+     * <p>
+     * Regular expression that defines the internal proxies.
+     * </p>
+     * <p>
+     * Default value : 10\.\d{1,3}\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}|169\.254.\d{1,3}.\d{1,3}|127\.\d{1,3}\.\d{1,3}\.\d{1,3}
+     * </p>
+     */
+    public void setInternalProxies(String internalProxies) {
+        if (internalProxies == null || internalProxies.length() == 0) {
+            this.internalProxies = null;
+        } else {
+            this.internalProxies = Pattern.compile(internalProxies);
+        }
+    }
+    
+    /**
+     * <p>
+     * Header that holds the incoming protocol, usally named <code>X-Forwarded-Proto</code>. If <code>null</code>, request.scheme and
+     * request.secure will not be modified.
+     * </p>
+     * <p>
+     * Default value : <code>null</code>
+     * </p>
+     */
+    public void setProtocolHeader(String protocolHeader) {
+        this.protocolHeader = protocolHeader;
+    }
+    
+    /**
+     * <p>
+     * Case insensitive value of the protocol header to indicate that the incoming http request uses SSL.
+     * </p>
+     * <p>
+     * Default value : <code>https</code>
+     * </p>
+     */
+    public void setProtocolHeaderHttpsValue(String protocolHeaderHttpsValue) {
+        this.protocolHeaderHttpsValue = protocolHeaderHttpsValue;
+    }
+    
+    /**
+     * <p>
+     * The proxiesHeader directive specifies a header into which mod_remoteip will collect a list of all of the intermediate client IP
+     * addresses trusted to resolve the actual remote IP. Note that intermediate RemoteIPTrustedProxy addresses are recorded in this header,
+     * while any intermediate RemoteIPInternalProxy addresses are discarded.
+     * </p>
+     * <p>
+     * Name of the http header that holds the list of trusted proxies that has been traversed by the http request.
+     * </p>
+     * <p>
+     * The value of this header can be comma delimited.
+     * </p>
+     * <p>
+     * Default value : <code>X-Forwarded-By</code>
+     * </p>
+     */
+    public void setProxiesHeader(String proxiesHeader) {
+        this.proxiesHeader = proxiesHeader;
+    }
+    
+    /**
+     * <p>
+     * Name of the http header from which the remote ip is extracted.
+     * </p>
+     * <p>
+     * The value of this header can be comma delimited.
+     * </p>
+     * <p>
+     * Default value : <code>X-Forwarded-For</code>
+     * </p>
+     * 
+     * @param remoteIpHeader
+     */
+    public void setRemoteIpHeader(String remoteIpHeader) {
+        this.remoteIpHeader = remoteIpHeader;
+    }
+    
+    /**
+     * Should this valve set request attributes for IP address, Hostname,
+     * protocol and port used for the request? This are typically used in
+     * conjunction with the {@link AccessLog} which will otherwise log the
+     * original values. Default is <code>true</code>.
+     * 
+     * The attributes set are:
+     * <ul>
+     * <li>org.apache.catalina.RemoteAddr</li>
+     * <li>org.apache.catalina.RemoteHost</li>
+     * <li>org.apache.catalina.Protocol</li>
+     * <li>org.apache.catalina.ServerPost</li>
+     * </ul>
+     * 
+     * @param requestAttributesEnabled  <code>true</code> causes the attributes
+     *                                  to be set, <code>false</code> disables
+     *                                  the setting of the attributes. 
+     */
+    public void setRequestAttributesEnabled(boolean requestAttributesEnabled) {
+        this.requestAttributesEnabled = requestAttributesEnabled;
+    }
+
+    /**
+     * <p>
+     * Regular expression defining proxies that are trusted when they appear in
+     * the {@link #remoteIpHeader} header.
+     * </p>
+     * <p>
+     * Default value : empty list, no external proxy is trusted.
+     * </p>
+     */
+    public void setTrustedProxies(String trustedProxies) {
+        if (trustedProxies == null || trustedProxies.length() == 0) {
+            this.trustedProxies = null;
+        } else {
+            this.trustedProxies = Pattern.compile(trustedProxies);
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/valves/RequestFilterValve.java b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/RequestFilterValve.java
new file mode 100644
index 0000000..e389ca4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/RequestFilterValve.java
@@ -0,0 +1,224 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.valves;
+
+
+import java.io.IOException;
+import java.util.regex.Pattern;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+
+/**
+ * Implementation of a Valve that performs filtering based on comparing the
+ * appropriate request property (selected based on which subclass you choose
+ * to configure into your Container's pipeline) against the regular expressions
+ * configured for this Valve.
+ * <p>
+ * This valve is configured by setting the <code>allow</code> and/or
+ * <code>deny</code> properties to a regular expressions (in the syntax
+ * supported by {@link Pattern}) to which the appropriate request property will
+ * be compared. Evaluation proceeds as follows:
+ * <ul>
+ * <li>The subclass extracts the request property to be filtered, and
+ *     calls the common <code>process()</code> method.
+ * <li>If there is a deny expression configured, the property will be compared
+ *     to the expression. If a match is found, this request will be rejected
+ *     with a "Forbidden" HTTP response.</li>
+ * <li>If there is a allow expression configured, the property will be compared
+ *     to each such expression.  If a match is found, this request will be
+ *     allowed to pass through to the next Valve in the current pipeline.</li>
+ * <li>If a deny expression was specified but no allow expression, allow this
+ *     request to pass through (because none of the deny expressions matched
+ *     it).
+ * <li>The request will be rejected with a "Forbidden" HTTP response.</li>
+ * </ul>
+ * <p>
+ * This Valve may be attached to any Container, depending on the granularity
+ * of the filtering you wish to perform.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: RequestFilterValve.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+
+public abstract class RequestFilterValve
+    extends ValveBase {
+
+    //------------------------------------------------------ Constructor
+    public RequestFilterValve() {
+        super(true);
+    }
+
+    // ----------------------------------------------------- Class Variables
+
+
+    /**
+     * The descriptive information related to this implementation.
+     */
+    private static final String info =
+        "org.apache.catalina.valves.RequestFilterValve/1.0";
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The regular expression used to test for allowed requests.
+     */
+    protected Pattern allow = null;
+
+
+    /**
+     * The regular expression used to test for denied requests.
+     */
+    protected Pattern deny = null;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the regular expression used to test for allowed requests for this
+     * Valve, if any; otherwise, return <code>null</code>.
+     */
+    public String getAllow() {
+        if (allow == null) {
+            return null;
+        }
+        return allow.toString();
+    }
+
+
+    /**
+     * Set the regular expression used to test for allowed requests for this
+     * Valve, if any.
+     *
+     * @param allow The new allow expression
+     */
+    public void setAllow(String allow) {
+        if (allow == null || allow.length() == 0) {
+            this.allow = null;
+        } else {
+            this.allow = Pattern.compile(allow);
+        }
+    }
+
+
+    /**
+     * Return the regular expression used to test for denied requests for this
+     * Valve, if any; otherwise, return <code>null</code>.
+     */
+    public String getDeny() {
+        if (deny == null) {
+            return null;
+        }
+        return deny.toString();
+    }
+
+
+    /**
+     * Set the regular expression used to test for denied requests for this
+     * Valve, if any.
+     *
+     * @param deny The new deny expression
+     */
+    public void setDeny(String deny) {
+        if (deny == null || deny.length() == 0) {
+            this.deny = null;
+        } else {
+            this.deny = Pattern.compile(deny);
+        }
+    }
+
+
+    /**
+     * Return descriptive information about this Valve implementation.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Extract the desired request property, and pass it (along with the
+     * specified request and response objects) to the protected
+     * <code>process()</code> method to perform the actual filtering.
+     * This method must be implemented by a concrete subclass.
+     *
+     * @param request The servlet request to be processed
+     * @param response The servlet response to be created
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    @Override
+    public abstract void invoke(Request request, Response response)
+        throws IOException, ServletException;
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Perform the filtering that has been configured for this Valve, matching
+     * against the specified request property.
+     *
+     * @param property The request property on which to filter
+     * @param request The servlet request to be processed
+     * @param response The servlet response to be processed
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    protected void process(String property,
+                           Request request, Response response)
+        throws IOException, ServletException {
+
+        // Check the deny patterns, if any
+        if (deny != null && deny.matcher(property).matches()) {
+            response.sendError(HttpServletResponse.SC_FORBIDDEN);
+            return;
+        }
+
+        // Check the allow patterns, if any
+        if (allow != null && allow.matcher(property).matches()) {
+            getNext().invoke(request, response);
+            return;
+        }
+
+        // Allow if denies specified but not allows
+        if (deny != null && allow == null) {
+            getNext().invoke(request, response);
+            return;
+        }
+
+        // Deny this request
+        response.sendError(HttpServletResponse.SC_FORBIDDEN);
+
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/valves/SSLValve.java b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/SSLValve.java
new file mode 100644
index 0000000..2ad9be0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/SSLValve.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.catalina.valves;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.NoSuchProviderException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+import javax.servlet.ServletException;
+
+import org.apache.catalina.Globals;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * When using mod_proxy_http, the client SSL information is not included in the
+ * protocol (unlike mod_jk and mod_proxy_ajp). To make the client SSL
+ * information available to Tomcat, some additional configuration is required.
+ * In httpd, mod_headers is used to add the SSL information as HTTP headers. In
+ * Tomcat, this valve is used to read the information from the HTTP headers and
+ * insert it into the request.<p>
+ * 
+ * <b>Note: Ensure that the headers are always set by httpd for all requests to
+ * prevent a client spoofing SSL information by sending fake headers. </b><p>
+ * 
+ * In httpd.conf add the following:
+ * <pre>
+ * &lt;IfModule ssl_module&gt;
+ *   RequestHeader set SSL_CLIENT_CERT "%{SSL_CLIENT_CERT}s"
+ *   RequestHeader set SSL_CIPHER "%{SSL_CIPHER}s"
+ *   RequestHeader set SSL_SESSION_ID "%{SSL_SESSION_ID}s"
+ *   RequestHeader set SSL_CIPHER_USEKEYSIZE "%{SSL_CIPHER_USEKEYSIZE}s"
+ * &lt;/IfModule&gt;
+ * </pre>
+ * 
+ * In server.xml, configure this valve under the Engine element in server.xml:
+ * <pre>
+ * &lt;Engine ...&gt;
+ *   &lt;Valve className="org.apache.catalina.valves.SSLValve" /&gt;
+ *   &lt;Host ... /&gt;
+ * &lt;/Engine&gt;
+ * </pre>
+ */
+public class SSLValve extends ValveBase {
+    
+    private static final Log log = LogFactory.getLog(SSLValve.class);
+
+    
+    //------------------------------------------------------ Constructor
+    public SSLValve() {
+        super(true);
+    }
+
+
+
+    public String mygetHeader(Request request, String header) {
+        String strcert0 = request.getHeader(header);
+        if (strcert0 == null)
+            return null;
+        /* mod_header writes "(null)" when the ssl variable is no filled */
+        if ("(null)".equals(strcert0))
+            return null;
+        return strcert0;
+    } 
+    @Override
+    public void invoke(Request request, Response response)
+        throws IOException, ServletException {
+
+        /* mod_header converts the '\n' into ' ' so we have to rebuild the client certificate */
+        String strcert0 = mygetHeader(request, "ssl_client_cert");
+        if (strcert0 != null && strcert0.length()>28) {
+            String strcert1 = strcert0.replace(' ', '\n');
+            String strcert2 = strcert1.substring(28, strcert1.length()-26);
+            String strcert3 = "-----BEGIN CERTIFICATE-----\n";
+            String strcert4 = strcert3.concat(strcert2);
+            String strcerts = strcert4.concat("\n-----END CERTIFICATE-----\n");
+            // ByteArrayInputStream bais = new ByteArrayInputStream(strcerts.getBytes("UTF-8"));
+            ByteArrayInputStream bais = new ByteArrayInputStream(strcerts.getBytes());
+            X509Certificate jsseCerts[] = null;
+            String providerName = (String) request.getConnector().getProperty(
+                    "clientCertProvider");
+            try {
+                CertificateFactory cf;
+                if (providerName == null) {
+                    cf = CertificateFactory.getInstance("X.509");    
+                } else {
+                    cf = CertificateFactory.getInstance("X.509", providerName);
+                }
+                X509Certificate cert = (X509Certificate) cf.generateCertificate(bais);
+                jsseCerts = new X509Certificate[1];
+                jsseCerts[0] = cert;
+            } catch (java.security.cert.CertificateException e) {
+                log.warn(sm.getString("sslValve.certError", strcerts), e);
+            } catch (NoSuchProviderException e) {
+                log.error(sm.getString(
+                        "sslValve.invalidProvider", providerName), e);
+            }
+            request.setAttribute(Globals.CERTIFICATES_ATTR, jsseCerts);
+        }
+        strcert0 = mygetHeader(request, "ssl_cipher");
+        if (strcert0 != null) {
+            request.setAttribute(Globals.CIPHER_SUITE_ATTR, strcert0);
+        }
+        strcert0 = mygetHeader(request, "ssl_session_id");
+        if (strcert0 != null) {
+            request.setAttribute(Globals.SSL_SESSION_ID_ATTR, strcert0);
+        }
+        strcert0 = mygetHeader(request, "ssl_cipher_usekeysize");
+        if (strcert0 != null) {
+            request.setAttribute(Globals.KEY_SIZE_ATTR, strcert0);
+        }
+        getNext().invoke(request, response);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/valves/SemaphoreValve.java b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/SemaphoreValve.java
new file mode 100644
index 0000000..4351d48
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/SemaphoreValve.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.valves;
+
+import java.io.IOException;
+import java.util.concurrent.Semaphore;
+
+import javax.servlet.ServletException;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+
+
+/**
+ * <p>Implementation of a Valve that limits concurrency.</p>
+ *
+ * <p>This Valve may be attached to any Container, depending on the granularity
+ * of the concurrency control you wish to perform.</p>
+ *
+ * @author Remy Maucherat
+ * @version $Id: SemaphoreValve.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+
+public class SemaphoreValve extends ValveBase {
+
+    //------------------------------------------------------ Constructor
+    public SemaphoreValve() {
+        super(false); //TODO - is this async aware
+    }
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The descriptive information related to this implementation.
+     */
+    private static final String info =
+        "org.apache.catalina.valves.SemaphoreValve/1.0";
+
+
+    /**
+     * Semaphore.
+     */
+    protected Semaphore semaphore = null;
+    
+
+    // ------------------------------------------------------------- Properties
+
+    
+    /**
+     * Concurrency level of the semaphore.
+     */
+    protected int concurrency = 10;
+    public int getConcurrency() { return concurrency; }
+    public void setConcurrency(int concurrency) { this.concurrency = concurrency; }
+    
+
+    /**
+     * Fairness of the semaphore.
+     */
+    protected boolean fairness = false;
+    public boolean getFairness() { return fairness; }
+    public void setFairness(boolean fairness) { this.fairness = fairness; }
+    
+
+    /**
+     * Block until a permit is available.
+     */
+    protected boolean block = true;
+    public boolean getBlock() { return block; }
+    public void setBlock(boolean block) { this.block = block; }
+    
+
+    /**
+     * Block interruptibly until a permit is available.
+     */
+    protected boolean interruptible = false;
+    public boolean getInterruptible() { return interruptible; }
+    public void setInterruptible(boolean interruptible) { this.interruptible = interruptible; }
+    
+
+    /**
+     * 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 {
+        
+        semaphore = new Semaphore(concurrency, fairness);
+
+        setState(LifecycleState.STARTING);
+    }
+
+
+    /**
+     * 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 {
+
+        setState(LifecycleState.STOPPING);
+
+        semaphore = null;
+    }
+
+    
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return descriptive information about this Valve implementation.
+     */
+    @Override
+    public String getInfo() {
+        return (info);
+    }
+
+
+    /**
+     * Do concurrency control on the request using the semaphore.
+     *
+     * @param request The servlet request to be processed
+     * @param response The servlet response to be created
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    @Override
+    public void invoke(Request request, Response response)
+        throws IOException, ServletException {
+
+        if (controlConcurrency(request, response)) {
+            boolean shouldRelease = true;
+            try {
+                if (block) {
+                    if (interruptible) {
+                        try {
+                            semaphore.acquire();
+                        } catch (InterruptedException e) {
+                            shouldRelease = false;
+                            permitDenied(request, response);
+                            return;
+                        }  
+                    } else {
+                        semaphore.acquireUninterruptibly();
+                    }
+                } else {
+                    if (!semaphore.tryAcquire()) {
+                        shouldRelease = false;
+                        permitDenied(request, response);
+                        return;
+                    }
+                }
+                getNext().invoke(request, response);
+            } finally {
+                if (shouldRelease) {
+                    semaphore.release();
+                }
+            }
+        } else {
+            getNext().invoke(request, response);
+        }
+
+    }
+
+    
+    /**
+     * Subclass friendly method to add conditions.
+     */
+    public boolean controlConcurrency(Request request, Response response) {
+        return true;
+    }
+    
+
+    /**
+     * Subclass friendly method to add error handling when a permit isn't
+     * granted.
+     * @throws IOException
+     * @throws ServletException
+     */
+    public void permitDenied(Request request, Response response)
+        throws IOException, ServletException {
+        // NO-OP by default
+    }
+    
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/valves/ValveBase.java b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/ValveBase.java
new file mode 100644
index 0000000..bf814cb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/ValveBase.java
@@ -0,0 +1,334 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.valves;
+
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+
+import org.apache.catalina.Contained;
+import org.apache.catalina.Container;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.Pipeline;
+import org.apache.catalina.Valve;
+import org.apache.catalina.comet.CometEvent;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.mbeans.MBeanUtils;
+import org.apache.catalina.util.LifecycleMBeanBase;
+import org.apache.juli.logging.Log;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Convenience base class for implementations of the <b>Valve</b> interface.
+ * A subclass <strong>MUST</strong> implement an <code>invoke()</code>
+ * method to provide the required functionality, and <strong>MAY</strong>
+ * implement the <code>Lifecycle</code> interface to provide configuration
+ * management and lifecycle support.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: ValveBase.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+
+public abstract class ValveBase extends LifecycleMBeanBase
+    implements Contained, Valve {
+
+    //------------------------------------------------------ Constructor
+    
+    public ValveBase() {
+        this(false);
+    }
+    
+    public ValveBase(boolean asyncSupported) {
+        this.asyncSupported = asyncSupported;
+    }
+
+    //------------------------------------------------------ Instance Variables
+    /**
+     * Does this valve support async reporting
+     */
+    protected boolean asyncSupported;
+    
+    /**
+     * The Container whose pipeline this Valve is a component of.
+     */
+    protected Container container = null;
+
+
+    /**
+     * Container log
+     */
+    protected Log containerLog = null;
+
+
+    /**
+     * Descriptive information about this Valve implementation.  This value
+     * should be overridden by subclasses.
+     */
+    protected static final String info =
+        "org.apache.catalina.core.ValveBase/1.0";
+
+
+    /**
+     * The next Valve in the pipeline this Valve is a component of.
+     */
+    protected Valve next = null;
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    //-------------------------------------------------------------- Properties
+
+    
+    /**
+     * Return the Container with which this Valve is associated, if any.
+     */
+    @Override
+    public Container getContainer() {
+
+        return (container);
+
+    }
+
+
+    @Override
+    public boolean isAsyncSupported() {
+        return asyncSupported;
+    }
+
+
+    public void setAsyncSupported(boolean asyncSupported) {
+        this.asyncSupported = asyncSupported;
+    }
+
+
+    /**
+     * Set the Container with which this Valve is associated, if any.
+     *
+     * @param container The new associated container
+     */
+    @Override
+    public void setContainer(Container container) {
+
+        this.container = container;
+
+    }
+
+
+    /**
+     * Return descriptive information about this Valve implementation.
+     */
+    @Override
+    public String getInfo() {
+
+        return (info);
+
+    }
+
+
+    /**
+     * Return the next Valve in this pipeline, or <code>null</code> if this
+     * is the last Valve in the pipeline.
+     */
+    @Override
+    public Valve getNext() {
+
+        return (next);
+
+    }
+
+
+    /**
+     * Set the Valve that follows this one in the pipeline it is part of.
+     *
+     * @param valve The new next valve
+     */
+    @Override
+    public void setNext(Valve valve) {
+
+        this.next = valve;
+
+    }
+
+
+    //---------------------------------------------------------- Public Methods
+
+
+    /**
+     * 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() {
+        // NOOP by default
+    }
+
+
+    /**
+     * The implementation-specific logic represented by this Valve.  See the
+     * Valve description for the normal design patterns for this method.
+     * <p>
+     * This method <strong>MUST</strong> be provided by a subclass.
+     *
+     * @param request The servlet request to be processed
+     * @param response The servlet response to be created
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    @Override
+    public abstract void invoke(Request request, Response response)
+        throws IOException, ServletException;
+
+
+    /**
+     * Process a Comet event. This method will rarely need to be provided by
+     * a subclass, unless it needs to reassociate a particular object with 
+     * the thread that is processing the request.
+     *
+     * @param request The servlet request to be processed
+     * @param response The servlet response to be created
+     *
+     * @exception IOException if an input/output error occurs, or is thrown
+     *  by a subsequently invoked Valve, Filter, or Servlet
+     * @exception ServletException if a servlet error occurs, or is thrown
+     *  by a subsequently invoked Valve, Filter, or Servlet
+     */
+    @Override
+    public void event(Request request, Response response, CometEvent event)
+        throws IOException, ServletException {
+        // Perform the request
+        getNext().event(request, response, event);
+    }
+
+
+    @Override
+    protected void initInternal() throws LifecycleException {
+        super.initInternal();
+        
+        containerLog = getContainer().getLogger();
+    }
+    
+    
+    /**
+     * 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 {
+        
+        setState(LifecycleState.STARTING);
+    }
+
+
+    /**
+     * 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 {
+
+        setState(LifecycleState.STOPPING);
+    }
+    
+    
+    /**
+     * Return a String rendering of this object.
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(this.getClass().getName());
+        sb.append('[');
+        if (container == null) {
+            sb.append("Container is null");
+        } else {
+            sb.append(container.getName());
+        }
+        sb.append(']');
+        return sb.toString();
+    }
+
+
+    // -------------------- JMX and Registration  --------------------
+    @Override
+    public String getObjectNameKeyProperties() {
+        StringBuilder name = new StringBuilder("type=Valve");
+        
+        Container container = getContainer();
+
+        name.append(MBeanUtils.getContainerKeyProperties(container));
+        
+        int seq = 0;
+        
+        // Pipeline may not be present in unit testing
+        Pipeline p = container.getPipeline();
+        if (p != null) {
+            for (Valve valve : p.getValves()) {
+                // Skip null valves
+                if (valve == null) {
+                    continue;
+                }
+                // Only compare valves in pipeline until we find this valve
+                if (valve == this) {
+                    break;
+                }
+                if (valve.getClass() == this.getClass()) {
+                    // Duplicate valve earlier in pipeline
+                    // increment sequence number
+                    seq ++;
+                }
+            }
+        }
+        
+        if (seq > 0) {
+            name.append(",seq=");
+            name.append(seq);
+        }
+
+        String className = this.getClass().getName();
+        int period = className.lastIndexOf('.');
+        if (period >= 0) {
+            className = className.substring(period + 1);
+        }
+        name.append(",name=");
+        name.append(className);
+        
+        return name.toString();
+    }
+
+    @Override
+    public String getDomainInternal() {
+        return MBeanUtils.getDomain(getContainer());
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/valves/mbeans-descriptors.xml b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/mbeans-descriptors.xml
new file mode 100644
index 0000000..562e859
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/mbeans-descriptors.xml
@@ -0,0 +1,393 @@
+<?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="AccessLogValve"
+         description="Valve that generates a web server access log"
+         domain="Catalina"
+         group="Valve"
+         type="org.apache.catalina.valves.AccessLogValve">
+         
+    <attribute name="asyncSupported"
+               description="Does this valve support async reporting."
+               is="true"
+               type="boolean"/>
+
+    <attribute name="buffered"
+               description="Flag to buffering."
+               is="true"
+               type="boolean"/>
+
+    <attribute name="checkExists"
+               description="Check for file existence before logging."
+               is="true"
+               type="boolean"/>
+
+    <attribute name="className"
+               description="Fully qualified class name of the managed object"
+               type="java.lang.String"
+               writeable="false"/>
+
+    <attribute name="condition"
+               description="The value to look for conditional logging."
+               type="java.lang.String"/>
+               
+    <attribute name="directory"
+               description="The directory in which log files are created"
+               type="java.lang.String"/>
+               
+    <attribute name="enabled"
+               description="Enable Access Logging"
+               is="false"
+               type="boolean"/>
+
+    <attribute name="fileDateFormat"
+               description="The format for the date date based log rotation."
+               type="java.lang.String"/>
+               
+    <attribute name="info"
+               description="Information about this implementation"
+               type="java.lang.String"
+               writeable="false"/>
+
+    <attribute   name="pattern"
+               description="The pattern used to format our access log lines"
+               type="java.lang.String"/>
+
+    <attribute name="prefix"
+               description="The prefix that is added to log file filenames"
+               type="java.lang.String"/>
+
+    <attribute name="resolveHosts"
+               description="Resolve hosts"
+               is="true"
+               type="boolean"/>
+
+    <attribute name="rotatable"
+               description="Flag to indicate automatic log rotation."
+               is="true"
+               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="suffix"
+               description="The suffix that is added to log file filenames"
+               type="java.lang.String"/>
+    
+    <operation name="rotate"
+               description="Move the existing log file to a new name"
+               impact="ACTION"
+               returnType="boolean">
+      <parameter name="newFileName"
+                 description="File name to move the log file to."
+                 type="java.lang.String"/>
+    </operation>
+ 
+  </mbean>
+
+  <mbean name="ErrorReportValve"
+         description="Implementation of a Valve that outputs HTML error pages"
+         domain="Catalina"
+         group="Valve"
+         type="org.apache.catalina.valves.ErrorReportValve">
+
+    <attribute name="asyncSupported"
+               description="Does this valve support async reporting."
+               is="true"
+               type="boolean"/>
+
+    <attribute name="className"
+               description="Fully qualified class name of the managed object"
+               type="java.lang.String"
+               writeable="false"/>
+
+    <attribute name="info"
+               description="Information about this implementation"
+               type="java.lang.String"
+               writeable="false"/>
+
+    <attribute name="stateName"
+               description="The name of the LifecycleState that this component is currently in"
+               type="java.lang.String"
+               writeable="false"/>
+
+  </mbean>
+
+  <mbean name="ExtendedAccessLogValve"
+         description="Valve that generates a web server access log"
+         domain="Catalina"
+         group="Valve"
+         type="org.apache.catalina.valves.ExtendedAccessLogValve">
+
+    <attribute name="asyncSupported"
+               description="Does this valve support async reporting."
+               is="true"
+               type="boolean"/>
+
+    <attribute name="buffered"
+               description="Flag to buffering."
+               is="true"
+               type="boolean"/>
+
+    <attribute name="checkExists"
+               description="Check for file existence before logging."
+               is="true"
+               type="boolean"/>
+
+    <attribute name="className"
+               description="Fully qualified class name of the managed object"
+               type="java.lang.String"
+               writeable="false"/>
+
+    <attribute name="condition"
+               description="The value to look for conditional logging."
+               type="java.lang.String"/>
+               
+    <attribute name="directory"
+               description="The directory in which log files are created"
+               type="java.lang.String"/>
+               
+     <attribute name="enabled"
+               description="Enable Access Logging"
+               is="false"
+               type="boolean"/>
+
+    <attribute name="fileDateFormat"
+               description="The format for the date date based log rotation."
+               type="java.lang.String"/>
+               
+    <attribute name="info"
+               description="Information about this implementation"
+               type="java.lang.String"
+               writeable="false"/>
+
+    <attribute   name="pattern"
+               description="The pattern used to format our access log lines"
+               type="java.lang.String"/>
+
+    <attribute name="prefix"
+               description="The prefix that is added to log file filenames"
+               type="java.lang.String"/>
+
+    <attribute name="resolveHosts"
+               description="Resolve hosts"
+               is="true"
+               type="boolean"/>
+
+    <attribute name="rotatable"
+               description="Flag to indicate automatic log rotation."
+               is="true"
+               type="boolean"/>
+
+    <attribute name="suffix"
+               description="The suffix that is added to log file filenames"
+               type="java.lang.String"/>
+
+    <operation name="rotate"
+               description="Move the existing log file to a new name"
+               impact="ACTION"
+               returnType="boolean">
+      <parameter name="newFileName"
+                 description="File name to move the log file to."
+                 type="java.lang.String"/>
+    </operation>
+
+  </mbean>
+
+  <mbean name="SemaphoreValve"
+         description="Valve that does concurrency control"
+         domain="Catalina"
+         group="Valve"
+         type="org.apache.catalina.valves.SemaphoreValve">
+         
+    <attribute name="asyncSupported"
+               description="Does this valve support async reporting."
+               is="true"
+               type="boolean"/>
+               
+    <attribute name="block"
+               description="Should this be blocked until a permit is available?"
+               type="boolean"/>
+
+    <attribute name="className"
+               description="Fully qualified class name of the managed object"
+               type="java.lang.String"
+               writeable="false"/>
+
+    <attribute name="concurrency"
+               description="Desired concurrency level"
+               type="int"/>
+
+    <attribute name="fairness"
+               description="Use a fair semaphore"
+               type="boolean"/>
+               
+    <attribute name="info"
+               description="Information about this implementation"
+               type="java.lang.String"
+               writeable="false"/>
+               
+    <attribute name="interruptible"
+               description="Should this be blocked interruptibly until a permit is availabl?"
+               type="boolean"/>
+               
+    <attribute name="stateName"
+               description="The name of the LifecycleState that this component is currently in"
+               type="java.lang.String"
+               writeable="false"/>
+
+  </mbean>
+
+  <mbean name="RemoteAddrValve"
+         description="Concrete implementation of RequestFilterValve that  filters based on the string representation of the remote client's IP address"
+         domain="Catalina"
+         group="Valve"
+         type="org.apache.catalina.valves.RemoteAddrValve">
+
+    <attribute name="allow"
+               description="The comma-delimited set of allow expressions"
+               type="java.lang.String"/>
+               
+    <attribute name="asyncSupported"
+               description="Does this valve support async reporting."
+               is="true"
+               type="boolean"/>
+
+    <attribute   name="className"
+               description="Fully qualified class name of the managed object"
+               type="java.lang.String"
+               writeable="false"/>
+
+    <attribute   name="deny"
+               description="The comma-delimited set of deny expressions"
+               type="java.lang.String"/>
+               
+    <attribute name="info"
+               description="Information about this implementation"
+               type="java.lang.String"
+               writeable="false"/>
+
+    <attribute name="stateName"
+               description="The name of the LifecycleState that this component is currently in"
+               type="java.lang.String"
+               writeable="false"/>
+
+  </mbean>
+
+  <mbean name="RemoteHostValve"
+         description="Concrete implementation of RequestFilterValve that filters based on the string representation of the remote client's host name"
+         domain="Catalina"
+         group="Valve"
+         type="org.apache.catalina.valves.RemoteHostValve">
+
+    <attribute name="allow"
+               description="The comma-delimited set of allow expressions"
+               type="java.lang.String"/>
+               
+    <attribute name="asyncSupported"
+               description="Does this valve support async reporting."
+               is="true"
+               type="boolean"/>
+
+    <attribute   name="className"
+               description="Fully qualified class name of the managed object"
+               type="java.lang.String"
+               writeable="false"/>
+
+    <attribute   name="deny"
+               description="The comma-delimited set of deny expressions"
+               type="java.lang.String"/>
+               
+    <attribute name="info"
+               description="Information about this implementation"
+               type="java.lang.String"
+               writeable="false"/>
+
+    <attribute name="stateName"
+               description="The name of the LifecycleState that this component is currently in"
+               type="java.lang.String"
+               writeable="false"/>
+
+  </mbean>
+
+  <mbean name="RemoteIpValve"
+         description="Valve that sets client information (eg IP address) based on data from a trusted proxy"
+         domain="Catalina"
+         group="Valve"
+         type="org.apache.catalina.valves.RemoteIpValve">
+         
+    <attribute name="asyncSupported"
+               description="Does this valve support async reporting."
+               is="true"
+               type="boolean"/>
+
+    <attribute name="httpServerPort"
+               description="Value returned by ServletRequest.getServerPort() when the protocolHeader indicates http protocol"
+               type="java.lang.String"
+               writeable="false" />
+    
+    <attribute name="httpsServerPort"
+               description="Value returned by ServletRequest.getServerPort() when the protocolHeader indicates https protocol"
+               type="java.lang.String"
+               writeable="false" />
+               
+    <attribute name="info"
+               description="Information about this implementation"
+               type="java.lang.String"
+               writeable="false"/>
+               
+    <attribute name="internalProxies"
+               description="Regular expression that matches IP addresses of internal proxies"
+               type="java.lang.String"
+               writeable="false" />
+    
+    <attribute name="protocolHeader"
+               description="The protocol header (e.g. &quot;X-Forwarded-Proto&quot;)"
+               type="java.lang.String"
+               writeable="false" />
+               
+    <attribute name="protocolHeaderHttpsValue"
+               description="The value of the protocol header for incoming https request (e.g. &quot;https&quot;)"
+               type="java.lang.String"
+               writeable="false" />
+               
+    <attribute name="proxiesHeader"
+               description="The proxies header name (e.g. &quot;X-Forwarded-By&quot;)"
+               type="java.lang.String"
+               writeable="false" />
+               
+    <attribute name="remoteIpHeader"
+               description="The remote IP header name (e.g. &quot;X-Forwarded-For&quot;)"
+               type="java.lang.String"
+               writeable="false" />
+               
+    <attribute name="stateName"
+               description="The name of the LifecycleState that this component is currently in"
+               type="java.lang.String"
+               writeable="false"/>
+
+    <attribute name="trustedProxies"
+               description="Regular expression that matches IP addresses of trusted proxies"
+               type="java.lang.String"
+               writeable="false" />
+               
+  </mbean>
+</mbeans-descriptors>
diff --git a/bundles/org.apache.tomcat/src/org/apache/catalina/valves/package.html b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/package.html
new file mode 100644
index 0000000..7643476
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/catalina/valves/package.html
@@ -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.
+-->
+<body>
+
+<p>This package contains a variety of small Valve implementations that do
+not warrant being packaged separately.  In addition, there is a convenience
+base class (<code>ValveBase</code>) that supports the usual mechanisms for
+including custom Valves into the corresponding Pipeline.</p>
+
+<p>Other packages that include Valves include
+<code>org.apache.tomcat.logger</code> and
+<code>org.apache.tomcat.security</code>.</p>
+
+</body>
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/AbstractProtocolHandler.java b/bundles/org.apache.tomcat/src/org/apache/coyote/AbstractProtocolHandler.java
new file mode 100644
index 0000000..c528490
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/AbstractProtocolHandler.java
@@ -0,0 +1,444 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import java.net.InetAddress;
+import java.util.concurrent.Executor;
+
+import javax.management.MBeanRegistration;
+import javax.management.MBeanServer;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+
+import org.apache.juli.logging.Log;
+import org.apache.tomcat.util.modeler.Registry;
+import org.apache.tomcat.util.net.AbstractEndpoint;
+import org.apache.tomcat.util.net.AbstractEndpoint.Handler;
+import org.apache.tomcat.util.res.StringManager;
+
+public abstract class AbstractProtocolHandler implements ProtocolHandler,
+        MBeanRegistration {
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
+     * Name of MBean for the Global Request Processor.
+     */
+    protected ObjectName rgOname = null;
+
+    
+    /**
+     * Name of MBean for the ThreadPool.
+     */
+    protected ObjectName tpOname = null;
+
+    
+    /**
+     * Endpoint that provides low-level network I/O - must be matched to the
+     * ProtocolHandler implementation (ProtocolHandler using BIO, requires BIO
+     * Endpoint etc.).
+     */
+    protected AbstractEndpoint endpoint = null;
+
+    
+    // ----------------------------------------------- Generic property handling
+
+    /**
+     * Generic property setter used by the digester. Other code should not need
+     * to use this. The digester will only use this method if it can't find a
+     * more specific setter. That means the property belongs to the Endpoint,
+     * the ServerSocketFactory or some other lower level component. This method
+     * ensures that it is visible to both.
+     */
+    public boolean setProperty(String name, String value) {
+        return endpoint.setProperty(name, value);
+    }
+
+
+    /**
+     * Generic property getter used by the digester. Other code should not need
+     * to use this.
+     */
+    public String getProperty(String name) {
+        return endpoint.getProperty(name);
+    }
+
+
+    // ------------------------------- Properties managed by the ProtocolHandler
+    
+    /**
+     * The adapter provides the link between the ProtocolHandler and the
+     * connector.
+     */
+    protected Adapter adapter;
+    @Override
+    public void setAdapter(Adapter adapter) { this.adapter = adapter; }
+    @Override
+    public Adapter getAdapter() { return adapter; }
+
+
+    /**
+     * The maximum number of idle processors that will be retained in the cache
+     * and re-used with a subsequent request. The default is -1, unlimited,
+     * although in that case there will never be more Processor objects than
+     * there are threads in the associated thread pool.
+     */
+    protected int processorCache = -1;
+    public int getProcessorCache() { return this.processorCache; }
+    public void setProcessorCache(int processorCache) {
+        this.processorCache = processorCache;
+    }
+
+
+    /**
+     * 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; }
+
+
+    // ---------------------- Properties that are passed through to the EndPoint
+
+    @Override
+    public Executor getExecutor() { return endpoint.getExecutor(); }
+    public void setExecutor(Executor executor) {
+        endpoint.setExecutor(executor);
+    }
+
+
+    public int getMaxThreads() { return endpoint.getMaxThreads(); }
+    public void setMaxThreads(int maxThreads) {
+        endpoint.setMaxThreads(maxThreads);
+    }
+    
+    public int getMaxConnections() { return endpoint.getMaxConnections(); }
+    public void setMaxConnections(int maxConnections) {
+        endpoint.setMaxConnections(maxConnections);
+    }
+
+
+    public int getMinSpareThreads() { return endpoint.getMinSpareThreads(); }
+    public void setMinSpareThreads(int minSpareThreads) {
+        endpoint.setMinSpareThreads(minSpareThreads);
+    }
+
+
+    public int getThreadPriority() { return endpoint.getThreadPriority(); }
+    public void setThreadPriority(int threadPriority) {
+        endpoint.setThreadPriority(threadPriority);
+    }
+
+
+    public int getBacklog() { return endpoint.getBacklog(); }
+    public void setBacklog(int backlog) { endpoint.setBacklog(backlog); }
+
+
+    public boolean getTcpNoDelay() { return endpoint.getTcpNoDelay(); }
+    public void setTcpNoDelay(boolean tcpNoDelay) {
+        endpoint.setTcpNoDelay(tcpNoDelay);
+    }
+
+
+    public int getSoLinger() { return endpoint.getSoLinger(); }
+    public void setSoLinger(int soLinger) { endpoint.setSoLinger(soLinger); }
+
+
+    public int getKeepAliveTimeout() { return endpoint.getKeepAliveTimeout(); }
+    public void setKeepAliveTimeout(int keepAliveTimeout) {
+        endpoint.setKeepAliveTimeout(keepAliveTimeout);
+    }
+
+    public InetAddress getAddress() { return endpoint.getAddress(); }
+    public void setAddress(InetAddress ia) {
+        endpoint.setAddress(ia);
+    }
+
+
+    public int getPort() { return endpoint.getPort(); }
+    public void setPort(int port) {
+        endpoint.setPort(port);
+    }
+
+
+    /*
+     * When Tomcat expects data from the client, this is the time Tomcat will
+     * wait for that data to arrive before closing the connection.
+     */
+    public int getConnectionTimeout() {
+        // Note that the endpoint uses the alternative name
+        return endpoint.getSoTimeout();
+    }
+    public void setConnectionTimeout(int timeout) {
+        // Note that the endpoint uses the alternative name
+        endpoint.setSoTimeout(timeout);
+    }
+
+    /*
+     * Alternative name for connectionTimeout property
+     */
+    public int getSoTimeout() {
+        return getConnectionTimeout();
+    }
+    public void setSoTimeout(int timeout) {
+        setConnectionTimeout(timeout);
+    }
+
+
+    // ---------------------------------------------------------- Public methods
+
+    /**
+     * The name will be prefix-address-port if address is non-null and
+     * prefix-port if the address is null. The name will be appropriately quoted
+     * so it can be used directly in an ObjectName.
+     */
+    public String getName() {
+        StringBuilder name = new StringBuilder(getNamePrefix());
+        name.append('-');
+        if (getAddress() != null) {
+            name.append(getAddress());
+            name.append('-');
+        }
+        name.append(endpoint.getPort());
+        return ObjectName.quote(name.toString());
+    }
+
+    
+    // -------------------------------------------------------- Abstract methods
+    
+    /**
+     * Concrete implementations need to provide access to their logger to be
+     * used by the abstract classes.
+     */
+    protected abstract Log getLog();
+    
+    
+    /**
+     * Obtain the prefix to be used when construction a name for this protocol
+     * handler. The name will be prefix-address-port.
+     */
+    protected abstract String getNamePrefix();
+
+
+    /**
+     * Obtain the handler associated with the underlying Endpoint
+     */
+    protected abstract Handler getHandler();
+
+
+    // ----------------------------------------------------- JMX related methods
+
+    protected String domain;
+    protected ObjectName oname;
+    protected MBeanServer mserver;
+
+    public ObjectName getObjectName() {
+        return oname;
+    }
+
+    public String getDomain() {
+        return domain;
+    }
+
+    @Override
+    public ObjectName preRegister(MBeanServer server, ObjectName name)
+            throws Exception {
+        oname = name;
+        mserver = server;
+        domain = name.getDomain();
+        return name;
+    }
+
+    @Override
+    public void postRegister(Boolean registrationDone) {
+        // NOOP
+    }
+
+    @Override
+    public void preDeregister() throws Exception {
+        // NOOP
+    }
+
+    @Override
+    public void postDeregister() {
+        // NOOP
+    }
+
+    private ObjectName createObjectName() throws MalformedObjectNameException {
+        // Use the same domain as the connector
+        domain = adapter.getDomain();
+        
+        if (domain == null) {
+            return null;
+        }
+
+        StringBuilder name = new StringBuilder(getDomain());
+        name.append(":type=ProtocolHandler,port=");
+        name.append(getPort());
+        InetAddress address = getAddress();
+        if (address != null) {
+            name.append(",address=");
+            name.append(ObjectName.quote(address.toString()));
+        }
+        return new ObjectName(name.toString());
+    }
+
+    // ------------------------------------------------------- Lifecycle methods
+
+    /*
+     * NOTE: There is no maintenance of state or checking for valid transitions
+     * within this class. It is expected that the connector will maintain state
+     * and prevent invalid state transitions.
+     */
+
+    @Override
+    public void init() throws Exception {
+        if (getLog().isInfoEnabled())
+            getLog().info(sm.getString("abstractProtocolHandler.init",
+                    getName()));
+
+        if (oname == null) {
+            // Component not pre-registered so register it
+            oname = createObjectName();
+            if (oname != null) {
+                Registry.getRegistry(null, null).registerComponent(this, oname,
+                    null);
+            }
+        }
+
+        if (this.domain != null) {
+            try {
+                tpOname = new ObjectName(domain + ":" +
+                        "type=ThreadPool,name=" + getName());
+                Registry.getRegistry(null, null).registerComponent(endpoint,
+                        tpOname, null);
+            } catch (Exception e) {
+                getLog().error(sm.getString(
+                        "abstractProtocolHandler.mbeanRegistrationFailed",
+                        tpOname, getName()), e);
+            }
+            rgOname=new ObjectName(domain +
+                    ":type=GlobalRequestProcessor,name=" + getName());
+            Registry.getRegistry(null, null).registerComponent(
+                    getHandler().getGlobal(), rgOname, null );
+        }
+
+        endpoint.setName(getName());
+
+        try {
+            endpoint.init();
+        } catch (Exception ex) {
+            getLog().error(sm.getString("abstractProtocolHandler.initError",
+                    getName()), ex);
+            throw ex;
+        }
+    }
+
+
+    @Override
+    public void start() throws Exception {
+        if (getLog().isInfoEnabled())
+            getLog().info(sm.getString("abstractProtocolHandler.start",
+                    getName()));
+        try {
+            endpoint.start();
+        } catch (Exception ex) {
+            getLog().error(sm.getString("abstractProtocolHandler.startError",
+                    getName()), ex);
+            throw ex;
+        }
+    }
+
+
+    @Override
+    public void pause() throws Exception {
+        if(getLog().isInfoEnabled())
+            getLog().info(sm.getString("abstractProtocolHandler.pause",
+                    getName()));
+        try {
+            endpoint.pause();
+        } catch (Exception ex) {
+            getLog().error(sm.getString("abstractProtocolHandler.pauseError",
+                    getName()), ex);
+            throw ex;
+        }
+    }
+
+    @Override
+    public void resume() throws Exception {
+        if(getLog().isInfoEnabled())
+            getLog().info(sm.getString("abstractProtocolHandler.resume",
+                    getName()));
+        try {
+            endpoint.resume();
+        } catch (Exception ex) {
+            getLog().error(sm.getString("abstractProtocolHandler.resumeError",
+                    getName()), ex);
+            throw ex;
+        }
+    }
+
+
+    @Override
+    public void stop() throws Exception {
+        if(getLog().isInfoEnabled())
+            getLog().info(sm.getString("abstractProtocolHandler.stop",
+                    getName()));
+        try {
+            endpoint.stop();
+        } catch (Exception ex) {
+            getLog().error(sm.getString("abstractProtocolHandler.stopError",
+                    getName()), ex);
+            throw ex;
+        }
+    }
+
+
+    @Override
+    public void destroy() {
+        if(getLog().isInfoEnabled()) {
+            getLog().info(sm.getString("abstractProtocolHandler.destroy",
+                    getName()));
+        }
+        try {
+            endpoint.destroy();
+        } catch (Exception e) {
+            getLog().error(sm.getString("abstractProtocolHandler.destroyError",
+                    getName()), e);
+        }
+        
+        if (oname != null) {
+            Registry.getRegistry(null, null).unregisterComponent(oname);
+        }
+
+        if (tpOname != null)
+            Registry.getRegistry(null, null).unregisterComponent(tpOname);
+        if (rgOname != null)
+            Registry.getRegistry(null, null).unregisterComponent(rgOname);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/ActionCode.java b/bundles/org.apache.tomcat/src/org/apache/coyote/ActionCode.java
new file mode 100644
index 0000000..c268658
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/ActionCode.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.coyote;
+
+/**
+ * ActionCodes represent callbacks from the servlet container to the coyote
+ * connector. Actions are implemented by ProtocolHandler, using the ActionHook
+ * interface.
+ * 
+ * @see ProtocolHandler
+ * @see ActionHook
+ * @author Remy Maucherat
+ */
+public enum ActionCode {
+    ACK,
+    CLOSE,
+    COMMIT,
+
+    /**
+     * A flush() operation originated by the client ( i.e. a flush() on the
+     * servlet output stream or writer, called by a servlet ). Argument is the
+     * Response.
+     */
+    CLIENT_FLUSH,
+
+    CUSTOM,
+    RESET,
+    WEBAPP,
+
+    /**
+     * Hook called after request, but before recycling. Can be used for logging,
+     * to update counters, custom cleanup - the request is still visible
+     */
+    POST_REQUEST,
+
+    /**
+     * Hook called if swallowing request input should be disabled.
+     * Example: Cancel a large file upload.
+     * 
+     */
+    DISABLE_SWALLOW_INPUT,
+
+    /**
+     * Callback for lazy evaluation - extract the remote host address.
+     */
+    REQ_HOST_ATTRIBUTE,
+
+    /**
+     * Callback for lazy evaluation - extract the remote host infos (address,
+     * name, port) and local address.
+     */
+    REQ_HOST_ADDR_ATTRIBUTE,
+
+    /**
+     * Callback for lazy evaluation - extract the SSL-related attributes.
+     */
+    REQ_SSL_ATTRIBUTE,
+
+    /**
+     * Chain for request creation. Called each time a new request is created
+     * (requests are recycled).
+     */
+    NEW_REQUEST,
+
+    /**
+     * Callback for lazy evaluation - extract the SSL-certificate (including
+     * forcing a re-handshake if necessary)
+     */
+    REQ_SSL_CERTIFICATE,
+
+    /**
+     * Callback for lazy evaluation - socket remote port.
+     */
+    REQ_REMOTEPORT_ATTRIBUTE,
+
+    /**
+     * Callback for lazy evaluation - socket local port.
+     */
+    REQ_LOCALPORT_ATTRIBUTE,
+
+    /**
+     * Callback for lazy evaluation - local address.
+     */
+    REQ_LOCAL_ADDR_ATTRIBUTE,
+
+    /**
+     * Callback for lazy evaluation - local address.
+     */
+    REQ_LOCAL_NAME_ATTRIBUTE,
+
+    /**
+     * Callback for setting FORM auth body replay
+     */
+    REQ_SET_BODY_REPLAY,
+
+    /**
+     * Callback for begin Comet processing
+     */
+    COMET_BEGIN,
+
+    /**
+     * Callback for end Comet processing
+     */
+    COMET_END,
+
+    /**
+     * Callback for getting the amount of available bytes
+     */
+    AVAILABLE,
+
+    /**
+     * Callback for an asynchronous close of the Comet event
+     */
+    COMET_CLOSE,
+
+    /**
+     * Callback for setting the timeout asynchronously
+     */
+    COMET_SETTIMEOUT,
+
+    /**
+     * Callback for an async request
+     */
+    ASYNC_START,
+
+    /**
+     * Callback for an async call to
+     * {@link javax.servlet.AsyncContext#dispatch()}
+     */
+    ASYNC_DISPATCH,
+
+    /**
+     * Callback to indicate the the actual dispatch has started and that the
+     * async state needs change.
+     */
+    ASYNC_DISPATCHED,
+
+    /**
+     * Callback for an async call to
+     * {@link javax.servlet.AsyncContext#start(Runnable)}
+     */
+    ASYNC_RUN,
+
+    /**
+     * Callback for an async call to
+     * {@link javax.servlet.AsyncContext#complete()}
+     */
+    ASYNC_COMPLETE,
+    
+    /**
+     * Callback to trigger the processing of an async timeout
+     */
+    ASYNC_TIMEOUT,
+    
+    /**
+     * Callback to trigger the error processing
+     */
+    ASYNC_ERROR,
+    
+    /**
+     * Callback for an async call to
+     * {@link javax.servlet.AsyncContext#setTimeout(long)}
+     */
+    ASYNC_SETTIMEOUT,
+    
+    /**
+     * Callback to determine if async processing is in progress 
+     */
+    ASYNC_IS_ASYNC,
+    
+    /**
+     * Callback to determine if async dispatch is in progress
+     */
+    ASYNC_IS_STARTED,
+
+    /**
+     * Callback to determine if async dispatch is in progress
+     */
+    ASYNC_IS_DISPATCHING,
+
+    /**
+     * Callback to determine if async is timing out
+     */
+    ASYNC_IS_TIMINGOUT
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/ActionHook.java b/bundles/org.apache.tomcat/src/org/apache/coyote/ActionHook.java
new file mode 100644
index 0000000..7215494
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/ActionHook.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.coyote;
+
+
+/**
+ * Action hook. Actions represent the callback mechanism used by
+ * coyote servlet containers to request operations on the coyote connectors.
+ * Some standard actions are defined in ActionCode, however custom
+ * actions are permitted.
+ *
+ * The param object can be used to pass and return informations related with the
+ * action.
+ * 
+ *
+ * This interface is typically implemented by ProtocolHandlers, and the param
+ * is usually a Request or Response object.
+ *
+ * @author Remy Maucherat
+ */
+public interface ActionHook {
+
+
+    /**
+     * Send an action to the connector.
+     * 
+     * @param actionCode Type of the action
+     * @param param Action parameter
+     */
+    public void action(ActionCode actionCode, Object param);
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/Adapter.java b/bundles/org.apache.tomcat/src/org/apache/coyote/Adapter.java
new file mode 100644
index 0000000..e9496e7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/Adapter.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.coyote;
+
+import org.apache.tomcat.util.net.SocketStatus;
+
+
+/**
+ * Adapter. This represents the entry point in a coyote-based servlet container.
+ *
+ *
+ * @author Remy Maucherat
+ * @see ProtocolHandler
+ */
+public interface Adapter {
+
+    /** 
+     * Call the service method, and notify all listeners
+     *
+     * @exception Exception if an error happens during handling of
+     *   the request. Common errors are:
+     *   <ul><li>IOException if an input/output error occurs and we are
+     *   processing an included servlet (otherwise it is swallowed and
+     *   handled by the top level error handler mechanism)
+     *       <li>ServletException if a servlet throws an exception and
+     *  we are processing an included servlet (otherwise it is swallowed
+     *  and handled by the top level error handler mechanism)
+     *  </ul>
+     *  Tomcat should be able to handle and log any other exception ( including
+     *  runtime exceptions )
+     */
+    public void service(Request req, Response res)
+            throws Exception;
+
+    public boolean event(Request req, Response res, SocketStatus status)
+            throws Exception;
+    
+    public boolean asyncDispatch(Request req,Response res, SocketStatus status)
+            throws Exception;
+
+    public void log(Request req, Response res, long time);
+
+    /**
+     * Provide the name of the domain to use to register MBeans for conponents
+     * associated with the connector.
+     * 
+     * @return  The MBean domain name
+     */
+    public String getDomain();
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/AsyncContextCallback.java b/bundles/org.apache.tomcat/src/org/apache/coyote/AsyncContextCallback.java
new file mode 100644
index 0000000..cf20be8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/AsyncContextCallback.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.coyote;
+
+/**
+ * Provides a mechanism for the Coyote connectors to signal to a
+ * {@link javax.servlet.AsyncContext} implementation that an action, such as
+ * firing event listeners needs to be taken. It is implemented in this manner
+ * so that the org.apache.coyote package does not have a dependency on the
+ * org.apache.coyote package.  
+ */
+public interface AsyncContextCallback {
+    public void fireOnComplete();
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/AsyncStateMachine.java b/bundles/org.apache.tomcat/src/org/apache/coyote/AsyncStateMachine.java
new file mode 100644
index 0000000..b71a4ac
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/AsyncStateMachine.java
@@ -0,0 +1,322 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Manages the state transitions for async requests.
+ * TODO: State transition diagram
+ * 
+ * The internal states that are used are:
+ * DISPATCHED    - Standard request. Not in Async mode.
+ * STARTING      - ServletRequest.startAsync() has been called but the
+ *                 request in which that call was made has not finished
+ *                 processing.
+ * STARTED       - ServletRequest.startAsync() has been called and the
+ *                 request in which that call was made has finished
+ *                 processing.
+ * MUST_COMPLETE - complete() has been called before the request in which
+ *                 ServletRequest.startAsync() has finished. As soon as that
+ *                 request finishes, the complete() will be processed.
+ * COMPLETING    - The call to complete() was made once the request was in
+ *                 the STARTED state. May or may not be triggered by a
+ *                 container thread - depends if start(Runnable) was used
+ * TIMING_OUT    - The async request has timed out and is waiting for a call
+ *                 to complete(). If that isn't made, the error state will
+ *                 entered.
+ * MUST_DISPATCH - dispatch() has been called before the request in which
+ *                 ServletRequest.startAsync() has finished. As soon as that
+ *                 request finishes, the dispatch() will be processed.
+ * DISPATCHING   - The dispatch is being processed.
+ * ERROR         - Something went wrong.
+ */
+public class AsyncStateMachine {
+
+    /**
+     * The string manager for this package.
+     */
+    private static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    private static enum AsyncState {
+        DISPATCHED(false, false, false),
+        STARTING(true, true, false),
+        STARTED(true, true, false),
+        MUST_COMPLETE(true, false, false),
+        COMPLETING(true, false, false),
+        TIMING_OUT(true, false, false),
+        MUST_DISPATCH(true, false, true),
+        DISPATCHING(true, false, true),
+        ERROR(true,false,false);
+    
+        private boolean isAsync;
+        private boolean isStarted;
+        private boolean isDispatching;
+        
+        private AsyncState(boolean isAsync, boolean isStarted,
+                boolean isDispatching) {
+            this.isAsync = isAsync;
+            this.isStarted = isStarted;
+            this.isDispatching = isDispatching;
+        }
+        
+        public boolean isAsync() {
+            return this.isAsync;
+        }
+        
+        public boolean isStarted() {
+            return this.isStarted;
+        }
+        
+        public boolean isDispatching() {
+            return this.isDispatching;
+        }
+    }
+    
+
+    private volatile AsyncState state = AsyncState.DISPATCHED;
+    // Need this to fire listener on complete
+    private AsyncContextCallback asyncCtxt = null;
+    private Processor processor;
+    
+    
+    public AsyncStateMachine(Processor processor) {
+        this.processor = processor;
+    }
+
+
+    public boolean isAsync() {
+        return state.isAsync();
+    }
+
+    public boolean isAsyncDispatching() {
+        return state.isDispatching();
+    }
+
+    public boolean isAsyncStarted() {
+        return state.isStarted();
+    }
+
+    public boolean isAsyncTimingOut() {
+        return state == AsyncState.TIMING_OUT;
+    }
+
+
+    public synchronized void asyncStart(AsyncContextCallback asyncCtxt) {
+        if (state == AsyncState.DISPATCHED) {
+            state = AsyncState.STARTING;
+            this.asyncCtxt = asyncCtxt;
+        } else {
+            throw new IllegalStateException(
+                    sm.getString("asyncStateMachine.invalidAsyncState",
+                            "asyncStart()", state));
+        }
+    }
+    
+    /*
+     * Async has been processed. Whether or not to enter a long poll depends on
+     * current state. For example, as per SRV.2.3.3.3 can now process calls to
+     * complete() or dispatch().
+     */
+    public synchronized SocketState asyncPostProcess() {
+        
+        if (state == AsyncState.STARTING) {
+            state = AsyncState.STARTED;
+            return SocketState.LONG;
+        } else if (state == AsyncState.MUST_COMPLETE) {
+            asyncCtxt.fireOnComplete();
+            state = AsyncState.DISPATCHED;
+            return SocketState.ASYNC_END;
+        } else if (state == AsyncState.COMPLETING) {
+            asyncCtxt.fireOnComplete();
+            state = AsyncState.DISPATCHED;
+            return SocketState.ASYNC_END;
+        } else if (state == AsyncState.MUST_DISPATCH) {
+            state = AsyncState.DISPATCHING;
+            return SocketState.ASYNC_END;
+        } else if (state == AsyncState.DISPATCHING) {
+            state = AsyncState.DISPATCHED;
+            return SocketState.ASYNC_END;
+        } else if (state == AsyncState.ERROR) {
+            asyncCtxt.fireOnComplete();
+            state = AsyncState.DISPATCHED;
+            return SocketState.ASYNC_END;
+        //} else if (state == AsyncState.DISPATCHED) {
+        //    // No state change
+        //    return SocketState.OPEN;
+        } else {
+            throw new IllegalStateException(
+                    sm.getString("asyncStateMachine.invalidAsyncState",
+                            "asyncPostProcess()", state));
+        }
+    }
+    
+
+    public synchronized boolean asyncComplete() {
+        boolean doComplete = false;
+        
+        if (state == AsyncState.STARTING) {
+            state = AsyncState.MUST_COMPLETE;
+        } else if (state == AsyncState.STARTED) {
+            state = AsyncState.COMPLETING;
+            doComplete = true;
+        } else if (state == AsyncState.TIMING_OUT ||
+                state == AsyncState.ERROR) {
+            state = AsyncState.MUST_COMPLETE;
+        } else {
+            throw new IllegalStateException(
+                    sm.getString("asyncStateMachine.invalidAsyncState",
+                            "asyncComplete()", state));
+            
+        }
+        return doComplete;
+    }
+    
+    
+    public synchronized boolean asyncTimeout() {
+        if (state == AsyncState.STARTED) {
+            state = AsyncState.TIMING_OUT;
+            return true;
+        } else if (state == AsyncState.COMPLETING ||
+                state == AsyncState.DISPATCHED) {
+            // NOOP - App called complete between the the timeout firing and
+            // execution reaching this point
+            return false;
+        } else {
+            throw new IllegalStateException(
+                    sm.getString("asyncStateMachine.invalidAsyncState",
+                            "asyncTimeout()", state));
+        }
+    }
+    
+    
+    public synchronized boolean asyncDispatch() {
+        boolean doDispatch = false;
+        if (state == AsyncState.STARTING) {
+            state = AsyncState.MUST_DISPATCH;
+        } else if (state == AsyncState.STARTED ||
+                state == AsyncState.TIMING_OUT) {
+            state = AsyncState.DISPATCHING;
+            doDispatch = true;
+        } else {
+            throw new IllegalStateException(
+                    sm.getString("asyncStateMachine.invalidAsyncState",
+                            "asyncDispatch()", state));
+        }
+        return doDispatch;
+    }
+    
+    
+    public synchronized void asyncDispatched() {
+        if (state == AsyncState.DISPATCHING) {
+            state = AsyncState.DISPATCHED;
+        } else {
+            throw new IllegalStateException(
+                    sm.getString("asyncStateMachine.invalidAsyncState",
+                            "asyncDispatched()", state));
+        }
+    }
+    
+    
+    public synchronized boolean asyncError() {
+        boolean doDispatch = false;
+        if (state == AsyncState.DISPATCHED ||
+                state == AsyncState.TIMING_OUT) {
+            state = AsyncState.ERROR;
+        } else {
+            throw new IllegalStateException(
+                    sm.getString("asyncStateMachine.invalidAsyncState",
+                            "asyncError()", state));
+        }
+        return doDispatch;
+    }
+    
+    public synchronized void asyncRun(Runnable runnable) {
+        if (state == AsyncState.STARTING || state ==  AsyncState.STARTED) {
+            // Execute the runnable using a container thread from the
+            // Connector's thread pool. Use a wrapper to prevent a memory leak
+            ClassLoader oldCL;
+            if (Constants.IS_SECURITY_ENABLED) {
+                PrivilegedAction<ClassLoader> pa = new PrivilegedGetTccl();
+                oldCL = AccessController.doPrivileged(pa);
+            } else {
+                oldCL = Thread.currentThread().getContextClassLoader();
+            }
+            try {
+                if (Constants.IS_SECURITY_ENABLED) {
+                    PrivilegedAction<Void> pa = new PrivilegedSetTccl(
+                            this.getClass().getClassLoader());
+                    AccessController.doPrivileged(pa);
+                } else {
+                    Thread.currentThread().setContextClassLoader(
+                            this.getClass().getClassLoader());
+                }
+                
+                processor.getExecutor().execute(runnable);
+            } finally {
+                if (Constants.IS_SECURITY_ENABLED) {
+                    PrivilegedAction<Void> pa = new PrivilegedSetTccl(
+                            oldCL);
+                    AccessController.doPrivileged(pa);
+                } else {
+                    Thread.currentThread().setContextClassLoader(oldCL);
+                }
+            }
+        } else {
+            throw new IllegalStateException(
+                    sm.getString("asyncStateMachine.invalidAsyncState",
+                            "asyncRun()", state));
+        }
+
+    }
+    
+    
+    public void recycle() {
+        asyncCtxt = null;
+        state = AsyncState.DISPATCHED;
+    }
+    
+    
+    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;
+        }
+    }
+
+    private static class PrivilegedGetTccl
+            implements PrivilegedAction<ClassLoader> {
+
+        @Override
+        public ClassLoader run() {
+            return Thread.currentThread().getContextClassLoader();
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/Constants.java b/bundles/org.apache.tomcat/src/org/apache/coyote/Constants.java
new file mode 100644
index 0000000..0b4bcc0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/Constants.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.coyote;
+
+
+/**
+ * Constants.
+ *
+ * @author Remy Maucherat
+ */
+public final class Constants {
+
+
+    // -------------------------------------------------------------- Constants
+
+    public static final String Package = "org.apache.coyote";
+    
+    public static final String DEFAULT_CHARACTER_ENCODING="ISO-8859-1";
+
+    public static final int MAX_NOTES = 32;
+
+
+    // Request states
+    public static final int STAGE_NEW = 0;
+    public static final int STAGE_PARSE = 1;
+    public static final int STAGE_PREPARE = 2;
+    public static final int STAGE_SERVICE = 3;
+    public static final int STAGE_ENDINPUT = 4;
+    public static final int STAGE_ENDOUTPUT = 5;
+    public static final int STAGE_KEEPALIVE = 6;
+    public static final int STAGE_ENDED = 7;
+
+
+    /**
+     * Has security been turned on?
+     */
+    public static final boolean IS_SECURITY_ENABLED =
+        (System.getSecurityManager() != null);
+
+
+    /**
+     * If true, custom HTTP status messages will be used in headers.
+     */
+    public static final boolean USE_CUSTOM_STATUS_MSG_IN_HEADER =
+        Boolean.valueOf(System.getProperty(
+                "org.apache.coyote.USE_CUSTOM_STATUS_MSG_IN_HEADER",
+                "false")).booleanValue();
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/InputBuffer.java b/bundles/org.apache.tomcat/src/org/apache/coyote/InputBuffer.java
new file mode 100644
index 0000000..d3bd3bf
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/InputBuffer.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.coyote;
+
+import java.io.IOException;
+
+import org.apache.tomcat.util.buf.ByteChunk;
+
+
+/**
+ * Input buffer.
+ *
+ * This class is used only in the protocol implementation. All reading from
+ * Tomcat ( or adapter ) should be done using Request.doRead().
+ *
+ * 
+ * @author Remy Maucherat
+ */
+public interface InputBuffer {
+
+
+    /** Return from the input stream.
+        IMPORTANT: the current model assumes that the protocol will 'own' the
+        buffer and return a pointer to it in ByteChunk ( i.e. the param will
+        have chunk.getBytes()==null before call, and the result after the call ).
+    */
+    public int doRead(ByteChunk chunk, Request request) 
+        throws IOException;
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/coyote/LocalStrings.properties
new file mode 100644
index 0000000..94a44ad
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/LocalStrings.properties
@@ -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.
+
+abstractProtocolHandler.getAttribute=Get attribute [{0}] with value [{1}]
+abstractProtocolHandler.setAttribute=Set attribute [{0}] with value [{1}]
+abstractProtocolHandler.init=Initializing ProtocolHandler [{0}]
+abstractProtocolHandler.initError=Failed to initialize end point associated with ProtocolHandler [{0}]
+abstractProtocolHandler.mbeanRegistrationFailed=Failed to register MBean [{0}] for ProtocolHandler [{1}]
+abstractProtocolHandler.start=Starting ProtocolHandler [{0}]
+abstractProtocolHandler.startError=Failed to start end point associated with ProtocolHandler [{0}]
+abstractProtocolHandler.pause=Pausing ProtocolHandler [{0}]
+abstractProtocolHandler.pauseError=Failed to pause end point associated with ProtocolHandler [{0}]
+abstractProtocolHandler.resume=Resuming ProtocolHandler [{0}]
+abstractProtocolHandler.resumeError=Failed to resume end point associated with ProtocolHandler [{0}]
+abstractProtocolHandler.stop=Stopping ProtocolHandler [{0}]
+abstractProtocolHandler.stopError=Failed to stop end point associated with ProtocolHandler [{0}]
+abstractProtocolHandler.destroy=Destroying ProtocolHandler [{0}]
+abstractProtocolHandler.destroyError=Failed to destroy end point associated with ProtocolHandler [{0}]
+
+asyncStateMachine.invalidAsyncState=Calling [{0}] is not valid for a request with Async state [{1}]
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/OutputBuffer.java b/bundles/org.apache.tomcat/src/org/apache/coyote/OutputBuffer.java
new file mode 100644
index 0000000..2dfb588
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/OutputBuffer.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.coyote;
+
+import java.io.IOException;
+
+import org.apache.tomcat.util.buf.ByteChunk;
+
+
+/**
+ * Output buffer.
+ *
+ * This class is used internally by the protocol implementation. All writes from
+ * higher level code should happen via Resonse.doWrite().
+ * 
+ * @author Remy Maucherat
+ */
+public interface OutputBuffer {
+
+
+    /**
+     * Write the response. The caller ( tomcat ) owns the chunks.
+     *
+     * @param chunk data to write
+     * @param response used to allow buffers that can be shared by multiple
+     *          responses.
+     * @throws IOException
+     */
+    public int doWrite(ByteChunk chunk, Response response)
+        throws IOException;
+
+    /**
+     * Bytes written to the underlying socket. This includes the effects of
+     * chunking, compression, etc.
+     * 
+     * @return  Bytes written for the current request
+     */
+    public long getBytesWritten();
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/Processor.java b/bundles/org.apache.tomcat/src/org/apache/coyote/Processor.java
new file mode 100644
index 0000000..11367b9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/Processor.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.coyote;
+
+import java.util.concurrent.Executor;
+
+
+/**
+ * Common interface for processors of all protocols.
+ */
+public interface Processor {
+    Executor getExecutor();
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/ProtocolHandler.java b/bundles/org.apache.tomcat/src/org/apache/coyote/ProtocolHandler.java
new file mode 100644
index 0000000..b1594c8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/ProtocolHandler.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.coyote;
+
+import java.util.concurrent.Executor;
+
+
+/**
+ * Abstract the protocol implementation, including threading, etc.
+ * Processor is single threaded and specific to stream-based protocols,
+ * will not fit Jk protocols like JNI.
+ *
+ * This is the main interface to be implemented by a coyote connector.
+ * Adapter is the main interface to be implemented by a coyote servlet
+ * container.
+ *
+ * @author Remy Maucherat
+ * @author Costin Manolache
+ * @see Adapter
+ */
+public interface ProtocolHandler {
+
+    /**
+     * The adapter, used to call the connector.
+     */
+    public void setAdapter(Adapter adapter);
+    public Adapter getAdapter();
+
+
+    /**
+     * The executor, provide access to the underlying thread pool.
+     */
+    public Executor getExecutor();
+
+
+    /**
+     * Initialise the protocol.
+     */
+    public void init() throws Exception;
+
+
+    /**
+     * Start the protocol.
+     */
+    public void start() throws Exception;
+
+
+    /**
+     * Pause the protocol (optional).
+     */
+    public void pause() throws Exception;
+
+
+    /**
+     * Resume the protocol (optional).
+     */
+    public void resume() throws Exception;
+
+
+    /**
+     * Stop the protocol.
+     */
+    public void stop() throws Exception;
+
+
+    /**
+     * Destroy the protocol (optional).
+     */
+    public void destroy() throws Exception;
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/Request.java b/bundles/org.apache.tomcat/src/org/apache/coyote/Request.java
new file mode 100644
index 0000000..7ae8f63
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/Request.java
@@ -0,0 +1,527 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.buf.UDecoder;
+import org.apache.tomcat.util.http.ContentType;
+import org.apache.tomcat.util.http.Cookies;
+import org.apache.tomcat.util.http.MimeHeaders;
+import org.apache.tomcat.util.http.Parameters;
+
+/**
+ * This is a low-level, efficient representation of a server request. Most 
+ * fields are GC-free, expensive operations are delayed until the  user code 
+ * needs the information.
+ *
+ * Processing is delegated to modules, using a hook mechanism.
+ * 
+ * This class is not intended for user code - it is used internally by tomcat
+ * for processing the request in the most efficient way. Users ( servlets ) can
+ * access the information using a facade, which provides the high-level view
+ * of the request.
+ *
+ * For lazy evaluation, the request uses the getInfo() hook. The following ids
+ * are defined:
+ * <ul>
+ *  <li>req.encoding - returns the request encoding
+ *  <li>req.attribute - returns a module-specific attribute ( like SSL keys, etc ).
+ * </ul>
+ *
+ * Tomcat defines a number of attributes:
+ * <ul>
+ *   <li>"org.apache.tomcat.request" - allows access to the low-level
+ *       request object in trusted applications 
+ * </ul>
+ *
+ * @author James Duncan Davidson [duncan@eng.sun.com]
+ * @author James Todd [gonzo@eng.sun.com]
+ * @author Jason Hunter [jch@eng.sun.com]
+ * @author Harish Prabandham
+ * @author Alex Cruikshank [alex@epitonic.com]
+ * @author Hans Bergsten [hans@gefionsoftware.com]
+ * @author Costin Manolache
+ * @author Remy Maucherat
+ */
+public final class Request {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    public Request() {
+
+        parameters.setQuery(queryMB);
+        parameters.setURLDecoder(urlDecoder);
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    private int serverPort = -1;
+    private MessageBytes serverNameMB = MessageBytes.newInstance();
+
+    private int remotePort;
+    private int localPort;
+
+    private MessageBytes schemeMB = MessageBytes.newInstance();
+
+    private MessageBytes methodMB = MessageBytes.newInstance();
+    private MessageBytes unparsedURIMB = MessageBytes.newInstance();
+    private MessageBytes uriMB = MessageBytes.newInstance();
+    private MessageBytes decodedUriMB = MessageBytes.newInstance();
+    private MessageBytes queryMB = MessageBytes.newInstance();
+    private MessageBytes protoMB = MessageBytes.newInstance();
+
+    // remote address/host
+    private MessageBytes remoteAddrMB = MessageBytes.newInstance();
+    private MessageBytes localNameMB = MessageBytes.newInstance();
+    private MessageBytes remoteHostMB = MessageBytes.newInstance();
+    private MessageBytes localAddrMB = MessageBytes.newInstance();
+     
+    private MimeHeaders headers = new MimeHeaders();
+
+    private MessageBytes instanceId = MessageBytes.newInstance();
+
+    /**
+     * Notes.
+     */
+    private Object notes[] = new Object[Constants.MAX_NOTES];
+
+
+    /**
+     * Associated input buffer.
+     */
+    private InputBuffer inputBuffer = null;
+
+
+    /**
+     * URL decoder.
+     */
+    private UDecoder urlDecoder = new UDecoder();
+
+
+    /**
+     * HTTP specific fields. (remove them ?)
+     */
+    private long contentLength = -1;
+    private MessageBytes contentTypeMB = null;
+    private String charEncoding = null;
+    private Cookies cookies = new Cookies(headers);
+    private Parameters parameters = new Parameters();
+
+    private MessageBytes remoteUser=MessageBytes.newInstance();
+    private MessageBytes authType=MessageBytes.newInstance();
+    private HashMap<String,Object> attributes=new HashMap<String,Object>();
+
+    private Response response;
+    private ActionHook hook;
+
+    private int bytesRead=0;
+    // Time of the request - useful to avoid repeated calls to System.currentTime
+    private long startTime = 0L;
+    private int available = 0;
+
+    private RequestInfo reqProcessorMX=new RequestInfo(this);
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Get the instance id (or JVM route). Currently Ajp is sending it with each
+     * request. In future this should be fixed, and sent only once ( or
+     * 'negotiated' at config time so both tomcat and apache share the same name.
+     * 
+     * @return the instance id
+     */
+    public MessageBytes instanceId() {
+        return instanceId;
+    }
+
+
+    public MimeHeaders getMimeHeaders() {
+        return headers;
+    }
+
+
+    public UDecoder getURLDecoder() {
+        return urlDecoder;
+    }
+
+    // -------------------- Request data --------------------
+
+
+    public MessageBytes scheme() {
+        return schemeMB;
+    }
+    
+    public MessageBytes method() {
+        return methodMB;
+    }
+    
+    public MessageBytes unparsedURI() {
+        return unparsedURIMB;
+    }
+
+    public MessageBytes requestURI() {
+        return uriMB;
+    }
+
+    public MessageBytes decodedURI() {
+        return decodedUriMB;
+    }
+
+    public MessageBytes queryString() {
+        return queryMB;
+    }
+
+    public MessageBytes protocol() {
+        return protoMB;
+    }
+    
+    /** 
+     * Return the buffer holding the server name, if
+     * any. Use isNull() to check if there is no value
+     * set.
+     * This is the "virtual host", derived from the
+     * Host: header.
+     */
+    public MessageBytes serverName() {
+        return serverNameMB;
+    }
+
+    public int getServerPort() {
+        return serverPort;
+    }
+    
+    public void setServerPort(int serverPort ) {
+        this.serverPort=serverPort;
+    }
+
+    public MessageBytes remoteAddr() {
+        return remoteAddrMB;
+    }
+
+    public MessageBytes remoteHost() {
+        return remoteHostMB;
+    }
+
+    public MessageBytes localName() {
+        return localNameMB;
+    }    
+
+    public MessageBytes localAddr() {
+        return localAddrMB;
+    }
+    
+    public int getRemotePort(){
+        return remotePort;
+    }
+        
+    public void setRemotePort(int port){
+        this.remotePort = port;
+    }
+    
+    public int getLocalPort(){
+        return localPort;
+    }
+        
+    public void setLocalPort(int port){
+        this.localPort = port;
+    }
+
+    // -------------------- encoding/type --------------------
+
+
+    /**
+     * Get the character encoding used for this request.
+     */
+    public String getCharacterEncoding() {
+
+        if (charEncoding != null)
+            return charEncoding;
+
+        charEncoding = ContentType.getCharsetFromContentType(getContentType());
+        return charEncoding;
+
+    }
+
+
+    public void setCharacterEncoding(String enc) {
+        this.charEncoding = enc;
+    }
+
+
+    public void setContentLength(int len) {
+        this.contentLength = len;
+    }
+
+
+    public int getContentLength() {
+        long length = getContentLengthLong();
+
+        if (length < Integer.MAX_VALUE) {
+            return (int) length;
+        }
+        return -1;
+    }
+
+    public long getContentLengthLong() {
+        if( contentLength > -1 ) return contentLength;
+
+        MessageBytes clB = headers.getUniqueValue("content-length");
+        contentLength = (clB == null || clB.isNull()) ? -1 : clB.getLong();
+
+        return contentLength;
+    }
+
+    public String getContentType() {
+        contentType();
+        if ((contentTypeMB == null) || contentTypeMB.isNull()) 
+            return null;
+        return contentTypeMB.toString();
+    }
+
+
+    public void setContentType(String type) {
+        contentTypeMB.setString(type);
+    }
+
+
+    public MessageBytes contentType() {
+        if (contentTypeMB == null)
+            contentTypeMB = headers.getValue("content-type");
+        return contentTypeMB;
+    }
+
+
+    public void setContentType(MessageBytes mb) {
+        contentTypeMB=mb;
+    }
+
+
+    public String getHeader(String name) {
+        return headers.getHeader(name);
+    }
+
+    // -------------------- Associated response --------------------
+
+    public Response getResponse() {
+        return response;
+    }
+
+    public void setResponse( Response response ) {
+        this.response=response;
+        response.setRequest( this );
+    }
+    
+    public void action(ActionCode actionCode, Object param) {
+        if( hook==null && response!=null )
+            hook=response.getHook();
+        
+        if (hook != null) {
+            if( param==null ) 
+                hook.action(actionCode, this);
+            else
+                hook.action(actionCode, param);
+        }
+    }
+
+
+    // -------------------- Cookies --------------------
+
+
+    public Cookies getCookies() {
+        return cookies;
+    }
+
+
+    // -------------------- Parameters --------------------
+
+
+    public Parameters getParameters() {
+        return parameters;
+    }
+
+
+    // -------------------- Other attributes --------------------
+    // We can use notes for most - need to discuss what is of general interest
+    
+    public void setAttribute( String name, Object o ) {
+        attributes.put( name, o );
+    }
+
+    public HashMap<String,Object> getAttributes() {
+        return attributes;
+    }
+
+    public Object getAttribute(String name ) {
+        return attributes.get(name);
+    }
+    
+    public MessageBytes getRemoteUser() {
+        return remoteUser;
+    }
+
+    public MessageBytes getAuthType() {
+        return authType;
+    }
+
+    public int getAvailable() {
+        return available;
+    }
+
+    public void setAvailable(int available) {
+        this.available = available;
+    }
+
+    // -------------------- Input Buffer --------------------
+
+
+    public InputBuffer getInputBuffer() {
+        return inputBuffer;
+    }
+
+
+    public void setInputBuffer(InputBuffer inputBuffer) {
+        this.inputBuffer = inputBuffer;
+    }
+
+
+    /**
+     * Read data from the input buffer and put it into a byte chunk.
+     *
+     * The buffer is owned by the protocol implementation - it will be reused on the next read.
+     * The Adapter must either process the data in place or copy it to a separate buffer if it needs
+     * to hold it. In most cases this is done during byte->char conversions or via InputStream. Unlike
+     * InputStream, this interface allows the app to process data in place, without copy.
+     *
+     */
+    public int doRead(ByteChunk chunk) 
+        throws IOException {
+        int n = inputBuffer.doRead(chunk, this);
+        if (n > 0) {
+            bytesRead+=n;
+        }
+        return n;
+    }
+
+
+    // -------------------- debug --------------------
+
+    @Override
+    public String toString() {
+        return "R( " + requestURI().toString() + ")";
+    }
+
+    public long getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(long startTime) {
+        this.startTime = startTime;
+    }
+
+    // -------------------- Per-Request "notes" --------------------
+
+
+    /** 
+     * Used to store private data. Thread data could be used instead - but 
+     * if you have the req, getting/setting a note is just a array access, may
+     * be faster than ThreadLocal for very frequent operations.
+     * 
+     *  Example use: 
+     *   Jk:
+     *     HandlerRequest.HOSTBUFFER = 10 CharChunk, buffer for Host decoding
+     *     WorkerEnv: SSL_CERT_NOTE=16 - MessageBytes containing the cert
+     *                
+     *   Catalina CoyoteAdapter:
+     *      ADAPTER_NOTES = 1 - stores the HttpServletRequest object ( req/res)             
+     *      
+     *   To avoid conflicts, note in the range 0 - 8 are reserved for the 
+     *   servlet container ( catalina connector, etc ), and values in 9 - 16 
+     *   for connector use. 
+     *   
+     *   17-31 range is not allocated or used.
+     */
+    public final void setNote(int pos, Object value) {
+        notes[pos] = value;
+    }
+
+
+    public final Object getNote(int pos) {
+        return notes[pos];
+    }
+
+
+    // -------------------- Recycling -------------------- 
+
+
+    public void recycle() {
+        bytesRead=0;
+
+        contentLength = -1;
+        contentTypeMB = null;
+        charEncoding = null;
+        headers.recycle();
+        serverNameMB.recycle();
+        serverPort=-1;
+        localPort = -1;
+        remotePort = -1;
+        available = 0;
+
+        cookies.recycle();
+        parameters.recycle();
+
+        unparsedURIMB.recycle();
+        uriMB.recycle(); 
+        decodedUriMB.recycle();
+        queryMB.recycle();
+        methodMB.recycle();
+        protoMB.recycle();
+
+        schemeMB.recycle();
+
+        instanceId.recycle();
+        remoteUser.recycle();
+        authType.recycle();
+        attributes.clear();
+    }
+
+    // -------------------- Info  --------------------
+    public void updateCounters() {
+        reqProcessorMX.updateCounters();
+    }
+
+    public RequestInfo getRequestProcessor() {
+        return reqProcessorMX;
+    }
+
+    public int getBytesRead() {
+        return bytesRead;
+    }
+
+    public boolean isProcessing() {
+        return reqProcessorMX.getStage()==org.apache.coyote.Constants.STAGE_SERVICE;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/RequestGroupInfo.java b/bundles/org.apache.tomcat/src/org/apache/coyote/RequestGroupInfo.java
new file mode 100644
index 0000000..a90a6c3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/RequestGroupInfo.java
@@ -0,0 +1,164 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import java.util.ArrayList;
+
+/** This can be moved to top level ( eventually with a better name ).
+ *  It is currently used only as a JMX artifact, to aggregate the data
+ *  collected from each RequestProcessor thread.
+ */
+public class RequestGroupInfo {
+    ArrayList<RequestInfo> processors=new ArrayList<RequestInfo>();
+    private long deadMaxTime = 0;
+    private long deadProcessingTime = 0;
+    private int deadRequestCount = 0;
+    private int deadErrorCount = 0;
+    private long deadBytesReceived = 0;
+    private long deadBytesSent = 0;
+
+    public synchronized void addRequestProcessor( RequestInfo rp ) {
+        processors.add( rp );
+    }
+
+    public synchronized void removeRequestProcessor( RequestInfo rp ) {
+        if( rp != null ) {
+            if( deadMaxTime < rp.getMaxTime() )
+                deadMaxTime = rp.getMaxTime();
+            deadProcessingTime += rp.getProcessingTime();
+            deadRequestCount += rp.getRequestCount();
+            deadErrorCount += rp.getErrorCount();
+            deadBytesReceived += rp.getBytesReceived();
+            deadBytesSent += rp.getBytesSent();
+
+            processors.remove( rp );
+        }
+    }
+
+    public synchronized long getMaxTime() {
+        long maxTime=deadMaxTime;
+        for( int i=0; i<processors.size(); i++ ) {
+            RequestInfo rp=processors.get( i );
+            if( maxTime < rp.getMaxTime() ) maxTime=rp.getMaxTime();
+        }
+        return maxTime;
+    }
+
+    // Used to reset the times
+    public synchronized void setMaxTime(long maxTime) {
+        deadMaxTime = maxTime;
+        for( int i=0; i<processors.size(); i++ ) {
+            RequestInfo rp=processors.get( i );
+            rp.setMaxTime(maxTime);
+        }
+    }
+
+    public synchronized long getProcessingTime() {
+        long time=deadProcessingTime;
+        for( int i=0; i<processors.size(); i++ ) {
+            RequestInfo rp=processors.get( i );
+            time += rp.getProcessingTime();
+        }
+        return time;
+    }
+
+    public synchronized void setProcessingTime(long totalTime) {
+        deadProcessingTime = totalTime;
+        for( int i=0; i<processors.size(); i++ ) {
+            RequestInfo rp=processors.get( i );
+            rp.setProcessingTime( totalTime );
+        }
+    }
+
+    public synchronized int getRequestCount() {
+        int requestCount=deadRequestCount;
+        for( int i=0; i<processors.size(); i++ ) {
+            RequestInfo rp=processors.get( i );
+            requestCount += rp.getRequestCount();
+        }
+        return requestCount;
+    }
+
+    public synchronized void setRequestCount(int requestCount) {
+        deadRequestCount = requestCount;
+        for( int i=0; i<processors.size(); i++ ) {
+            RequestInfo rp=processors.get( i );
+            rp.setRequestCount( requestCount );
+        }
+    }
+
+    public synchronized int getErrorCount() {
+        int requestCount=deadErrorCount;
+        for( int i=0; i<processors.size(); i++ ) {
+            RequestInfo rp=processors.get( i );
+            requestCount += rp.getErrorCount();
+        }
+        return requestCount;
+    }
+
+    public synchronized void setErrorCount(int errorCount) {
+        deadErrorCount = errorCount;
+        for( int i=0; i<processors.size(); i++ ) {
+            RequestInfo rp=processors.get( i );
+            rp.setErrorCount( errorCount);
+        }
+    }
+
+    public synchronized long getBytesReceived() {
+        long bytes=deadBytesReceived;
+        for( int i=0; i<processors.size(); i++ ) {
+            RequestInfo rp=processors.get( i );
+            bytes += rp.getBytesReceived();
+        }
+        return bytes;
+    }
+
+    public synchronized void setBytesReceived(long bytesReceived) {
+        deadBytesReceived = bytesReceived;
+        for( int i=0; i<processors.size(); i++ ) {
+            RequestInfo rp=processors.get( i );
+            rp.setBytesReceived( bytesReceived );
+        }
+    }
+
+    public synchronized long getBytesSent() {
+        long bytes=deadBytesSent;
+        for( int i=0; i<processors.size(); i++ ) {
+            RequestInfo rp=processors.get( i );
+            bytes += rp.getBytesSent();
+        }
+        return bytes;
+    }
+
+    public synchronized void setBytesSent(long bytesSent) {
+        deadBytesSent = bytesSent;
+        for( int i=0; i<processors.size(); i++ ) {
+            RequestInfo rp=processors.get( i );
+            rp.setBytesSent( bytesSent );
+        }
+    }
+
+    public void resetCounters() {
+        this.setBytesReceived(0);
+        this.setBytesSent(0);
+        this.setRequestCount(0);
+        this.setProcessingTime(0);
+        this.setMaxTime(0);
+        this.setErrorCount(0);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/RequestInfo.java b/bundles/org.apache.tomcat/src/org/apache/coyote/RequestInfo.java
new file mode 100644
index 0000000..c142cf6
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/RequestInfo.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.coyote;
+
+import javax.management.ObjectName;
+
+
+/**
+ * Structure holding the Request and Response objects. It also holds statistical
+ * informations about request processing and provide management informations
+ * about the requests being processed.
+ *
+ * Each thread uses a Request/Response pair that is recycled on each request.
+ * This object provides a place to collect global low-level statistics - without
+ * having to deal with synchronization ( since each thread will have it's own
+ * RequestProcessorMX ).
+ *
+ * TODO: Request notifications will be registered here.
+ *
+ * @author Costin Manolache
+ */
+public class RequestInfo  {
+    RequestGroupInfo global=null;
+
+    // ----------------------------------------------------------- Constructors
+
+    public RequestInfo( Request req) {
+        this.req=req;
+    }
+
+    public RequestGroupInfo getGlobalProcessor() {
+        return global;
+    }
+    
+    public void setGlobalProcessor(RequestGroupInfo global) {
+        if( global != null) {
+            this.global=global;
+            global.addRequestProcessor( this );
+        } else {
+            if (this.global != null) {
+                this.global.removeRequestProcessor( this ); 
+                this.global = null;
+            }
+        }
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+    Request req;
+    int stage = Constants.STAGE_NEW;
+    String workerThreadName;
+    ObjectName rpName;
+
+    // -------------------- Information about the current request  -----------
+    // This is useful for long-running requests only
+
+    public String getMethod() {
+        return req.method().toString();
+    }
+
+    public String getCurrentUri() {
+        return req.requestURI().toString();
+    }
+
+    public String getCurrentQueryString() {
+        return req.queryString().toString();
+    }
+
+    public String getProtocol() {
+        return req.protocol().toString();
+    }
+
+    public String getVirtualHost() {
+        return req.serverName().toString();
+    }
+
+    public int getServerPort() {
+        return req.getServerPort();
+    }
+
+    public String getRemoteAddr() {
+        req.action(ActionCode.REQ_HOST_ADDR_ATTRIBUTE, null);
+        return req.remoteAddr().toString();
+    }
+
+    public int getContentLength() {
+        return req.getContentLength();
+    }
+
+    public long getRequestBytesReceived() {
+        return req.getBytesRead();
+    }
+
+    public long getRequestBytesSent() {
+        return req.getResponse().getContentWritten();
+    }
+
+    public long getRequestProcessingTime() {
+        if ( getStage() == org.apache.coyote.Constants.STAGE_ENDED ) return 0;
+        else return (System.currentTimeMillis() - req.getStartTime());
+    }
+
+    // -------------------- Statistical data  --------------------
+    // Collected at the end of each request.
+    private long bytesSent;
+    private long bytesReceived;
+
+    // Total time = divide by requestCount to get average.
+    private long processingTime;
+    // The longest response time for a request
+    private long maxTime;
+    // URI of the request that took maxTime
+    private String maxRequestUri;
+
+    private int requestCount;
+    // number of response codes >= 400
+    private int errorCount;
+    
+    //the time of the last request
+    private long lastRequestProcessingTime = 0;
+
+
+    /** Called by the processor before recycling the request. It'll collect
+     * statistic information.
+     */
+    void updateCounters() {
+        bytesReceived+=req.getBytesRead();
+        bytesSent+=req.getResponse().getContentWritten();
+
+        requestCount++;
+        if( req.getResponse().getStatus() >=400 )
+            errorCount++;
+        long t0=req.getStartTime();
+        long t1=System.currentTimeMillis();
+        long time=t1-t0;
+        this.lastRequestProcessingTime = time;
+        processingTime+=time;
+        if( maxTime < time ) {
+            maxTime=time;
+            maxRequestUri=req.requestURI().toString();
+        }
+    }
+
+    public int getStage() {
+        return stage;
+    }
+
+    public void setStage(int stage) {
+        this.stage = stage;
+    }
+
+    public long getBytesSent() {
+        return bytesSent;
+    }
+
+    public void setBytesSent(long bytesSent) {
+        this.bytesSent = bytesSent;
+    }
+
+    public long getBytesReceived() {
+        return bytesReceived;
+    }
+
+    public void setBytesReceived(long bytesReceived) {
+        this.bytesReceived = bytesReceived;
+    }
+
+    public long getProcessingTime() {
+        return processingTime;
+    }
+
+    public void setProcessingTime(long processingTime) {
+        this.processingTime = processingTime;
+    }
+
+    public long getMaxTime() {
+        return maxTime;
+    }
+
+    public void setMaxTime(long maxTime) {
+        this.maxTime = maxTime;
+    }
+
+    public String getMaxRequestUri() {
+        return maxRequestUri;
+    }
+
+    public void setMaxRequestUri(String maxRequestUri) {
+        this.maxRequestUri = maxRequestUri;
+    }
+
+    public int getRequestCount() {
+        return requestCount;
+    }
+
+    public void setRequestCount(int requestCount) {
+        this.requestCount = requestCount;
+    }
+
+    public int getErrorCount() {
+        return errorCount;
+    }
+
+    public void setErrorCount(int errorCount) {
+        this.errorCount = errorCount;
+    }
+
+    public String getWorkerThreadName() {
+        return workerThreadName;
+    }
+
+    public ObjectName getRpName() {
+        return rpName;
+    }
+
+    public long getLastRequestProcessingTime() {
+        return lastRequestProcessingTime;
+    }
+
+    public void setWorkerThreadName(String workerThreadName) {
+        this.workerThreadName = workerThreadName;
+    }
+
+    public void setRpName(ObjectName rpName) {
+        this.rpName = rpName;
+    }
+
+    public void setLastRequestProcessingTime(long lastRequestProcessingTime) {
+        this.lastRequestProcessingTime = lastRequestProcessingTime;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/Response.java b/bundles/org.apache.tomcat/src/org/apache/coyote/Response.java
new file mode 100644
index 0000000..88f1bbd
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/Response.java
@@ -0,0 +1,573 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.http.MimeHeaders;
+
+/**
+ * Response object.
+ * 
+ * @author James Duncan Davidson [duncan@eng.sun.com]
+ * @author Jason Hunter [jch@eng.sun.com]
+ * @author James Todd [gonzo@eng.sun.com]
+ * @author Harish Prabandham
+ * @author Hans Bergsten <hans@gefionsoftware.com>
+ * @author Remy Maucherat
+ */
+public final class Response {
+
+    // ----------------------------------------------------- Class Variables
+
+    /**
+     * Default locale as mandated by the spec.
+     */
+    private static Locale DEFAULT_LOCALE = Locale.getDefault();
+
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * Status code.
+     */
+    protected int status = 200;
+
+
+    /**
+     * Status message.
+     */
+    protected String message = null;
+
+
+    /**
+     * Response headers.
+     */
+    protected MimeHeaders headers = new MimeHeaders();
+
+
+    /**
+     * Associated output buffer.
+     */
+    protected OutputBuffer outputBuffer;
+
+
+    /**
+     * Notes.
+     */
+    protected Object notes[] = new Object[Constants.MAX_NOTES];
+
+
+    /**
+     * Committed flag.
+     */
+    protected boolean commited = false;
+
+
+    /**
+     * Action hook.
+     */
+    public ActionHook hook;
+
+
+    /**
+     * HTTP specific fields.
+     */
+    protected String contentType = null;
+    protected String contentLanguage = null;
+    protected String characterEncoding = Constants.DEFAULT_CHARACTER_ENCODING;
+    protected long contentLength = -1;
+    private Locale locale = DEFAULT_LOCALE;
+
+    // General informations
+    private long contentWritten = 0;
+
+    /**
+     * Holds request error exception.
+     */
+    protected Exception errorException = null;
+
+    /**
+     * Has the charset been explicitly set.
+     */
+    protected boolean charsetSet = false;
+
+    protected Request req;
+
+    // ------------------------------------------------------------- Properties
+
+    public Request getRequest() {
+        return req;
+    }
+
+    public void setRequest( Request req ) {
+        this.req=req;
+    }
+
+    public OutputBuffer getOutputBuffer() {
+        return outputBuffer;
+    }
+
+
+    public void setOutputBuffer(OutputBuffer outputBuffer) {
+        this.outputBuffer = outputBuffer;
+    }
+
+
+    public MimeHeaders getMimeHeaders() {
+        return headers;
+    }
+
+
+    public ActionHook getHook() {
+        return hook;
+    }
+
+
+    public void setHook(ActionHook hook) {
+        this.hook = hook;
+    }
+
+
+    // -------------------- Per-Response "notes" --------------------
+
+
+    public final void setNote(int pos, Object value) {
+        notes[pos] = value;
+    }
+
+
+    public final Object getNote(int pos) {
+        return notes[pos];
+    }
+
+
+    // -------------------- Actions --------------------
+
+
+    public void action(ActionCode actionCode, Object param) {
+        if (hook != null) {
+            if( param==null ) 
+                hook.action(actionCode, this);
+            else
+                hook.action(actionCode, param);
+        }
+    }
+
+
+    // -------------------- State --------------------
+
+
+    public int getStatus() {
+        return status;
+    }
+
+    
+    /** 
+     * Set the response status 
+     */ 
+    public void setStatus( int status ) {
+        this.status = status;
+    }
+
+
+    /**
+     * Get the status message.
+     */
+    public String getMessage() {
+        return message;
+    }
+
+
+    /**
+     * Set the status message.
+     */
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+
+    public boolean isCommitted() {
+        return commited;
+    }
+
+
+    public void setCommitted(boolean v) {
+        this.commited = v;
+    }
+
+
+    // -----------------Error State --------------------
+
+
+    /** 
+     * Set the error Exception that occurred during
+     * request processing.
+     */
+    public void setErrorException(Exception ex) {
+    errorException = ex;
+    }
+
+
+    /** 
+     * Get the Exception that occurred during request
+     * processing.
+     */
+    public Exception getErrorException() {
+        return errorException;
+    }
+
+
+    public boolean isExceptionPresent() {
+        return ( errorException != null );
+    }
+
+
+    // -------------------- Methods --------------------
+    
+    
+    public void reset() 
+        throws IllegalStateException {
+        
+        // Reset the headers only if this is the main request,
+        // not for included
+        contentType = null;
+        locale = DEFAULT_LOCALE;
+        contentLanguage = null;
+        characterEncoding = Constants.DEFAULT_CHARACTER_ENCODING;
+        contentLength = -1;
+        charsetSet = false;
+
+        status = 200;
+        message = null;
+        headers.clear();
+        
+        // Force the PrintWriter to flush its data to the output
+        // stream before resetting the output stream
+        //
+        // Reset the stream
+        if (commited) {
+            //String msg = sm.getString("servletOutputStreamImpl.reset.ise"); 
+            throw new IllegalStateException();
+        }
+        
+        action(ActionCode.RESET, this);
+    }
+
+
+    public void finish() {
+        action(ActionCode.CLOSE, this);
+    }
+
+
+    public void acknowledge() {
+        action(ActionCode.ACK, this);
+    }
+
+
+    // -------------------- Headers --------------------
+    /**
+     * Warning: This method always returns <code>false<code> for Content-Type
+     * and Content-Length.
+     */
+    public boolean containsHeader(String name) {
+        return headers.getHeader(name) != null;
+    }
+
+
+    public void setHeader(String name, String value) {
+        char cc=name.charAt(0);
+        if( cc=='C' || cc=='c' ) {
+            if( checkSpecialHeader(name, value) )
+            return;
+        }
+        headers.setValue(name).setString( value);
+    }
+
+
+    public void addHeader(String name, String value) {
+        char cc=name.charAt(0);
+        if( cc=='C' || cc=='c' ) {
+            if( checkSpecialHeader(name, value) )
+            return;
+        }
+        headers.addValue(name).setString( value );
+    }
+
+    
+    /** 
+     * Set internal fields for special header names. 
+     * Called from set/addHeader.
+     * Return true if the header is special, no need to set the header.
+     */
+    private boolean checkSpecialHeader( String name, String value) {
+        // XXX Eliminate redundant fields !!!
+        // ( both header and in special fields )
+        if( name.equalsIgnoreCase( "Content-Type" ) ) {
+            setContentType( value );
+            return true;
+        }
+        if( name.equalsIgnoreCase( "Content-Length" ) ) {
+            try {
+                long cL=Long.parseLong( value );
+                setContentLength( cL );
+                return true;
+            } catch( NumberFormatException ex ) {
+                // Do nothing - the spec doesn't have any "throws" 
+                // and the user might know what he's doing
+                return false;
+            }
+        }
+        if( name.equalsIgnoreCase( "Content-Language" ) ) {
+            // XXX XXX Need to construct Locale or something else
+        }
+        return false;
+    }
+
+
+    /** Signal that we're done with the headers, and body will follow.
+     *  Any implementation needs to notify ContextManager, to allow
+     *  interceptors to fix headers.
+     */
+    public void sendHeaders() {
+        action(ActionCode.COMMIT, this);
+        commited = true;
+    }
+
+
+    // -------------------- I18N --------------------
+
+
+    public Locale getLocale() {
+        return locale;
+    }
+
+    /**
+     * Called explicitly by user to set the Content-Language and
+     * the default encoding
+     */
+    public void setLocale(Locale locale) {
+
+        if (locale == null) {
+            return;  // throw an exception?
+        }
+
+        // Save the locale for use by getLocale()
+        this.locale = locale;
+
+        // Set the contentLanguage for header output
+        contentLanguage = locale.getLanguage();
+        if ((contentLanguage != null) && (contentLanguage.length() > 0)) {
+            String country = locale.getCountry();
+            StringBuilder value = new StringBuilder(contentLanguage);
+            if ((country != null) && (country.length() > 0)) {
+                value.append('-');
+                value.append(country);
+            }
+            contentLanguage = value.toString();
+        }
+
+    }
+
+    /**
+     * Return the content language.
+     */
+    public String getContentLanguage() {
+        return contentLanguage;
+    }
+
+    /*
+     * Overrides the name of the character encoding used in the body
+     * of the response. This method must be called prior to writing output
+     * using getWriter().
+     *
+     * @param charset String containing the name of the character encoding.
+     */
+    public void setCharacterEncoding(String charset) {
+
+        if (isCommitted())
+            return;
+        if (charset == null)
+            return;
+
+        characterEncoding = charset;
+        charsetSet=true;
+    }
+
+    public String getCharacterEncoding() {
+        return characterEncoding;
+    }
+
+    /**
+     * Sets the content type.
+     *
+     * This method must preserve any response charset that may already have 
+     * been set via a call to response.setContentType(), response.setLocale(),
+     * or response.setCharacterEncoding().
+     *
+     * @param type the content type
+     */
+    @SuppressWarnings("deprecation")
+    public void setContentType(String type) {
+
+        int semicolonIndex = -1;
+
+        if (type == null) {
+            this.contentType = null;
+            return;
+        }
+
+        /*
+         * Remove the charset param (if any) from the Content-Type, and use it
+         * to set the response encoding.
+         * The most recent response encoding setting will be appended to the
+         * response's Content-Type (as its charset param) by getContentType();
+         */
+        boolean hasCharset = false;
+        int len = type.length();
+        int index = type.indexOf(';');
+        while (index != -1) {
+            semicolonIndex = index;
+            index++;
+            // Yes, isSpace() is deprecated but it does exactly what we need
+            while (index < len && Character.isSpace(type.charAt(index))) {
+                index++;
+            }
+            if (index+8 < len
+                    && type.charAt(index) == 'c'
+                    && type.charAt(index+1) == 'h'
+                    && type.charAt(index+2) == 'a'
+                    && type.charAt(index+3) == 'r'
+                    && type.charAt(index+4) == 's'
+                    && type.charAt(index+5) == 'e'
+                    && type.charAt(index+6) == 't'
+                    && type.charAt(index+7) == '=') {
+                hasCharset = true;
+                break;
+            }
+            index = type.indexOf(';', index);
+        }
+
+        if (!hasCharset) {
+            this.contentType = type;
+            return;
+        }
+
+        this.contentType = type.substring(0, semicolonIndex);
+        String tail = type.substring(index+8);
+        int nextParam = tail.indexOf(';');
+        String charsetValue = null;
+        if (nextParam != -1) {
+            this.contentType += tail.substring(nextParam);
+            charsetValue = tail.substring(0, nextParam);
+        } else {
+            charsetValue = tail;
+        }
+
+        // The charset value may be quoted, but must not contain any quotes.
+        if (charsetValue != null && charsetValue.length() > 0) {
+            charsetSet=true;
+            charsetValue = charsetValue.replace('"', ' ');
+            this.characterEncoding = charsetValue.trim();
+        }
+    }
+
+    public String getContentType() {
+
+        String ret = contentType;
+
+        if (ret != null 
+            && characterEncoding != null
+            && charsetSet) {
+            ret = ret + ";charset=" + characterEncoding;
+        }
+
+        return ret;
+    }
+    
+    public void setContentLength(int contentLength) {
+        this.contentLength = contentLength;
+    }
+
+    public void setContentLength(long contentLength) {
+        this.contentLength = contentLength;
+    }
+
+    public int getContentLength() {
+        long length = getContentLengthLong();
+        
+        if (length < Integer.MAX_VALUE) {
+            return (int) length;
+        }
+        return -1;
+    }
+    
+    public long getContentLengthLong() {
+        return contentLength;
+    }
+
+
+    /** 
+     * Write a chunk of bytes.
+     */
+    public void doWrite(ByteChunk chunk/*byte buffer[], int pos, int count*/)
+        throws IOException
+    {
+        outputBuffer.doWrite(chunk, this);
+        contentWritten+=chunk.getLength();
+    }
+
+    // --------------------
+    
+    public void recycle() {
+        
+        contentType = null;
+        contentLanguage = null;
+        locale = DEFAULT_LOCALE;
+        characterEncoding = Constants.DEFAULT_CHARACTER_ENCODING;
+        charsetSet = false;
+        contentLength = -1;
+        status = 200;
+        message = null;
+        commited = false;
+        errorException = null;
+        headers.clear();
+
+        // update counters
+        contentWritten=0;
+    }
+
+    /**
+     * Bytes written by application - i.e. before compression, chunking, etc.
+     */
+    public long getContentWritten() {
+        return contentWritten;
+    }
+    
+    /**
+     * Bytes written to socket - i.e. after compression, chunking, etc.
+     */
+    public long getBytesWritten(boolean flush) {
+        if (flush) {
+            action(ActionCode.CLIENT_FLUSH, this);
+        }
+        return outputBuffer.getBytesWritten();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/BufferedInputFilter.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/BufferedInputFilter.java
new file mode 100644
index 0000000..14dc25e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/BufferedInputFilter.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.coyote.http11.filters;
+
+import java.io.IOException;
+
+import org.apache.coyote.InputBuffer;
+import org.apache.coyote.Request;
+import org.apache.coyote.http11.InputFilter;
+import org.apache.tomcat.util.buf.ByteChunk;
+
+/**
+ * Input filter responsible for reading and buffering the request body, so that
+ * it does not interfere with client SSL handshake messages.
+ */
+public class BufferedInputFilter implements InputFilter {
+
+    // -------------------------------------------------------------- Constants
+
+    private static final String ENCODING_NAME = "buffered";
+    private static final ByteChunk ENCODING = new ByteChunk();
+
+
+    // ----------------------------------------------------- Instance Variables
+
+    private ByteChunk buffered = null;
+    private ByteChunk tempRead = new ByteChunk(1024);
+    private InputBuffer buffer;
+    private boolean hasRead = false;
+
+
+    // ----------------------------------------------------- Static Initializer
+
+    static {
+        ENCODING.setBytes(ENCODING_NAME.getBytes(), 0, ENCODING_NAME.length());
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Set the buffering limit. This should be reset every time the buffer is
+     * used.
+     */
+    public void setLimit(int limit) {
+        if (buffered == null) {
+            buffered = new ByteChunk(4048);
+            buffered.setLimit(limit);
+        }
+    }
+
+
+    // ---------------------------------------------------- InputBuffer Methods
+
+
+    /**
+     * Reads the request body and buffers it.
+     */
+    @Override
+    public void setRequest(Request request) {
+        // save off the Request body
+        try {
+            while (buffer.doRead(tempRead, request) >= 0) {
+                buffered.append(tempRead);
+                tempRead.recycle();
+            }
+        } catch(IOException ioe) {
+            // No need for i18n - this isn't going to get logged anywhere
+            throw new IllegalStateException(
+                    "Request body too large for buffer");
+        }
+    }
+
+    /**
+     * Fills the given ByteChunk with the buffered request body.
+     */
+    @Override
+    public int doRead(ByteChunk chunk, Request request) throws IOException {
+        if (hasRead || buffered.getLength() <= 0) {
+            return -1;
+        }
+
+        chunk.setBytes(buffered.getBytes(), buffered.getStart(),
+                buffered.getLength());
+        hasRead = true;
+        return chunk.getLength();
+    }
+
+    @Override
+    public void setBuffer(InputBuffer buffer) {
+        this.buffer = buffer;
+    }
+
+    @Override
+    public void recycle() {
+        if (buffered != null) {
+            if (buffered.getBuffer().length > 65536) {
+                buffered = null;
+            } else {
+                buffered.recycle();
+            }
+        }
+        tempRead.recycle();
+        hasRead = false;
+        buffer = null;
+    }
+
+    @Override
+    public ByteChunk getEncodingName() {
+        return ENCODING;
+    }
+
+    @Override
+    public long end() throws IOException {
+        return 0;
+    }
+
+    @Override
+    public int available() {
+        return buffered.getLength();
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/ChunkedInputFilter.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/ChunkedInputFilter.java
new file mode 100644
index 0000000..a48ae35
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/ChunkedInputFilter.java
@@ -0,0 +1,534 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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.filters;
+
+import java.io.EOFException;
+import java.io.IOException;
+
+import org.apache.coyote.InputBuffer;
+import org.apache.coyote.Request;
+import org.apache.coyote.http11.Constants;
+import org.apache.coyote.http11.InputFilter;
+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;
+
+/**
+ * Chunked input filter. Parses chunked data according to
+ * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1">http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1</a><br>
+ * 
+ * @author Remy Maucherat
+ * @author Filip Hanik
+ */
+public class ChunkedInputFilter implements InputFilter {
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    protected static final String ENCODING_NAME = "chunked";
+    protected static final ByteChunk ENCODING = new ByteChunk();
+
+
+    // ----------------------------------------------------- Static Initializer
+
+
+    static {
+        ENCODING.setBytes(ENCODING_NAME.getBytes(), 0, ENCODING_NAME.length());
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Next buffer in the pipeline.
+     */
+    protected InputBuffer buffer;
+
+
+    /**
+     * Number of bytes remaining in the current chunk.
+     */
+    protected int remaining = 0;
+
+
+    /**
+     * Position in the buffer.
+     */
+    protected int pos = 0;
+
+
+    /**
+     * Last valid byte in the buffer.
+     */
+    protected int lastValid = 0;
+
+
+    /**
+     * Read bytes buffer.
+     */
+    protected byte[] buf = null;
+
+
+    /**
+     * Byte chunk used to read bytes.
+     */
+    protected ByteChunk readChunk = new ByteChunk();
+
+
+    /**
+     * Flag set to true when the end chunk has been read.
+     */
+    protected boolean endChunk = false;
+
+
+    /**
+     * Byte chunk used to store trailing headers.
+     */
+    protected ByteChunk trailingHeaders = new ByteChunk();
+
+    /**
+     * Flag set to true if the next call to doRead() must parse a CRLF pair
+     * before doing anything else.
+     */
+    protected boolean needCRLFParse = false;
+
+
+    /**
+     * Request being parsed.
+     */
+    private Request request;
+    
+    // ----------------------------------------------------------- Constructors
+    public ChunkedInputFilter(int maxTrailerSize) {
+        this.trailingHeaders.setLimit(maxTrailerSize);
+    }
+
+    // ---------------------------------------------------- InputBuffer Methods
+
+
+    /**
+     * Read bytes.
+     * 
+     * @return If the filter does request length control, this value is
+     * significant; it should be the number of bytes consumed from the buffer,
+     * up until the end of the current request body, or the buffer length, 
+     * whichever is greater. If the filter does not do request body length
+     * control, the returned value should be -1.
+     */
+    @Override
+    public int doRead(ByteChunk chunk, Request req)
+        throws IOException {
+
+        if (endChunk)
+            return -1;
+
+        if(needCRLFParse) {
+            needCRLFParse = false;
+            parseCRLF();
+        }
+
+        if (remaining <= 0) {
+            if (!parseChunkHeader()) {
+                throw new IOException("Invalid chunk header");
+            }
+            if (endChunk) {
+                parseEndChunk();
+                return -1;
+            }
+        }
+
+        int result = 0;
+
+        if (pos >= lastValid) {
+            readBytes();
+        }
+
+        if (remaining > (lastValid - pos)) {
+            result = lastValid - pos;
+            remaining = remaining - result;
+            chunk.setBytes(buf, pos, result);
+            pos = lastValid;
+        } else {
+            result = remaining;
+            chunk.setBytes(buf, pos, remaining);
+            pos = pos + remaining;
+            remaining = 0;
+            //we need a CRLF
+            if ((pos+1) >= lastValid) {   
+                //if we call parseCRLF we overrun the buffer here
+                //so we defer it to the next call BZ 11117
+                needCRLFParse = true;
+            } else {
+                parseCRLF(); //parse the CRLF immediately
+            }
+        }
+
+        return result;
+
+    }
+
+
+    // ---------------------------------------------------- InputFilter Methods
+
+
+    /**
+     * Read the content length from the request.
+     */
+    @Override
+    public void setRequest(Request request) {
+        this.request = request;
+    }
+
+
+    /**
+     * End the current request.
+     */
+    @Override
+    public long end()
+        throws IOException {
+
+        // Consume extra bytes : parse the stream until the end chunk is found
+        while (doRead(readChunk, null) >= 0) {
+            // NOOP: Just consume the input
+        }
+
+        // Return the number of extra bytes which were consumed
+        return (lastValid - pos);
+
+    }
+
+
+    /**
+     * Amount of bytes still available in a buffer.
+     */
+    @Override
+    public int available() {
+        return (lastValid - pos);
+    }
+    
+
+    /**
+     * Set the next buffer in the filter pipeline.
+     */
+    @Override
+    public void setBuffer(InputBuffer buffer) {
+        this.buffer = buffer;
+    }
+
+
+    /**
+     * Make the filter ready to process the next request.
+     */
+    @Override
+    public void recycle() {
+        remaining = 0;
+        pos = 0;
+        lastValid = 0;
+        endChunk = false;
+        trailingHeaders.recycle();
+    }
+
+
+    /**
+     * Return the name of the associated encoding; Here, the value is 
+     * "identity".
+     */
+    @Override
+    public ByteChunk getEncodingName() {
+        return ENCODING;
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Read bytes from the previous buffer.
+     */
+    protected int readBytes()
+        throws IOException {
+
+        int nRead = buffer.doRead(readChunk, null);
+        pos = readChunk.getStart();
+        lastValid = pos + nRead;
+        buf = readChunk.getBytes();
+
+        return nRead;
+
+    }
+
+
+    /**
+     * Parse the header of a chunk.
+     * A chunk header can look like 
+     * A10CRLF
+     * F23;chunk-extension to be ignoredCRLF
+     * The letters before CRLF but after the trailer mark, must be valid hex digits, 
+     * we should not parse F23IAMGONNAMESSTHISUP34CRLF as a valid header
+     * according to spec
+     */
+    protected boolean parseChunkHeader()
+        throws IOException {
+
+        int result = 0;
+        boolean eol = false;
+        boolean readDigit = false;
+        boolean trailer = false;
+
+        while (!eol) {
+
+            if (pos >= lastValid) {
+                if (readBytes() <= 0)
+                    return false;
+            }
+
+            if (buf[pos] == Constants.CR) {
+                // FIXME: Improve parsing to check for CRLF 
+            } else if (buf[pos] == Constants.LF) {
+                eol = true;
+            } else if (buf[pos] == Constants.SEMI_COLON) {
+                trailer = true;
+            } else if (!trailer) { 
+                //don't read data after the trailer
+                if (HexUtils.getDec(buf[pos]) != -1) {
+                    readDigit = true;
+                    result *= 16;
+                    result += HexUtils.getDec(buf[pos]);
+                } else {
+                    //we shouldn't allow invalid, non hex characters
+                    //in the chunked header
+                    return false;
+                }
+            }
+
+            pos++;
+
+        }
+
+        if (!readDigit)
+            return false;
+
+        if (result == 0)
+            endChunk = true;
+
+        remaining = result;
+        if (remaining < 0)
+            return false;
+
+        return true;
+
+    }
+
+
+    /**
+     * Parse CRLF at end of chunk.
+     */
+    protected boolean parseCRLF()
+        throws IOException {
+
+        boolean eol = false;
+        boolean crfound = false;
+
+        while (!eol) {
+
+            if (pos >= lastValid) {
+                if (readBytes() <= 0)
+                    throw new IOException("Invalid CRLF");
+            }
+
+            if (buf[pos] == Constants.CR) {
+                if (crfound) throw new IOException("Invalid CRLF, two CR characters encountered.");
+                crfound = true;
+            } else if (buf[pos] == Constants.LF) {
+                if (!crfound) throw new IOException("Invalid CRLF, no CR character encountered.");
+                eol = true;
+            } else {
+                throw new IOException("Invalid CRLF");
+            }
+
+            pos++;
+
+        }
+
+        return true;
+
+    }
+
+
+    /**
+     * Parse end chunk data.
+     */
+    protected void parseEndChunk() throws IOException {
+
+        // Handle option trailer headers
+        while (parseHeader()) {
+            // Loop until we run out of headers
+        }
+    }
+
+    
+    private boolean parseHeader() throws IOException {
+
+        MimeHeaders headers = request.getMimeHeaders();
+
+        byte chr = 0;
+        while (true) {
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (readBytes() <0)
+                    throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request");
+            }
+
+            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 = trailingHeaders.getEnd();
+    
+        //
+        // Reading the header name
+        // Header name is always US-ASCII
+        //
+    
+        boolean colon = false;
+        while (!colon) {
+    
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (readBytes() <0)
+                    throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request");
+            }
+    
+            chr = buf[pos];
+            if ((chr >= Constants.A) && (chr <= Constants.Z)) {
+                chr = (byte) (chr - Constants.LC_OFFSET);
+            }
+
+            if (chr == Constants.COLON) {
+                colon = true;
+            } else {
+                trailingHeaders.append(chr);
+            }
+    
+            pos++;
+    
+        }
+        MessageBytes headerValue = headers.addValue(trailingHeaders.getBytes(),
+                start, trailingHeaders.getEnd() - start);
+    
+        // Mark the current buffer position
+        start = trailingHeaders.getEnd();
+
+        //
+        // Reading the header value (which can be spanned over multiple lines)
+        //
+    
+        boolean eol = false;
+        boolean validLine = true;
+        int lastSignificantChar = 0;
+    
+        while (validLine) {
+    
+            boolean space = true;
+    
+            // Skipping spaces
+            while (space) {
+    
+                // Read new bytes if needed
+                if (pos >= lastValid) {
+                    if (readBytes() <0)
+                        throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request");
+                }
+    
+                chr = buf[pos];
+                if ((chr == Constants.SP) || (chr == Constants.HT)) {
+                    pos++;
+                } else {
+                    space = false;
+                }
+    
+            }
+    
+            // Reading bytes until the end of the line
+            while (!eol) {
+    
+                // Read new bytes if needed
+                if (pos >= lastValid) {
+                    if (readBytes() <0)
+                        throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request");
+                }
+    
+                chr = buf[pos];
+                if (chr == Constants.CR) {
+                    // Skip
+                } else if (chr == Constants.LF) {
+                    eol = true;
+                } else if (chr == Constants.SP) {
+                    trailingHeaders.append(chr);
+                } else {
+                    trailingHeaders.append(chr);
+                    lastSignificantChar = trailingHeaders.getEnd();
+                }
+    
+                pos++;
+    
+            }
+    
+            // 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 (readBytes() <0)
+                    throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request");
+            }
+    
+            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)
+                trailingHeaders.append(chr);
+            }
+    
+        }
+    
+        // Set the header value
+        headerValue.setBytes(trailingHeaders.getBytes(), start,
+                lastSignificantChar - start);
+    
+        return true;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/ChunkedOutputFilter.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/ChunkedOutputFilter.java
new file mode 100644
index 0000000..797f3e8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/ChunkedOutputFilter.java
@@ -0,0 +1,181 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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.filters;
+
+import java.io.IOException;
+
+import org.apache.coyote.OutputBuffer;
+import org.apache.coyote.Response;
+import org.apache.coyote.http11.OutputFilter;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.HexUtils;
+
+/**
+ * Chunked output filter.
+ * 
+ * @author Remy Maucherat
+ */
+public class ChunkedOutputFilter implements OutputFilter {
+
+
+    // -------------------------------------------------------------- Constants
+    /**
+     * End chunk.
+     */
+    protected static final ByteChunk END_CHUNK = new ByteChunk();
+
+
+    // ----------------------------------------------------- Static Initializer
+
+
+    static {
+        byte[] END_CHUNK_BYTES = {(byte) '0', (byte) '\r', (byte) '\n', 
+                                  (byte) '\r', (byte) '\n'};
+        END_CHUNK.setBytes(END_CHUNK_BYTES, 0, END_CHUNK_BYTES.length);
+    }
+
+
+    // ------------------------------------------------------------ Constructor
+
+
+    /**
+     * Default constructor.
+     */
+    public ChunkedOutputFilter() {
+        chunkLength = new byte[10];
+        chunkLength[8] = (byte) '\r';
+        chunkLength[9] = (byte) '\n';
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Next buffer in the pipeline.
+     */
+    protected OutputBuffer buffer;
+
+
+    /**
+     * Buffer used for chunk length conversion.
+     */
+    protected byte[] chunkLength = new byte[10];
+
+
+    /**
+     * Chunk header.
+     */
+    protected ByteChunk chunkHeader = new ByteChunk();
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    // --------------------------------------------------- OutputBuffer Methods
+
+
+    /**
+     * Write some bytes.
+     * 
+     * @return number of bytes written by the filter
+     */
+    @Override
+    public int doWrite(ByteChunk chunk, Response res)
+        throws IOException {
+
+        int result = chunk.getLength();
+
+        if (result <= 0) {
+            return 0;
+        }
+
+        // Calculate chunk header
+        int pos = 7;
+        int current = result;
+        while (current > 0) {
+            int digit = current % 16;
+            current = current / 16;
+            chunkLength[pos--] = HexUtils.getHex(digit);
+        }
+        chunkHeader.setBytes(chunkLength, pos + 1, 9 - pos);
+        buffer.doWrite(chunkHeader, res);
+
+        buffer.doWrite(chunk, res);
+
+        chunkHeader.setBytes(chunkLength, 8, 2);
+        buffer.doWrite(chunkHeader, res);
+
+        return result;
+
+    }
+
+
+    @Override
+    public long getBytesWritten() {
+        return buffer.getBytesWritten();
+    }
+
+
+    // --------------------------------------------------- OutputFilter Methods
+
+
+    /**
+     * 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.
+     */
+    @Override
+    public void setResponse(Response response) {
+        // NOOP: No need for parameters from response in this filter
+    }
+
+
+    /**
+     * Set the next buffer in the filter pipeline.
+     */
+    @Override
+    public void setBuffer(OutputBuffer buffer) {
+        this.buffer = buffer;
+    }
+
+
+    /**
+     * End the current request. It is acceptable to write extra bytes using
+     * buffer.doWrite during the execution of this method.
+     */
+    @Override
+    public long end()
+        throws IOException {
+
+        // Write end chunk
+        buffer.doWrite(END_CHUNK, null);
+        
+        return 0;
+
+    }
+
+
+    /**
+     * Make the filter ready to process the next request.
+     */
+    @Override
+    public void recycle() {
+        // NOOP: Nothing to recycle
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/FlushableGZIPOutputStream.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/FlushableGZIPOutputStream.java
new file mode 100644
index 0000000..1656a9c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/FlushableGZIPOutputStream.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.coyote.http11.filters;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.Deflater;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * Extension of {@link GZIPOutputStream} to workaround for a couple of long
+ * standing JDK bugs
+ * (<a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4255743">Bug
+ * 4255743</a> and
+ * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4813885">Bug
+ * 4813885</a>) so the GZIP'd output can be flushed. 
+ */
+public class FlushableGZIPOutputStream extends GZIPOutputStream {
+    public FlushableGZIPOutputStream(OutputStream os) throws IOException {
+        super(os);
+    }
+
+    private static final byte[] EMPTYBYTEARRAY = new byte[0];
+    private boolean hasData = false;
+
+    /**
+     * Here we make sure we have received data, so that the header has been for
+     * sure written to the output stream already.
+     */
+    @Override
+    public synchronized void write(byte[] bytes, int i, int i1)
+            throws IOException {
+        super.write(bytes, i, i1);
+        hasData = true;
+    }
+
+    @Override
+    public synchronized void write(int i) throws IOException {
+        super.write(i);
+        hasData = true;
+    }
+
+    @Override
+    public synchronized void write(byte[] bytes) throws IOException {
+        super.write(bytes);
+        hasData = true;
+    }
+
+    @Override
+    public synchronized void flush() throws IOException {
+        if (!hasData) {
+            return; // do not allow the gzip header to be flushed on its own
+        }
+
+        // trick the deflater to flush
+        /**
+         * Now this is tricky: We force the Deflater to flush its data by
+         * switching compression level. As yet, a perplexingly simple workaround
+         * for
+         * http://developer.java.sun.com/developer/bugParade/bugs/4255743.html
+         */
+        if (!def.finished()) {
+            def.setInput(EMPTYBYTEARRAY, 0, 0);
+
+            def.setLevel(Deflater.NO_COMPRESSION);
+            deflate();
+
+            def.setLevel(Deflater.DEFAULT_COMPRESSION);
+            deflate();
+
+            out.flush();
+        }
+
+        hasData = false; // no more data to flush
+    }
+
+    /*
+     * Keep on calling deflate until it runs dry. The default implementation
+     * only does it once and can therefore hold onto data when they need to be
+     * flushed out.
+     */
+    @Override
+    protected void deflate() throws IOException {
+        int len;
+        do {
+            len = def.deflate(buf, 0, buf.length);
+            if (len > 0) {
+                out.write(buf, 0, len);
+            }
+        } while (len != 0);
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/GzipOutputFilter.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/GzipOutputFilter.java
new file mode 100644
index 0000000..a7071f8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/GzipOutputFilter.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.coyote.http11.filters;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.GZIPOutputStream;
+
+import org.apache.coyote.OutputBuffer;
+import org.apache.coyote.Response;
+import org.apache.coyote.http11.OutputFilter;
+import org.apache.tomcat.util.buf.ByteChunk;
+
+/**
+ * Gzip output filter.
+ * 
+ * @author Remy Maucherat
+ */
+public class GzipOutputFilter implements OutputFilter {
+
+
+    /**
+     * Logger.
+     */
+    protected static org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog(GzipOutputFilter.class);
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Next buffer in the pipeline.
+     */
+    protected OutputBuffer buffer;
+
+
+    /**
+     * Compression output stream.
+     */
+    protected GZIPOutputStream compressionStream = null;
+
+
+    /**
+     * Fake internal output stream.
+     */
+    protected OutputStream fakeOutputStream = new FakeOutputStream();
+
+
+    // --------------------------------------------------- OutputBuffer Methods
+
+
+    /**
+     * Write some bytes.
+     * 
+     * @return number of bytes written by the filter
+     */
+    @Override
+    public int doWrite(ByteChunk chunk, Response res)
+        throws IOException {
+        if (compressionStream == null) {
+            compressionStream = new FlushableGZIPOutputStream(fakeOutputStream);
+        }
+        compressionStream.write(chunk.getBytes(), chunk.getStart(), 
+                                chunk.getLength());
+        return chunk.getLength();
+    }
+
+
+    @Override
+    public long getBytesWritten() {
+        return buffer.getBytesWritten();
+    }
+
+
+    // --------------------------------------------------- OutputFilter Methods
+
+    /**
+     * Added to allow flushing to happen for the gzip'ed outputstream
+     */
+    public void flush() {
+        if (compressionStream != null) {
+            try {
+                if (log.isDebugEnabled()) {
+                    log.debug("Flushing the compression stream!");
+                }
+                compressionStream.flush();
+            } catch (IOException e) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Ignored exception while flushing gzip filter", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * 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.
+     */
+    @Override
+    public void setResponse(Response response) {
+        // NOOP: No need for parameters from response in this filter
+    }
+
+
+    /**
+     * Set the next buffer in the filter pipeline.
+     */
+    @Override
+    public void setBuffer(OutputBuffer buffer) {
+        this.buffer = buffer;
+    }
+
+
+    /**
+     * End the current request. It is acceptable to write extra bytes using
+     * buffer.doWrite during the execution of this method.
+     */
+    @Override
+    public long end()
+        throws IOException {
+        if (compressionStream == null) {
+            compressionStream = new FlushableGZIPOutputStream(fakeOutputStream);
+        }
+        compressionStream.finish();
+        compressionStream.close();
+        return ((OutputFilter) buffer).end();
+    }
+
+
+    /**
+     * Make the filter ready to process the next request.
+     */
+    @Override
+    public void recycle() {
+        // Set compression stream to null
+        compressionStream = null;
+    }
+
+
+    // ------------------------------------------- FakeOutputStream Inner Class
+
+
+    protected class FakeOutputStream
+        extends OutputStream {
+        protected ByteChunk outputChunk = new ByteChunk();
+        protected byte[] singleByteBuffer = new byte[1];
+        @Override
+        public void write(int b)
+            throws IOException {
+            // Shouldn't get used for good performance, but is needed for 
+            // compatibility with Sun JDK 1.4.0
+            singleByteBuffer[0] = (byte) (b & 0xff);
+            outputChunk.setBytes(singleByteBuffer, 0, 1);
+            buffer.doWrite(outputChunk, null);
+        }
+        @Override
+        public void write(byte[] b, int off, int len)
+            throws IOException {
+            outputChunk.setBytes(b, off, len);
+            buffer.doWrite(outputChunk, null);
+        }
+        @Override
+        public void flush() throws IOException {/*NOOP*/}
+        @Override
+        public void close() throws IOException {/*NOOP*/}
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/IdentityInputFilter.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/IdentityInputFilter.java
new file mode 100644
index 0000000..873f8ea
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/IdentityInputFilter.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.coyote.http11.filters;
+
+import java.io.IOException;
+
+import org.apache.coyote.InputBuffer;
+import org.apache.coyote.Request;
+import org.apache.coyote.http11.InputFilter;
+import org.apache.tomcat.util.buf.ByteChunk;
+
+/**
+ * Identity input filter.
+ * 
+ * @author Remy Maucherat
+ */
+public class IdentityInputFilter implements InputFilter {
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    protected static final String ENCODING_NAME = "identity";
+    protected static final ByteChunk ENCODING = new ByteChunk();
+
+
+    // ----------------------------------------------------- Static Initializer
+
+
+    static {
+        ENCODING.setBytes(ENCODING_NAME.getBytes(), 0, ENCODING_NAME.length());
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Content length.
+     */
+    protected long contentLength = -1;
+
+
+    /**
+     * Remaining bytes.
+     */
+    protected long remaining = 0;
+
+
+    /**
+     * Next buffer in the pipeline.
+     */
+    protected InputBuffer buffer;
+
+
+    /**
+     * Chunk used to read leftover bytes.
+     */
+    protected ByteChunk endChunk = new ByteChunk();
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Get content length.
+     */
+    public long getContentLength() {
+        return contentLength;
+    }
+
+
+    /**
+     * Get remaining bytes.
+     */
+    public long getRemaining() {
+        return remaining;
+    }
+
+
+    // ---------------------------------------------------- InputBuffer Methods
+
+
+    /**
+     * Read bytes.
+     * 
+     * @return If the filter does request length control, this value is
+     * significant; it should be the number of bytes consumed from the buffer,
+     * up until the end of the current request body, or the buffer length, 
+     * whichever is greater. If the filter does not do request body length
+     * control, the returned value should be -1.
+     */
+    @Override
+    public int doRead(ByteChunk chunk, Request req)
+        throws IOException {
+
+        int result = -1;
+
+        if (contentLength >= 0) {
+            if (remaining > 0) {
+                int nRead = buffer.doRead(chunk, req);
+                if (nRead > remaining) {
+                    // The chunk is longer than the number of bytes remaining
+                    // in the body; changing the chunk length to the number
+                    // of bytes remaining
+                    chunk.setBytes(chunk.getBytes(), chunk.getStart(), 
+                                   (int) remaining);
+                    result = (int) remaining;
+                } else {
+                    result = nRead;
+                }
+                remaining = remaining - nRead;
+            } else {
+                // No more bytes left to be read : return -1 and clear the 
+                // buffer
+                chunk.recycle();
+                result = -1;
+            }
+        }
+
+        return result;
+
+    }
+
+
+    // ---------------------------------------------------- InputFilter Methods
+
+
+    /**
+     * Read the content length from the request.
+     */
+    @Override
+    public void setRequest(Request request) {
+        contentLength = request.getContentLengthLong();
+        remaining = contentLength;
+    }
+
+
+    /**
+     * End the current request.
+     */
+    @Override
+    public long end()
+        throws IOException {
+
+        // Consume extra bytes.
+        while (remaining > 0) {
+            int nread = buffer.doRead(endChunk, null);
+            if (nread > 0 ) {
+                remaining = remaining - nread;
+            } else { // errors are handled higher up.
+                remaining = 0;
+            }
+        }
+
+        // If too many bytes were read, return the amount.
+        return -remaining;
+
+    }
+
+
+    /**
+     * Amount of bytes still available in a buffer.
+     */
+    @Override
+    public int available() {
+        return 0;
+    }
+    
+
+    /**
+     * Set the next buffer in the filter pipeline.
+     */
+    @Override
+    public void setBuffer(InputBuffer buffer) {
+        this.buffer = buffer;
+    }
+
+
+    /**
+     * Make the filter ready to process the next request.
+     */
+    @Override
+    public void recycle() {
+        contentLength = -1;
+        remaining = 0;
+        endChunk.recycle();
+    }
+
+
+    /**
+     * Return the name of the associated encoding; Here, the value is 
+     * "identity".
+     */
+    @Override
+    public ByteChunk getEncodingName() {
+        return ENCODING;
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/IdentityOutputFilter.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/IdentityOutputFilter.java
new file mode 100644
index 0000000..83912a4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/IdentityOutputFilter.java
@@ -0,0 +1,155 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.coyote.http11.filters;
+
+import java.io.IOException;
+
+import org.apache.coyote.OutputBuffer;
+import org.apache.coyote.Response;
+import org.apache.coyote.http11.OutputFilter;
+import org.apache.tomcat.util.buf.ByteChunk;
+
+/**
+ * Identity output filter.
+ * 
+ * @author Remy Maucherat
+ */
+public class IdentityOutputFilter implements OutputFilter {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Content length.
+     */
+    protected long contentLength = -1;
+
+
+    /**
+     * Remaining bytes.
+     */
+    protected long remaining = 0;
+
+
+    /**
+     * Next buffer in the pipeline.
+     */
+    protected OutputBuffer buffer;
+
+
+    // --------------------------------------------------- OutputBuffer Methods
+
+
+    /**
+     * Write some bytes.
+     * 
+     * @return number of bytes written by the filter
+     */
+    @Override
+    public int doWrite(ByteChunk chunk, Response res)
+        throws IOException {
+
+        int result = -1;
+
+        if (contentLength >= 0) {
+            if (remaining > 0) {
+                result = chunk.getLength();
+                if (result > remaining) {
+                    // The chunk is longer than the number of bytes remaining
+                    // in the body; changing the chunk length to the number
+                    // of bytes remaining
+                    chunk.setBytes(chunk.getBytes(), chunk.getStart(), 
+                                   (int) remaining);
+                    result = (int) remaining;
+                    remaining = 0;
+                } else {
+                    remaining = remaining - result;
+                }
+                buffer.doWrite(chunk, res);
+            } else {
+                // No more bytes left to be written : return -1 and clear the 
+                // buffer
+                chunk.recycle();
+                result = -1;
+            }
+        } else {
+            // If no content length was set, just write the bytes
+            buffer.doWrite(chunk, res);
+            result = chunk.getLength();
+        }
+
+        return result;
+
+    }
+
+
+    @Override
+    public long getBytesWritten() {
+        return buffer.getBytesWritten();
+    }
+
+
+    // --------------------------------------------------- OutputFilter Methods
+
+
+    /**
+     * 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.
+     */
+    @Override
+    public void setResponse(Response response) {
+        contentLength = response.getContentLengthLong();
+        remaining = contentLength;
+    }
+
+
+    /**
+     * Set the next buffer in the filter pipeline.
+     */
+    @Override
+    public void setBuffer(OutputBuffer buffer) {
+        this.buffer = buffer;
+    }
+
+
+    /**
+     * End the current request. It is acceptable to write extra bytes using
+     * buffer.doWrite during the execution of this method.
+     */
+    @Override
+    public long end()
+        throws IOException {
+
+        if (remaining > 0)
+            return remaining;
+        return 0;
+
+    }
+
+
+    /**
+     * Make the filter ready to process the next request.
+     */
+    @Override
+    public void recycle() {
+        contentLength = -1;
+        remaining = 0;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/SavedRequestInputFilter.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/SavedRequestInputFilter.java
new file mode 100644
index 0000000..33289ba
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/SavedRequestInputFilter.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.coyote.http11.filters;
+
+import java.io.IOException;
+
+import org.apache.coyote.InputBuffer;
+import org.apache.coyote.http11.InputFilter;
+import org.apache.tomcat.util.buf.ByteChunk;
+
+/**
+ * Input filter responsible for replaying the request body when restoring the
+ * saved request after FORM authentication.
+ */
+public class SavedRequestInputFilter implements InputFilter {
+
+    /**
+     * The original request body.
+     */
+    protected ByteChunk input = null;
+
+    /**
+     * Create a new SavedRequestInputFilter.
+     * 
+     * @param input The saved request body to be replayed.
+     */
+    public SavedRequestInputFilter(ByteChunk input) {
+        this.input = input;
+    }
+
+    /**
+     * Read bytes.
+     */
+    @Override
+    public int doRead(ByteChunk chunk, org.apache.coyote.Request request)
+            throws IOException {
+        int writeLength = 0;
+        
+        if (chunk.getLimit() > 0 && chunk.getLimit() < input.getLength()) {
+            writeLength = chunk.getLimit();
+        } else {
+            writeLength = input.getLength();
+        }
+        
+        if(input.getOffset()>= input.getEnd())
+            return -1;
+        
+        input.substract(chunk.getBuffer(), 0, writeLength);
+        chunk.setOffset(0);
+        chunk.setEnd(writeLength);
+        
+        return writeLength;
+    }
+
+    /**
+     * Set the content length on the request.
+     */
+    @Override
+    public void setRequest(org.apache.coyote.Request request) {
+        request.setContentLength(input.getLength());
+    }
+
+    /**
+     * Make the filter ready to process the next request.
+     */
+    @Override
+    public void recycle() {
+        input = null;
+    }
+
+    /**
+     * Return the name of the associated encoding; here, the value is null.
+     */
+    @Override
+    public ByteChunk getEncodingName() {
+        return null;
+    }
+
+    /**
+     * Set the next buffer in the filter pipeline (has no effect).
+     */
+    @Override
+    public void setBuffer(InputBuffer buffer) {
+        // NOOP since this filter will be providing the request body
+    }
+
+    /**
+     * Amount of bytes still available in a buffer.
+     */
+    @Override
+    public int available() {
+        return input.getLength();
+    }
+    
+    /**
+     * End the current request (has no effect).
+     */
+    @Override
+    public long end() throws IOException {
+        return 0;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/VoidInputFilter.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/VoidInputFilter.java
new file mode 100644
index 0000000..05571fd
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/VoidInputFilter.java
@@ -0,0 +1,135 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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.filters;
+
+import java.io.IOException;
+
+import org.apache.coyote.InputBuffer;
+import org.apache.coyote.Request;
+import org.apache.coyote.http11.InputFilter;
+import org.apache.tomcat.util.buf.ByteChunk;
+
+/**
+ * Void input filter, which returns -1 when attempting a read. Used with a GET,
+ * HEAD, or a similar request.
+ * 
+ * @author Remy Maucherat
+ */
+public class VoidInputFilter implements InputFilter {
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    protected static final String ENCODING_NAME = "void";
+    protected static final ByteChunk ENCODING = new ByteChunk();
+
+
+    // ----------------------------------------------------- Static Initializer
+
+
+    static {
+        ENCODING.setBytes(ENCODING_NAME.getBytes(), 0, ENCODING_NAME.length());
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    // --------------------------------------------------- OutputBuffer Methods
+
+
+    /**
+     * Write some bytes.
+     * 
+     * @return number of bytes written by the filter
+     */
+    @Override
+    public int doRead(ByteChunk chunk, Request req)
+        throws IOException {
+
+        return -1;
+
+    }
+
+
+    // --------------------------------------------------- OutputFilter Methods
+
+
+    /**
+     * Set the associated request.
+     */
+    @Override
+    public void setRequest(Request request) {
+        // NOOP: Request isn't used so ignore it
+    }
+
+
+    /**
+     * Set the next buffer in the filter pipeline.
+     */
+    @Override
+    public void setBuffer(InputBuffer buffer) {
+        // NOOP: No body to read
+    }
+
+
+    /**
+     * Make the filter ready to process the next request.
+     */
+    @Override
+    public void recycle() {
+        // NOOP: Nothing to recycle
+    }
+
+
+    /**
+     * Return the name of the associated encoding; Here, the value is 
+     * "void".
+     */
+    @Override
+    public ByteChunk getEncodingName() {
+        return ENCODING;
+    }
+
+
+    /**
+     * 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.
+     */
+    @Override
+    public long end()
+        throws IOException {
+        return 0;
+    }
+
+
+    /**
+     * Amount of bytes still available in a buffer.
+     */
+    @Override
+    public int available() {
+        return 0;
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/VoidOutputFilter.java b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/VoidOutputFilter.java
new file mode 100644
index 0000000..ce3eb72
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/coyote/http11/filters/VoidOutputFilter.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.coyote.http11.filters;
+
+import java.io.IOException;
+
+import org.apache.coyote.OutputBuffer;
+import org.apache.coyote.Response;
+import org.apache.coyote.http11.OutputFilter;
+import org.apache.tomcat.util.buf.ByteChunk;
+
+/**
+ * Void output filter, which silently swallows bytes written. Used with a 204
+ * status (no content) or a HEAD request.
+ * 
+ * @author Remy Maucherat
+ */
+public class VoidOutputFilter implements OutputFilter {
+
+
+    // --------------------------------------------------- OutputBuffer Methods
+
+
+    /**
+     * Write some bytes.
+     * 
+     * @return number of bytes written by the filter
+     */
+    @Override
+    public int doWrite(ByteChunk chunk, Response res)
+        throws IOException {
+
+        return chunk.getLength();
+
+    }
+
+
+    @Override
+    public long getBytesWritten() {
+        return 0;
+    }
+
+
+    // --------------------------------------------------- OutputFilter Methods
+
+
+    /**
+     * 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.
+     */
+    @Override
+    public void setResponse(Response response) {
+        // NOOP: No need for parameters from response in this filter
+    }
+
+
+    /**
+     * Set the next buffer in the filter pipeline.
+     */
+    @Override
+    public void setBuffer(OutputBuffer buffer) {
+        // NO-OP
+    }
+
+
+    /**
+     * Make the filter ready to process the next request.
+     */
+    @Override
+    public void recycle() {
+        // NOOP: Nothing to recycle
+    }
+
+
+    /**
+     * 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.
+     */
+    @Override
+    public long end()
+        throws IOException {
+        return 0;
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/util/ConcurrentCache.java b/bundles/org.apache.tomcat/src/org/apache/el/util/ConcurrentCache.java
new file mode 100644
index 0000000..26029bb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/util/ConcurrentCache.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.el.util;
+
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+public final class ConcurrentCache<K,V> {
+
+    private final int size;
+
+    private final Map<K,V> eden;
+
+    private final Map<K,V> longterm;
+
+    public ConcurrentCache(int size) {
+        this.size = size;
+        this.eden = new ConcurrentHashMap<K,V>(size);
+        this.longterm = new WeakHashMap<K,V>(size);
+    }
+
+    public V get(K k) {
+        V v = this.eden.get(k);
+        if (v == null) {
+            synchronized (longterm) {
+                v = this.longterm.get(k);
+            }
+            if (v != null) {
+                this.eden.put(k, v);
+            }
+        }
+        return v;
+    }
+
+    public void put(K k, V v) {
+        if (this.eden.size() >= size) {
+            synchronized (longterm) {
+                this.longterm.putAll(this.eden);
+            }
+            this.eden.clear();
+        }
+        this.eden.put(k, v);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/util/MessageFactory.java b/bundles/org.apache.tomcat/src/org/apache/el/util/MessageFactory.java
new file mode 100644
index 0000000..c606816
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/util/MessageFactory.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.el.util;
+
+import java.text.MessageFormat;
+import java.util.ResourceBundle;
+
+/**
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: MessageFactory.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ */
+public final class MessageFactory {
+
+    protected static final ResourceBundle bundle =
+            ResourceBundle.getBundle("org.apache.el.Messages");
+
+    public MessageFactory() {
+        super();
+    }
+
+    public static String get(final String key) {
+        return bundle.getString(key);
+    }
+
+    public static String get(final String key, final Object... args) {
+        String value = get(key);
+        if (value == null) {
+            value = key;
+        }
+
+        MessageFormat mf = new MessageFormat(value);
+        return mf.format(args, new StringBuffer(), null).toString();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/util/ReflectionUtil.java b/bundles/org.apache.tomcat/src/org/apache/el/util/ReflectionUtil.java
new file mode 100644
index 0000000..4c44047
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/util/ReflectionUtil.java
@@ -0,0 +1,315 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.util;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.el.MethodNotFoundException;
+
+
+/**
+ * Utilities for Managing Serialization and Reflection
+ * 
+ * @author Jacob Hookom [jacob@hookom.net]
+ * @version $Id: ReflectionUtil.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ */
+public class ReflectionUtil {
+
+    protected static final String[] PRIMITIVE_NAMES = new String[] { "boolean",
+            "byte", "char", "double", "float", "int", "long", "short", "void" };
+
+    protected static final Class<?>[] PRIMITIVES = new Class[] { boolean.class,
+            byte.class, char.class, double.class, float.class, int.class,
+            long.class, short.class, Void.TYPE };
+
+    private ReflectionUtil() {
+        super();
+    }
+
+    public static Class<?> forName(String name) throws ClassNotFoundException {
+        if (null == name || "".equals(name)) {
+            return null;
+        }
+        Class<?> c = forNamePrimitive(name);
+        if (c == null) {
+            if (name.endsWith("[]")) {
+                String nc = name.substring(0, name.length() - 2);
+                c = Class.forName(nc, true, Thread.currentThread().getContextClassLoader());
+                c = Array.newInstance(c, 0).getClass();
+            } else {
+                c = Class.forName(name, true, Thread.currentThread().getContextClassLoader());
+            }
+        }
+        return c;
+    }
+
+    protected static Class<?> forNamePrimitive(String name) {
+        if (name.length() <= 8) {
+            int p = Arrays.binarySearch(PRIMITIVE_NAMES, name);
+            if (p >= 0) {
+                return PRIMITIVES[p];
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Converts an array of Class names to Class types
+     * @param s
+     * @throws ClassNotFoundException
+     */
+    public static Class<?>[] toTypeArray(String[] s) throws ClassNotFoundException {
+        if (s == null)
+            return null;
+        Class<?>[] c = new Class[s.length];
+        for (int i = 0; i < s.length; i++) {
+            c[i] = forName(s[i]);
+        }
+        return c;
+    }
+
+    /**
+     * Converts an array of Class types to Class names
+     * @param c
+     */
+    public static String[] toTypeNameArray(Class<?>[] c) {
+        if (c == null)
+            return null;
+        String[] s = new String[c.length];
+        for (int i = 0; i < c.length; i++) {
+            s[i] = c[i].getName();
+        }
+        return s;
+    }
+
+    /**
+     * Returns a method based on the criteria
+     * @param base the object that owns the method
+     * @param property the name of the method
+     * @param paramTypes the parameter types to use
+     * @return the method specified
+     * @throws MethodNotFoundException
+     */
+    @SuppressWarnings("null")
+    public static Method getMethod(Object base, Object property,
+            Class<?>[] paramTypes) throws MethodNotFoundException {
+        if (base == null || property == null) {
+            throw new MethodNotFoundException(MessageFactory.get(
+                    "error.method.notfound", base, property,
+                    paramString(paramTypes)));
+        }
+
+        String methodName = (property instanceof String) ? (String) property
+                : property.toString();
+        
+        int paramCount;
+        if (paramTypes == null) {
+            paramCount = 0;
+        } else {
+            paramCount = paramTypes.length;
+        }
+
+        Method[] methods = base.getClass().getMethods();
+        Map<Method,Integer> candidates = new HashMap<Method,Integer>();
+        
+        for (Method m : methods) {
+            if (!m.getName().equals(methodName)) {
+                // Method name doesn't match
+                continue;
+            }
+            
+            Class<?>[] mParamTypes = m.getParameterTypes();
+            int mParamCount;
+            if (mParamTypes == null) {
+                mParamCount = 0;
+            } else {
+                mParamCount = mParamTypes.length;
+            }
+            
+            // Check the number of parameters
+            if (!(paramCount == mParamCount ||
+                    (m.isVarArgs() && paramCount >= mParamCount))) {
+                // Method has wrong number of parameters
+                continue;
+            }
+            
+            // Check the parameters match
+            int exactMatch = 0;
+            boolean noMatch = false;
+            for (int i = 0; i < mParamCount; i++) {
+                // Can't be null
+                if (mParamTypes[i].equals(paramTypes[i])) {
+                    exactMatch++;
+                } else if (i == (mParamCount - 1) && m.isVarArgs()) {
+                    Class<?> varType = mParamTypes[i].getComponentType();
+                    for (int j = i; j < paramCount; j++) {
+                        if (!isAssignableFrom(paramTypes[j], varType)) {
+                            break;
+                        }
+                        // Don't treat a varArgs match as an exact match, it can
+                        // lead to a varArgs method matching when the result
+                        // should be ambiguous
+                    }
+                } else if (!isAssignableFrom(paramTypes[i], mParamTypes[i])) {
+                    noMatch = true;
+                    break;
+                }
+            }
+            if (noMatch) {
+                continue;
+            }
+            
+            // If a method is found where every parameter matches exactly,
+            // return it
+            if (exactMatch == paramCount) {
+                return m;
+            }
+            
+            candidates.put(m, Integer.valueOf(exactMatch));
+        }
+
+        // Look for the method that has the highest number of parameters where
+        // the type matches exactly
+        int bestMatch = 0;
+        Method match = null;
+        boolean multiple = false;
+        for (Map.Entry<Method, Integer> entry : candidates.entrySet()) {
+            if (entry.getValue().intValue() > bestMatch ||
+                    match == null) {
+                bestMatch = entry.getValue().intValue();
+                match = entry.getKey();
+                multiple = false;
+            } else if (entry.getValue().intValue() == bestMatch) {
+                multiple = true;
+            }
+        }
+        if (multiple) {
+            if (bestMatch == paramCount - 1) {
+                // Only one parameter is not an exact match - try using the
+                // super class
+                match = resolveAmbiguousMethod(candidates.keySet(), paramTypes);
+            } else {
+                match = null;
+            }
+            
+            if (match == null) {
+                // If multiple methods have the same matching number of parameters
+                // the match is ambiguous so throw an exception
+                throw new MethodNotFoundException(MessageFactory.get(
+                        "error.method.ambiguous", base, property,
+                        paramString(paramTypes)));
+                }
+        }
+        
+        // Handle case where no match at all was found
+        if (match == null) {
+            throw new MethodNotFoundException(MessageFactory.get(
+                        "error.method.notfound", base, property,
+                        paramString(paramTypes)));
+        }
+        
+        return match;
+    }
+
+    @SuppressWarnings("null")
+    private static Method resolveAmbiguousMethod(Set<Method> candidates,
+            Class<?>[] paramTypes) {
+        // Identify which parameter isn't an exact match
+        Method m = candidates.iterator().next();
+        
+        int nonMatchIndex = 0;
+        Class<?> nonMatchClass = null;
+        
+        for (int i = 0; i < paramTypes.length; i++) {
+            if (m.getParameterTypes()[i] != paramTypes[i]) {
+                nonMatchIndex = i;
+                nonMatchClass = paramTypes[i];
+                break;
+            }
+        }
+        
+        for (Method c : candidates) {
+           if (c.getParameterTypes()[nonMatchIndex] ==
+                   paramTypes[nonMatchIndex]) {
+               // Methods have different non-matching parameters
+               // Result is ambiguous
+               return null;
+           }
+        }
+        
+        // Can't be null
+        nonMatchClass = nonMatchClass.getSuperclass();
+        while (nonMatchClass != null) {
+            for (Method c : candidates) {
+                if (c.getParameterTypes()[nonMatchIndex].equals(
+                        nonMatchClass)) {
+                    // Found a match
+                    return c;
+                }
+            }
+            nonMatchClass = nonMatchClass.getSuperclass();
+        }
+        
+        return null;
+    }
+
+    // src will always be an object
+    private static boolean isAssignableFrom(Class<?> src, Class<?> target) {
+        Class<?> targetClass;
+        if (target.isPrimitive()) {
+            if (target == Boolean.TYPE) {
+                targetClass = Boolean.class;
+            } else if (target == Character.TYPE) {
+                targetClass = Character.class;
+            } else if (target == Byte.TYPE) {
+                targetClass = Byte.class;
+            } else if (target == Short.TYPE) {
+                targetClass = Short.class;
+            } else if (target == Integer.TYPE) {
+                targetClass = Integer.class;
+            } else if (target == Long.TYPE) {
+                targetClass = Long.class;
+            } else if (target == Float.TYPE) {
+                targetClass = Float.class;
+            } else {
+                targetClass = Double.class;
+            }
+        } else {
+            targetClass = target;
+        }
+        return targetClass.isAssignableFrom(src);
+    }
+
+    protected static final String paramString(Class<?>[] types) {
+        if (types != null) {
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < types.length; i++) {
+                sb.append(types[i].getName()).append(", ");
+            }
+            if (sb.length() > 2) {
+                sb.setLength(sb.length() - 2);
+            }
+            return sb.toString();
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/el/util/Validation.java b/bundles/org.apache.tomcat/src/org/apache/el/util/Validation.java
new file mode 100644
index 0000000..166130b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/el/util/Validation.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.el.util;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+public class Validation {
+
+    // Java keywords, boolean literals & the null literal in alphabetical order
+    private static final String invalidIdentifiers[] = { "abstract", "assert",
+        "boolean", "break", "byte", "case", "catch", "char", "class", "const",
+        "continue", "default", "do", "double", "else", "enum", "extends",
+        "false", "final", "finally", "float", "for", "goto", "if", "implements",
+        "import", "instanceof", "int", "interface", "long", "native", "new",
+        "null", "package", "private", "protected", "public", "return", "short",
+        "static", "strictfp", "super", "switch", "synchronized", "this",
+        "throw", "throws", "transient", "true", "try", "void", "volatile",
+        "while" };
+    
+    private static final boolean IS_SECURITY_ENABLED =
+            (System.getSecurityManager() != null);
+
+    private static final boolean SKIP_IDENTIFIER_CHECK;
+
+    static {
+        if (IS_SECURITY_ENABLED) {
+            SKIP_IDENTIFIER_CHECK = AccessController.doPrivileged(
+                    new PrivilegedAction<Boolean>(){
+                        @Override
+                        public Boolean run() {
+                            return Boolean.valueOf(System.getProperty(
+                                    "org.apache.el.parser.SKIP_IDENTIFIER_CHECK",
+                            "false"));
+                        }
+                    }
+            ).booleanValue();
+        } else {
+            SKIP_IDENTIFIER_CHECK = Boolean.valueOf(System.getProperty(
+                    "org.apache.el.parser.SKIP_IDENTIFIER_CHECK",
+            "false")).booleanValue();
+        }
+    }
+    
+    
+    private Validation() {
+        // Utility class. Hide default constructor
+    }
+    
+    /**
+     * Test whether the argument is a Java identifier.
+     */
+    public static boolean isIdentifier(String key) {
+        
+        if (SKIP_IDENTIFIER_CHECK) {
+            return true;
+        }
+
+        // Should not be the case but check to be sure
+        if (key == null || key.length() == 0) {
+            return false;
+        }
+        
+        // Check the list of known invalid values
+        int i = 0;
+        int j = invalidIdentifiers.length;
+        while (i < j) {
+            int k = (i + j) >>> 1; // Avoid overflow
+            int result = invalidIdentifiers[k].compareTo(key);
+            if (result == 0) {
+                return false;
+            }
+            if (result < 0) {
+                i = k + 1;
+            } else {
+                j = k;
+            }
+        }
+
+        // Check the start character that has more restrictions
+        if (!Character.isJavaIdentifierStart(key.charAt(0))) {
+            return false;
+        }
+
+        // Check each remaining character used is permitted
+        for (int idx = 1; idx < key.length(); idx++) {
+            if (!Character.isJavaIdentifierPart(key.charAt(idx))) {
+                return false;
+            }
+        }
+        
+        return true;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/AttributeParser.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/AttributeParser.java
new file mode 100644
index 0000000..1d6d0e1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/AttributeParser.java
@@ -0,0 +1,351 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/**
+ * Converts a JSP attribute value into the unquoted equivalent. The attribute
+ * may contain EL expressions, in which case care needs to be taken to avoid any
+ * ambiguities. For example, consider the attribute values "${1+1}" and
+ * "\${1+1}". After unquoting, both appear as "${1+1}" but the first should
+ * evaluate to "2" and the second to "${1+1}". Literal \, $ and # need special
+ * treatment to ensure there is no ambiguity. The JSP attribute unquoting
+ * covers \\, \", \', \$, \#, %\&gt;, &lt;\%, &amp;apos; and &amp;quot;
+ */
+public class AttributeParser {
+
+    /* System property that controls if the strict quoting rules are applied. */ 
+    private static final boolean STRICT_QUOTE_ESCAPING = Boolean.valueOf(
+            System.getProperty(
+                    "org.apache.jasper.compiler.Parser.STRICT_QUOTE_ESCAPING",
+                    "true")).booleanValue();
+
+    /**
+     * Parses the provided input String as a JSP attribute and returns an
+     * unquoted value.
+     * 
+     * @param input         The input.
+     * @param quote         The quote character for the attribute or 0 for
+     *                      scripting expressions.
+     * @param isELIgnored   Is expression language being ignored on the page
+     *                      where the JSP attribute is defined.
+     * @param isDeferredSyntaxAllowedAsLiteral
+     *                      Are deferred expressions treated as literals?
+     * @return              An unquoted JSP attribute that, if it contains
+     *                      expression language can be safely passed to the EL
+     *                      processor without fear of ambiguity.
+     */
+    public static String getUnquoted(String input, char quote,
+            boolean isELIgnored, boolean isDeferredSyntaxAllowedAsLiteral) {
+        return (new AttributeParser(input, quote, isELIgnored,
+                isDeferredSyntaxAllowedAsLiteral,
+                STRICT_QUOTE_ESCAPING)).getUnquoted();
+    }
+
+    /**
+     * Provided solely for unit test purposes and allows per call overriding of
+     * the STRICT_QUOTE_ESCAPING system property.
+     * 
+     * @param input         The input.
+     * @param quote         The quote character for the attribute or 0 for
+     *                      scripting expressions.
+     * @param isELIgnored   Is expression language being ignored on the page
+     *                      where the JSP attribute is defined.
+     * @param isDeferredSyntaxAllowedAsLiteral
+     *                      Are deferred expressions treated as literals?
+     * @param strict        The value to use for STRICT_QUOTE_ESCAPING.
+     * @return              An unquoted JSP attribute that, if it contains
+     *                      expression language can be safely passed to the EL
+     *                      processor without fear of ambiguity.
+     */
+    protected static String getUnquoted(String input, char quote,
+            boolean isELIgnored, boolean isDeferredSyntaxAllowedAsLiteral,
+            boolean strict) {
+        return (new AttributeParser(input, quote, isELIgnored,
+                isDeferredSyntaxAllowedAsLiteral, strict)).getUnquoted();
+    }
+
+    /* The quoted input string. */
+    private final String input;
+    
+    /* The quote used for the attribute - null for scripting expressions. */
+    private final char quote;
+    
+    /* Is expression language being ignored - affects unquoting. \$ and \# are
+     * treated as literals rather than quoted values. */
+    private final boolean isELIgnored;
+    
+    /* Are deferred expression treated as literals */
+    private final boolean isDeferredSyntaxAllowedAsLiteral;
+    
+    /* Overrides the STRICT_QUOTE_ESCAPING. Used for Unit tests only. */
+    private final boolean strict;
+    
+    /* The type ($ or #) of expression. Literals have a type of null. */
+    private char type;
+    
+    /* The length of the quoted input string. */
+    private final int size;
+    
+    /* Tracks the current position of the parser in the input String. */
+    private int i = 0;
+    
+    /* Indicates if the last character returned by nextChar() was escaped. */
+    private boolean lastChEscaped = false;
+    
+    /* The unquoted result. */
+    private StringBuilder result;
+
+
+    /**
+     * For test purposes.
+     * @param input
+     * @param quote
+     * @param strict
+     */
+    private AttributeParser(String input, char quote,
+            boolean isELIgnored, boolean isDeferredSyntaxAllowedAsLiteral,
+            boolean strict) {
+        this.input = input;
+        this.quote = quote;
+        this.isELIgnored = isELIgnored;
+        this.isDeferredSyntaxAllowedAsLiteral =
+            isDeferredSyntaxAllowedAsLiteral;
+        this.strict = strict;
+        this.type = getType(input);
+        this.size = input.length();
+        result = new StringBuilder(size);
+    }
+
+    /*
+     * Work through input looking for literals and expressions until the input
+     * has all been read.
+     */
+    private String getUnquoted() {
+        while (i < size) {
+            parseLiteral();
+            parseEL();
+        }
+        return result.toString();
+    }
+
+    /*
+     * This method gets the next unquoted character and looks for
+     * - literals that need to be converted for EL processing
+     *   \ -> type{'\\'}
+     *   $ -> type{'$'}
+     *   # -> type{'#'}
+     * - start of EL
+     *   ${
+     *   #{
+     * Note all the examples above *do not* include the escaping required to use
+     * the values in Java code.
+     */
+    private void parseLiteral() {
+        boolean foundEL = false;
+        while (i < size && !foundEL) {
+            char ch = nextChar();
+            if (!isELIgnored && ch == '\\') {
+                if (type == 0) {
+                    result.append("\\");
+                } else {
+                    result.append(type);
+                    result.append("{'\\\\'}");
+                }
+            } else if (!isELIgnored && ch == '$' && lastChEscaped){
+                if (type == 0) {
+                    result.append("\\$");
+                } else {
+                    result.append(type);
+                    result.append("{'$'}");
+                }
+            } else if (!isELIgnored && ch == '#' && lastChEscaped){
+                // Note if isDeferredSyntaxAllowedAsLiteral==true, \# will
+                // not be treated as an escape
+                if (type == 0) {
+                    result.append("\\#");
+                } else {
+                    result.append(type);
+                    result.append("{'#'}");
+                }
+            } else if (ch == type){
+                if (i < size) {
+                    char next = input.charAt(i);
+                    if (next == '{') {
+                        foundEL = true;
+                        // Move back to start of EL
+                        i--;
+                    } else {
+                        result.append(ch);
+                    }
+                } else {
+                    result.append(ch);
+                }
+            } else {
+                result.append(ch);
+            }
+        }
+    }
+
+    /*
+     * For EL need to unquote everything but no need to convert anything. The
+     * EL is terminated by '}'. The only other valid location for '}' is inside
+     * a StringLiteral. The literals are delimited by '\'' or '\"'. The only
+     * other valid location for '\'' or '\"' is also inside a StringLiteral. A
+     * quote character inside a StringLiteral must be escaped if the same quote
+     * character is used to delimit the StringLiteral.
+     */
+    private void parseEL() {
+        boolean endEL = false;
+        boolean insideLiteral = false;
+        char literalQuote = 0;
+        while (i < size && !endEL) {
+            char ch = nextChar();
+            if (ch == '\'' || ch == '\"') {
+                if (insideLiteral) {
+                    if (literalQuote == ch) {
+                        insideLiteral = false;
+                    }
+                } else {
+                    insideLiteral = true;
+                    literalQuote = ch;
+                }
+                result.append(ch);
+            } else if (ch == '\\') {
+                result.append(ch);
+                if (insideLiteral && size < i) {
+                    ch = nextChar();
+                    result.append(ch);
+                }
+            } else if (ch == '}') {
+                if (!insideLiteral) {
+                    endEL = true;
+                }
+                result.append(ch);
+            } else {
+                result.append(ch);
+            }
+        }
+    }
+
+    /*
+     * Returns the next unquoted character and sets the lastChEscaped flag to
+     * indicate if it was quoted/escaped or not.
+     * &apos; is always unquoted to '
+     * &quot; is always unquoted to "
+     * \" is always unquoted to "
+     * \' is always unquoted to '
+     * \\ is always unquoted to \
+     * \$ is unquoted to $ if EL is not being ignored
+     * \# is unquoted to # if EL is not being ignored
+     * <\% is always unquoted to <%
+     * %\> is always unquoted to %>
+     */
+    private char nextChar() {
+        lastChEscaped = false;
+        char ch = input.charAt(i);
+        
+        if (ch == '&') {
+            if (i + 5 < size && input.charAt(i + 1) == 'a' &&
+                    input.charAt(i + 2) == 'p' && input.charAt(i + 3) == 'o' &&
+                    input.charAt(i + 4) == 's' && input.charAt(i + 5) == ';') {
+                ch = '\'';
+                i += 6;
+            } else if (i + 5 < size && input.charAt(i + 1) == 'q' &&
+                    input.charAt(i + 2) == 'u' && input.charAt(i + 3) == 'o' &&
+                    input.charAt(i + 4) == 't' && input.charAt(i + 5) == ';') {
+                ch = '\"';
+                i += 6;
+            } else {
+                ++i;
+            }
+        } else if (ch == '\\' && i + 1 < size) {
+            ch = input.charAt(i + 1);
+            if (ch == '\\' || ch == '\"' || ch == '\'' ||
+                    (!isELIgnored &&
+                            (ch == '$' ||
+                                    (!isDeferredSyntaxAllowedAsLiteral &&
+                                            ch == '#')))) {
+                i += 2;
+                lastChEscaped = true;
+            } else {
+                ch = '\\';
+                ++i;
+            }
+        } else if (ch == '<' && (i + 2 < size) && input.charAt(i + 1) == '\\' &&
+                input.charAt(i + 2) == '%') {
+            // Note this is a hack since nextChar only returns a single char
+            // It is safe since <% does not require special treatment for EL
+            // or for literals
+            result.append('<');
+            i+=3;
+            return '%';
+        } else if (ch == '%' && i + 2 < size && input.charAt(i + 1) == '\\' &&
+                input.charAt(i + 2) == '>') {
+            // Note this is a hack since nextChar only returns a single char
+            // It is safe since %> does not require special treatment for EL
+            // or for literals
+            result.append('%');
+            i+=3;
+            return '>';
+        } else if (ch == quote && strict) {
+            String msg = Localizer.getMessage("jsp.error.attribute.noescape",
+                    input, ""+ quote);
+            throw new IllegalArgumentException(msg);
+        } else {
+            ++i;
+        }
+
+        return ch;
+    }
+
+    /*
+     * Determines the type of expression by looking for the first unquoted ${
+     * or #{.
+     */
+    private char getType(String value) {
+        if (value == null) {
+            return 0;
+        }
+
+        if (isELIgnored) {
+            return 0;
+        }
+
+        int j = 0;
+        int len = value.length();
+        char current;
+
+        while (j < len) {
+            current = value.charAt(j);
+            if (current == '\\') {
+                // Escape character - skip a character
+                j++;
+            } else if (current == '#' && !isDeferredSyntaxAllowedAsLiteral) {
+                if (j < (len -1) && value.charAt(j + 1) == '{') {
+                    return '#';
+                }
+            } else if (current == '$') {
+                if (j < (len - 1) && value.charAt(j + 1) == '{') {
+                    return '$';
+                }
+            }
+            j++;
+        }
+        return 0;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/BeanRepository.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/BeanRepository.java
new file mode 100644
index 0000000..c702c21
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/BeanRepository.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.compiler;
+
+
+import java.util.HashMap;
+
+import org.apache.jasper.JasperException;
+
+/**
+ * Repository of {page, request, session, application}-scoped beans 
+ *
+ * @author Mandar Raje
+ * @author Remy Maucherat
+ */
+public class BeanRepository {
+
+    protected HashMap<String, String> beanTypes;
+    protected ClassLoader loader;
+    protected ErrorDispatcher errDispatcher;
+
+    /**
+     * Constructor.
+     */    
+    public BeanRepository(ClassLoader loader, ErrorDispatcher err) {
+        this.loader = loader;
+        this.errDispatcher = err;
+        beanTypes = new HashMap<String, String>();
+    }
+
+    public void addBean(Node.UseBean n, String s, String type, String scope)
+        throws JasperException {
+
+        if (!(scope == null || scope.equals("page") || scope.equals("request") 
+                || scope.equals("session") || scope.equals("application"))) {
+            errDispatcher.jspError(n, "jsp.error.usebean.badScope");
+        }
+
+        beanTypes.put(s, type);
+    }
+            
+    public Class<?> getBeanType(String bean)
+        throws JasperException {
+        Class<?> clazz = null;
+        try {
+            clazz = loader.loadClass(beanTypes.get(bean));
+        } catch (ClassNotFoundException ex) {
+            throw new JasperException (ex);
+        }
+        return clazz;
+    }
+      
+    public boolean checkVariable(String bean) {
+        return beanTypes.containsKey(bean);
+    }
+
+}
+
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Collector.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Collector.java
new file mode 100644
index 0000000..e42e04a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Collector.java
@@ -0,0 +1,217 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import org.apache.jasper.JasperException;
+
+/**
+ * Collect info about the page and nodes, and make them available through
+ * the PageInfo object.
+ *
+ * @author Kin-man Chung
+ * @author Mark Roth
+ */
+
+class Collector {
+
+    /**
+     * A visitor for collecting information on the page and the body of
+     * the custom tags.
+     */
+    static class CollectVisitor extends Node.Visitor {
+
+        private boolean scriptingElementSeen = false;
+        private boolean usebeanSeen = false;
+        private boolean includeActionSeen = false;
+        private boolean paramActionSeen = false;
+        private boolean setPropertySeen = false;
+        private boolean hasScriptingVars = false;
+
+        @Override
+        public void visit(Node.ParamAction n) throws JasperException {
+            if (n.getValue().isExpression()) {
+                scriptingElementSeen = true;
+            }
+            paramActionSeen = true;
+        }
+
+        @Override
+        public void visit(Node.IncludeAction n) throws JasperException {
+            if (n.getPage().isExpression()) {
+                scriptingElementSeen = true;
+            }
+            includeActionSeen = true;
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.ForwardAction n) throws JasperException {
+            if (n.getPage().isExpression()) {
+                scriptingElementSeen = true;
+            }
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.SetProperty n) throws JasperException {
+            if (n.getValue() != null && n.getValue().isExpression()) {
+                scriptingElementSeen = true;
+            }
+            setPropertySeen = true;
+        }
+
+        @Override
+        public void visit(Node.UseBean n) throws JasperException {
+            if (n.getBeanName() != null && n.getBeanName().isExpression()) {
+                scriptingElementSeen = true;
+            }
+            usebeanSeen = true;
+                visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.PlugIn n) throws JasperException {
+            if (n.getHeight() != null && n.getHeight().isExpression()) {
+                scriptingElementSeen = true;
+            }
+            if (n.getWidth() != null && n.getWidth().isExpression()) {
+                scriptingElementSeen = true;
+            }
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.CustomTag n) throws JasperException {
+            // Check to see what kinds of element we see as child elements
+            checkSeen( n.getChildInfo(), n );
+        }
+
+        /**
+         * Check all child nodes for various elements and update the given
+         * ChildInfo object accordingly.  Visits body in the process.
+         */
+        private void checkSeen( Node.ChildInfo ci, Node n )
+            throws JasperException
+        {
+            // save values collected so far
+            boolean scriptingElementSeenSave = scriptingElementSeen;
+            scriptingElementSeen = false;
+            boolean usebeanSeenSave = usebeanSeen;
+            usebeanSeen = false;
+            boolean includeActionSeenSave = includeActionSeen;
+            includeActionSeen = false;
+            boolean paramActionSeenSave = paramActionSeen;
+            paramActionSeen = false;
+            boolean setPropertySeenSave = setPropertySeen;
+            setPropertySeen = false;
+            boolean hasScriptingVarsSave = hasScriptingVars;
+            hasScriptingVars = false;
+
+            // Scan attribute list for expressions
+            if( n instanceof Node.CustomTag ) {
+                Node.CustomTag ct = (Node.CustomTag)n;
+                Node.JspAttribute[] attrs = ct.getJspAttributes();
+                for (int i = 0; attrs != null && i < attrs.length; i++) {
+                    if (attrs[i].isExpression()) {
+                        scriptingElementSeen = true;
+                        break;
+                    }
+                }
+            }
+
+            visitBody(n);
+
+            if( (n instanceof Node.CustomTag) && !hasScriptingVars) {
+                Node.CustomTag ct = (Node.CustomTag)n;
+                hasScriptingVars = ct.getVariableInfos().length > 0 ||
+                    ct.getTagVariableInfos().length > 0;
+            }
+
+            // Record if the tag element and its body contains any scriptlet.
+            ci.setScriptless(! scriptingElementSeen);
+            ci.setHasUseBean(usebeanSeen);
+            ci.setHasIncludeAction(includeActionSeen);
+            ci.setHasParamAction(paramActionSeen);
+            ci.setHasSetProperty(setPropertySeen);
+            ci.setHasScriptingVars(hasScriptingVars);
+
+            // Propagate value of scriptingElementSeen up.
+            scriptingElementSeen = scriptingElementSeen || scriptingElementSeenSave;
+            usebeanSeen = usebeanSeen || usebeanSeenSave;
+            setPropertySeen = setPropertySeen || setPropertySeenSave;
+            includeActionSeen = includeActionSeen || includeActionSeenSave;
+            paramActionSeen = paramActionSeen || paramActionSeenSave;
+            hasScriptingVars = hasScriptingVars || hasScriptingVarsSave;
+        }
+
+        @Override
+        public void visit(Node.JspElement n) throws JasperException {
+            if (n.getNameAttribute().isExpression())
+                scriptingElementSeen = true;
+
+            Node.JspAttribute[] attrs = n.getJspAttributes();
+            for (int i = 0; i < attrs.length; i++) {
+                if (attrs[i].isExpression()) {
+                    scriptingElementSeen = true;
+                    break;
+                }
+            }
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.JspBody n) throws JasperException {
+            checkSeen( n.getChildInfo(), n );
+        }
+
+        @Override
+        public void visit(Node.NamedAttribute n) throws JasperException {
+            checkSeen( n.getChildInfo(), n );
+        }
+
+        @Override
+        public void visit(Node.Declaration n) throws JasperException {
+            scriptingElementSeen = true;
+        }
+
+        @Override
+        public void visit(Node.Expression n) throws JasperException {
+            scriptingElementSeen = true;
+        }
+
+        @Override
+        public void visit(Node.Scriptlet n) throws JasperException {
+            scriptingElementSeen = true;
+        }
+
+        public void updatePageInfo(PageInfo pageInfo) {
+            pageInfo.setScriptless(! scriptingElementSeen);
+        }
+    }
+
+
+    public static void collect(Compiler compiler, Node.Nodes page)
+        throws JasperException {
+
+    CollectVisitor collectVisitor = new CollectVisitor();
+        page.visit(collectVisitor);
+        collectVisitor.updatePageInfo(compiler.getPageInfo());
+
+    }
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Compiler.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Compiler.java
new file mode 100644
index 0000000..28b4c2a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Compiler.java
@@ -0,0 +1,613 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.apache.jasper.Options;
+import org.apache.jasper.servlet.JspServletWrapper;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * Main JSP compiler class. This class uses Ant for compiling.
+ * 
+ * @author Anil K. Vijendran
+ * @author Mandar Raje
+ * @author Pierre Delisle
+ * @author Kin-man Chung
+ * @author Remy Maucherat
+ * @author Mark Roth
+ */
+public abstract class Compiler {
+    
+    private final Log log = LogFactory.getLog(Compiler.class); // must not be static
+
+    // ----------------------------------------------------- Instance Variables
+
+    protected JspCompilationContext ctxt;
+
+    protected ErrorDispatcher errDispatcher;
+
+    protected PageInfo pageInfo;
+
+    protected JspServletWrapper jsw;
+
+    protected TagFileProcessor tfp;
+
+    protected Options options;
+
+    protected Node.Nodes pageNodes;
+
+    // ------------------------------------------------------------ Constructor
+
+    public void init(JspCompilationContext ctxt, JspServletWrapper jsw) {
+        this.jsw = jsw;
+        this.ctxt = ctxt;
+        this.options = ctxt.getOptions();
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * <p>
+     * Retrieves the parsed nodes of the JSP page, if they are available. May
+     * return null. Used in development mode for generating detailed error
+     * messages. http://issues.apache.org/bugzilla/show_bug.cgi?id=37062.
+     * </p>
+     */
+    public Node.Nodes getPageNodes() {
+        return this.pageNodes;
+    }
+
+    /**
+     * Compile the jsp file into equivalent servlet in .java file
+     * 
+     * @return a smap for the current JSP page, if one is generated, null
+     *         otherwise
+     */
+    protected String[] generateJava() throws Exception {
+
+        String[] smapStr = null;
+
+        long t1, t2, t3, t4;
+
+        t1 = t2 = t3 = t4 = 0;
+
+        if (log.isDebugEnabled()) {
+            t1 = System.currentTimeMillis();
+        }
+
+        // Setup page info area
+        pageInfo = new PageInfo(new BeanRepository(ctxt.getClassLoader(),
+                errDispatcher), ctxt.getJspFile());
+
+        JspConfig jspConfig = options.getJspConfig();
+        JspConfig.JspProperty jspProperty = jspConfig.findJspProperty(ctxt
+                .getJspFile());
+
+        /*
+         * If the current uri is matched by a pattern specified in a
+         * jsp-property-group in web.xml, initialize pageInfo with those
+         * properties.
+         */
+        if (jspProperty.isELIgnored() != null) {
+            pageInfo.setELIgnored(JspUtil.booleanValue(jspProperty
+                    .isELIgnored()));
+        }
+        if (jspProperty.isScriptingInvalid() != null) {
+            pageInfo.setScriptingInvalid(JspUtil.booleanValue(jspProperty
+                    .isScriptingInvalid()));
+        }
+        if (jspProperty.getIncludePrelude() != null) {
+            pageInfo.setIncludePrelude(jspProperty.getIncludePrelude());
+        }
+        if (jspProperty.getIncludeCoda() != null) {
+            pageInfo.setIncludeCoda(jspProperty.getIncludeCoda());
+        }
+        if (jspProperty.isDeferedSyntaxAllowedAsLiteral() != null) {
+            pageInfo.setDeferredSyntaxAllowedAsLiteral(JspUtil.booleanValue(jspProperty
+                    .isDeferedSyntaxAllowedAsLiteral()));
+        }
+        if (jspProperty.isTrimDirectiveWhitespaces() != null) {
+            pageInfo.setTrimDirectiveWhitespaces(JspUtil.booleanValue(jspProperty
+                    .isTrimDirectiveWhitespaces()));
+        }
+        // Default ContentType processing is deferred until after the page has
+        // been parsed
+        if (jspProperty.getBuffer() != null) {
+            pageInfo.setBufferValue(jspProperty.getBuffer(), null,
+                    errDispatcher);
+        }
+        if (jspProperty.isErrorOnUndeclaredNamespace() != null) {
+            pageInfo.setErrorOnUndeclaredNamespace(
+                    JspUtil.booleanValue(
+                            jspProperty.isErrorOnUndeclaredNamespace()));
+        }
+        if (ctxt.isTagFile()) {
+            try {
+                double libraryVersion = Double.parseDouble(ctxt.getTagInfo()
+                        .getTagLibrary().getRequiredVersion());
+                if (libraryVersion < 2.0) {
+                    pageInfo.setIsELIgnored("true", null, errDispatcher, true);
+                }
+                if (libraryVersion < 2.1) {
+                    pageInfo.setDeferredSyntaxAllowedAsLiteral("true", null,
+                            errDispatcher, true);
+                }
+            } catch (NumberFormatException ex) {
+                errDispatcher.jspError(ex);
+            }
+        }
+
+        ctxt.checkOutputDir();
+        String javaFileName = ctxt.getServletJavaFileName();
+
+        ServletWriter writer = null;
+        try {
+            /*
+             * The setting of isELIgnored changes the behaviour of the parser
+             * in subtle ways. To add to the 'fun', isELIgnored can be set in
+             * any file that forms part of the translation unit so setting it
+             * in a file included towards the end of the translation unit can
+             * change how the parser should have behaved when parsing content
+             * up to the point where isELIgnored was set. Arghh!
+             * Previous attempts to hack around this have only provided partial
+             * solutions. We now use two passes to parse the translation unit.
+             * The first just parses the directives and the second parses the
+             * whole translation unit once we know how isELIgnored has been set.
+             * TODO There are some possible optimisations of this process.  
+             */ 
+            // Parse the file
+            ParserController parserCtl = new ParserController(ctxt, this);
+            
+            // Pass 1 - the directives
+            Node.Nodes directives =
+                parserCtl.parseDirectives(ctxt.getJspFile());
+            Validator.validateDirectives(this, directives);
+            
+            // Pass 2 - the whole translation unit
+            pageNodes = parserCtl.parse(ctxt.getJspFile());
+
+            // Leave this until now since it can only be set once - bug 49726
+            if (pageInfo.getContentType() == null &&
+                    jspProperty.getDefaultContentType() != null) {
+                pageInfo.setContentType(jspProperty.getDefaultContentType());
+            }
+
+            if (ctxt.isPrototypeMode()) {
+                // generate prototype .java file for the tag file
+                writer = setupContextWriter(javaFileName);
+                Generator.generate(writer, this, pageNodes);
+                writer.close();
+                writer = null;
+                return null;
+            }
+
+            // Validate and process attributes - don't re-validate the
+            // directives we validated in pass 1
+            Validator.validateExDirectives(this, pageNodes);
+
+            if (log.isDebugEnabled()) {
+                t2 = System.currentTimeMillis();
+            }
+
+            // Collect page info
+            Collector.collect(this, pageNodes);
+
+            // Compile (if necessary) and load the tag files referenced in
+            // this compilation unit.
+            tfp = new TagFileProcessor();
+            tfp.loadTagFiles(this, pageNodes);
+
+            if (log.isDebugEnabled()) {
+                t3 = System.currentTimeMillis();
+            }
+
+            // Determine which custom tag needs to declare which scripting vars
+            ScriptingVariabler.set(pageNodes, errDispatcher);
+
+            // Optimizations by Tag Plugins
+            TagPluginManager tagPluginManager = options.getTagPluginManager();
+            tagPluginManager.apply(pageNodes, errDispatcher, pageInfo);
+
+            // Optimization: concatenate contiguous template texts.
+            TextOptimizer.concatenate(this, pageNodes);
+
+            // Generate static function mapper codes.
+            ELFunctionMapper.map(pageNodes);
+
+            // generate servlet .java file
+            writer = setupContextWriter(javaFileName);
+            Generator.generate(writer, this, pageNodes);
+            writer.close();
+            writer = null;
+
+            // The writer is only used during the compile, dereference
+            // it in the JspCompilationContext when done to allow it
+            // to be GC'd and save memory.
+            ctxt.setWriter(null);
+
+            if (log.isDebugEnabled()) {
+                t4 = System.currentTimeMillis();
+                log.debug("Generated " + javaFileName + " total=" + (t4 - t1)
+                        + " generate=" + (t4 - t3) + " validate=" + (t2 - t1));
+            }
+
+        } catch (Exception e) {
+            if (writer != null) {
+                try {
+                    writer.close();
+                    writer = null;
+                } catch (Exception e1) {
+                    // do nothing
+                }
+            }
+            // Remove the generated .java file
+            File file = new File(javaFileName);
+            if (file.exists()) {
+                if (!file.delete()) {
+                    log.warn(Localizer.getMessage(
+                            "jsp.warning.compiler.javafile.delete.fail",
+                            file.getAbsolutePath()));
+                }
+            }
+            throw e;
+        } finally {
+            if (writer != null) {
+                try {
+                    writer.close();
+                } catch (Exception e2) {
+                    // do nothing
+                }
+            }
+        }
+
+        // JSR45 Support
+        if (!options.isSmapSuppressed()) {
+            smapStr = SmapUtil.generateSmap(ctxt, pageNodes);
+        }
+
+        // If any proto type .java and .class files was generated,
+        // the prototype .java may have been replaced by the current
+        // compilation (if the tag file is self referencing), but the
+        // .class file need to be removed, to make sure that javac would
+        // generate .class again from the new .java file just generated.
+        tfp.removeProtoTypeFiles(ctxt.getClassFileName());
+
+        return smapStr;
+    }
+
+    private ServletWriter setupContextWriter(String javaFileName)
+            throws FileNotFoundException, JasperException {
+        ServletWriter writer;
+        // Setup the ServletWriter
+        String javaEncoding = ctxt.getOptions().getJavaEncoding();
+        OutputStreamWriter osw = null;
+
+        try {
+            osw = new OutputStreamWriter(
+                    new FileOutputStream(javaFileName), javaEncoding);
+        } catch (UnsupportedEncodingException ex) {
+            errDispatcher.jspError("jsp.error.needAlternateJavaEncoding",
+                    javaEncoding);
+        }
+
+        writer = new ServletWriter(new PrintWriter(osw));
+        ctxt.setWriter(writer);
+        return writer;
+    }
+
+    /**
+     * Compile the servlet from .java file to .class file
+     */
+    protected abstract void generateClass(String[] smap)
+            throws FileNotFoundException, JasperException, Exception;
+
+    /**
+     * Compile the jsp file from the current engine context
+     */
+    public void compile() throws FileNotFoundException, JasperException,
+            Exception {
+        compile(true);
+    }
+
+    /**
+     * Compile the jsp file from the current engine context. As an side- effect,
+     * tag files that are referenced by this page are also compiled.
+     * 
+     * @param compileClass
+     *            If true, generate both .java and .class file If false,
+     *            generate only .java file
+     */
+    public void compile(boolean compileClass) throws FileNotFoundException,
+            JasperException, Exception {
+        compile(compileClass, false);
+    }
+
+    /**
+     * Compile the jsp file from the current engine context. As an side- effect,
+     * tag files that are referenced by this page are also compiled.
+     * 
+     * @param compileClass
+     *            If true, generate both .java and .class file If false,
+     *            generate only .java file
+     * @param jspcMode
+     *            true if invoked from JspC, false otherwise
+     */
+    public void compile(boolean compileClass, boolean jspcMode)
+            throws FileNotFoundException, JasperException, Exception {
+        if (errDispatcher == null) {
+            this.errDispatcher = new ErrorDispatcher(jspcMode);
+        }
+
+        try {
+            String[] smap = generateJava();
+            if (compileClass) {
+                generateClass(smap);
+                // Fix for bugzilla 41606
+                // Set JspServletWrapper.servletClassLastModifiedTime after successful compile
+                String targetFileName = ctxt.getClassFileName();
+                if (targetFileName != null) {
+                    File targetFile = new File(targetFileName);
+                    if (targetFile.exists() && jsw != null) {
+                        jsw.setServletClassLastModifiedTime(targetFile.lastModified());
+                    }
+                }
+            }
+        } finally {
+            if (tfp != null && ctxt.isPrototypeMode()) {
+                tfp.removeProtoTypeFiles(null);
+            }
+            // Make sure these object which are only used during the
+            // generation and compilation of the JSP page get
+            // dereferenced so that they can be GC'd and reduce the
+            // memory footprint.
+            tfp = null;
+            errDispatcher = null;
+            pageInfo = null;
+
+            // Only get rid of the pageNodes if in production.
+            // In development mode, they are used for detailed
+            // error messages.
+            // http://issues.apache.org/bugzilla/show_bug.cgi?id=37062
+            if (!this.options.getDevelopment()) {
+                pageNodes = null;
+            }
+
+            if (ctxt.getWriter() != null) {
+                ctxt.getWriter().close();
+                ctxt.setWriter(null);
+            }
+        }
+    }
+
+    /**
+     * This is a protected method intended to be overridden by subclasses of
+     * Compiler. This is used by the compile method to do all the compilation.
+     */
+    public boolean isOutDated() {
+        return isOutDated(true);
+    }
+
+    /**
+     * Determine if a compilation is necessary by checking the time stamp of the
+     * JSP page with that of the corresponding .class or .java file. If the page
+     * has dependencies, the check is also extended to its dependents, and so
+     * on. This method can by overridden by a subclasses of Compiler.
+     * 
+     * @param checkClass
+     *            If true, check against .class file, if false, check against
+     *            .java file.
+     */
+    public boolean isOutDated(boolean checkClass) {
+
+        String jsp = ctxt.getJspFile();
+
+        if (jsw != null
+                && (ctxt.getOptions().getModificationTestInterval() > 0)) {
+
+            if (jsw.getLastModificationTest()
+                    + (ctxt.getOptions().getModificationTestInterval() * 1000) > System
+                    .currentTimeMillis()) {
+                return false;
+            }
+            jsw.setLastModificationTest(System.currentTimeMillis());
+        }
+
+        long jspRealLastModified = 0;
+        try {
+            URL jspUrl = ctxt.getResource(jsp);
+            if (jspUrl == null) {
+                ctxt.incrementRemoved();
+                return true;
+            }
+            URLConnection uc = jspUrl.openConnection();
+            if (uc instanceof JarURLConnection) {
+                jspRealLastModified =
+                    ((JarURLConnection) uc).getJarEntry().getTime();
+            } else {
+                jspRealLastModified = uc.getLastModified();
+            }
+            uc.getInputStream().close();
+        } catch (Exception e) {
+            if (log.isDebugEnabled())
+                log.debug("Problem accessing resource. Treat as outdated.", e);
+            return true;
+        }
+
+        long targetLastModified = 0;
+        File targetFile;
+
+        if (checkClass) {
+            targetFile = new File(ctxt.getClassFileName());
+        } else {
+            targetFile = new File(ctxt.getServletJavaFileName());
+        }
+
+        if (!targetFile.exists()) {
+            return true;
+        }
+
+        targetLastModified = targetFile.lastModified();
+        if (checkClass && jsw != null) {
+            jsw.setServletClassLastModifiedTime(targetLastModified);
+        }
+        if (targetLastModified < jspRealLastModified) {
+            if (log.isDebugEnabled()) {
+                log.debug("Compiler: outdated: " + targetFile + " "
+                        + targetLastModified);
+            }
+            return true;
+        }
+
+        // determine if source dependent files (e.g. includes using include
+        // directives) have been changed.
+        if (jsw == null) {
+            return false;
+        }
+
+        List<String> depends = jsw.getDependants();
+        if (depends == null) {
+            return false;
+        }
+
+        Iterator<String> it = depends.iterator();
+        while (it.hasNext()) {
+            String include = it.next();
+            try {
+                URL includeUrl = ctxt.getResource(include);
+                if (includeUrl == null) {
+                    return true;
+                }
+
+                URLConnection iuc = includeUrl.openConnection();
+                long includeLastModified = 0;
+                if (iuc instanceof JarURLConnection) {
+                    includeLastModified =
+                        ((JarURLConnection) iuc).getJarEntry().getTime();
+                } else {
+                    includeLastModified = iuc.getLastModified();
+                }
+                iuc.getInputStream().close();
+
+                if (includeLastModified > targetLastModified) {
+                    return true;
+                }
+            } catch (Exception e) {
+                if (log.isDebugEnabled())
+                    log.debug("Problem accessing resource. Treat as outdated.",
+                            e);
+                return true;
+            }
+        }
+
+        return false;
+
+    }
+
+    /**
+     * Gets the error dispatcher.
+     */
+    public ErrorDispatcher getErrorDispatcher() {
+        return errDispatcher;
+    }
+
+    /**
+     * Gets the info about the page under compilation
+     */
+    public PageInfo getPageInfo() {
+        return pageInfo;
+    }
+
+    public JspCompilationContext getCompilationContext() {
+        return ctxt;
+    }
+
+    /**
+     * Remove generated files
+     */
+    public void removeGeneratedFiles() {
+        try {
+            String classFileName = ctxt.getClassFileName();
+            if (classFileName != null) {
+                File classFile = new File(classFileName);
+                if (log.isDebugEnabled())
+                    log.debug("Deleting " + classFile);
+                if (classFile.exists()) {
+                    if (!classFile.delete()) {
+                        log.warn(Localizer.getMessage(
+                                "jsp.warning.compiler.classfile.delete.fail",
+                                classFile.getAbsolutePath()));
+                    }
+                }
+            }
+        } catch (Exception e) {
+            // Remove as much as possible, ignore possible exceptions
+        }
+        try {
+            String javaFileName = ctxt.getServletJavaFileName();
+            if (javaFileName != null) {
+                File javaFile = new File(javaFileName);
+                if (log.isDebugEnabled())
+                    log.debug("Deleting " + javaFile);
+                if (javaFile.exists()) {
+                    if (!javaFile.delete()) {
+                        log.warn(Localizer.getMessage(
+                                "jsp.warning.compiler.javafile.delete.fail",
+                                javaFile.getAbsolutePath()));
+                    }
+                }
+            }
+        } catch (Exception e) {
+            // Remove as much as possible, ignore possible exceptions
+        }
+    }
+
+    public void removeGeneratedClassFiles() {
+        try {
+            String classFileName = ctxt.getClassFileName();
+            if (classFileName != null) {
+                File classFile = new File(classFileName);
+                if (log.isDebugEnabled())
+                    log.debug("Deleting " + classFile);
+                if (classFile.exists()) {
+                    if (!classFile.delete()) {
+                        log.warn(Localizer.getMessage(
+                                "jsp.warning.compiler.classfile.delete.fail",
+                                classFile.getAbsolutePath()));
+                    }
+                }
+            }
+        } catch (Exception e) {
+            // Remove as much as possible, ignore possible exceptions
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/DefaultErrorHandler.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/DefaultErrorHandler.java
new file mode 100644
index 0000000..f12ff5f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/DefaultErrorHandler.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.jasper.compiler;
+
+import org.apache.jasper.JasperException;
+
+/**
+ * Default implementation of ErrorHandler interface.
+ *
+ * @author Jan Luehe
+ */
+class DefaultErrorHandler implements ErrorHandler {
+    
+    /*
+     * Processes the given JSP parse error.
+     *
+     * @param fname Name of the JSP file in which the parse error occurred
+     * @param line Parse error line number
+     * @param column Parse error column number
+     * @param errMsg Parse error message
+     * @param exception Parse exception
+     */
+    @Override
+    public void jspError(String fname, int line, int column, String errMsg,
+            Exception ex) throws JasperException {
+        throw new JasperException(fname + "(" + line + "," + column + ")"
+                + " " + errMsg, ex);
+    }
+    
+    /*
+     * Processes the given JSP parse error.
+     *
+     * @param errMsg Parse error message
+     * @param exception Parse exception
+     */
+    @Override
+    public void jspError(String errMsg, Exception ex) throws JasperException {
+        throw new JasperException(errMsg, ex);
+    }
+    
+    /*
+     * Processes the given javac compilation errors.
+     *
+     * @param details Array of JavacErrorDetail instances corresponding to the
+     * compilation errors
+     */
+    @Override
+    public void javacError(JavacErrorDetail[] details) throws JasperException {
+        
+        if (details == null) {
+            return;
+        }
+        
+        Object[] args = null;
+        StringBuilder buf = new StringBuilder();
+        
+        for (int i=0; i < details.length; i++) {
+            if (details[i].getJspBeginLineNumber() >= 0) {
+                args = new Object[] {
+                        new Integer(details[i].getJspBeginLineNumber()), 
+                        details[i].getJspFileName() };
+                buf.append("\n\n");
+                buf.append(Localizer.getMessage("jsp.error.single.line.number",
+                        args));
+                buf.append("\n");
+                buf.append(details[i].getErrorMessage());
+                buf.append("\n");
+                buf.append(details[i].getJspExtract());
+            } else {
+                args = new Object[] {
+                        new Integer(details[i].getJavaLineNumber()) };
+                buf.append("\n\n");
+                buf.append(Localizer.getMessage("jsp.error.java.line.number",
+                        args));
+                buf.append("\n");
+                buf.append(details[i].getErrorMessage());
+            }
+        }
+        buf.append("\n\nStacktrace:");
+        throw new JasperException(
+                Localizer.getMessage("jsp.error.unable.compile") + ": " + buf);
+    }
+    
+    /**
+     * Processes the given javac error report and exception.
+     *
+     * @param errorReport Compilation error report
+     * @param exception Compilation exception
+     */
+    @Override
+    public void javacError(String errorReport, Exception exception)
+    throws JasperException {
+        
+        throw new JasperException(
+                Localizer.getMessage("jsp.error.unable.compile"), exception);
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Dumper.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Dumper.java
new file mode 100644
index 0000000..9803566
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Dumper.java
@@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import org.apache.jasper.JasperException;
+import org.xml.sax.Attributes;
+
+class Dumper {
+
+    static class DumpVisitor extends Node.Visitor {
+        private int indent = 0;
+
+        private String getAttributes(Attributes attrs) {
+            if (attrs == null)
+                return "";
+
+            StringBuilder buf = new StringBuilder();
+            for (int i=0; i < attrs.getLength(); i++) {
+                buf.append(" " + attrs.getQName(i) + "=\""
+                           + attrs.getValue(i) + "\"");
+            }
+            return buf.toString();
+        }
+
+        private void printString(String str) {
+            printIndent();
+            System.out.print(str);
+        }
+
+        private void printString(String prefix, String str, String suffix) {
+            printIndent();
+            if (str != null) {
+                System.out.print(prefix + str + suffix);
+            } else {
+                System.out.print(prefix + suffix);
+            }
+        }
+
+        private void printAttributes(String prefix, Attributes attrs,
+                                     String suffix) {
+            printString(prefix, getAttributes(attrs), suffix);
+        }
+
+        private void dumpBody(Node n) throws JasperException {
+            Node.Nodes page = n.getBody();
+            if (page != null) {
+//                indent++;
+                page.visit(this);
+//                indent--;
+            }
+        }
+
+        @Override
+        public void visit(Node.PageDirective n) throws JasperException {
+            printAttributes("<%@ page", n.getAttributes(), "%>");
+        }
+
+        @Override
+        public void visit(Node.TaglibDirective n) throws JasperException {
+            printAttributes("<%@ taglib", n.getAttributes(), "%>");
+        }
+
+        @Override
+        public void visit(Node.IncludeDirective n) throws JasperException {
+            printAttributes("<%@ include", n.getAttributes(), "%>");
+            dumpBody(n);
+        }
+
+        @Override
+        public void visit(Node.Comment n) throws JasperException {
+            printString("<%--", n.getText(), "--%>");
+        }
+
+        @Override
+        public void visit(Node.Declaration n) throws JasperException {
+            printString("<%!", n.getText(), "%>");
+        }
+
+        @Override
+        public void visit(Node.Expression n) throws JasperException {
+            printString("<%=", n.getText(), "%>");
+        }
+
+        @Override
+        public void visit(Node.Scriptlet n) throws JasperException {
+            printString("<%", n.getText(), "%>");
+        }
+
+        @Override
+        public void visit(Node.IncludeAction n) throws JasperException {
+            printAttributes("<jsp:include", n.getAttributes(), ">");
+            dumpBody(n);
+            printString("</jsp:include>");
+        }
+
+        @Override
+        public void visit(Node.ForwardAction n) throws JasperException {
+            printAttributes("<jsp:forward", n.getAttributes(), ">");
+            dumpBody(n);
+            printString("</jsp:forward>");
+        }
+
+        @Override
+        public void visit(Node.GetProperty n) throws JasperException {
+            printAttributes("<jsp:getProperty", n.getAttributes(), "/>");
+        }
+
+        @Override
+        public void visit(Node.SetProperty n) throws JasperException {
+            printAttributes("<jsp:setProperty", n.getAttributes(), ">");
+            dumpBody(n);
+            printString("</jsp:setProperty>");
+        }
+
+        @Override
+        public void visit(Node.UseBean n) throws JasperException {
+            printAttributes("<jsp:useBean", n.getAttributes(), ">");
+            dumpBody(n);
+            printString("</jsp:useBean>");
+        }
+        
+        @Override
+        public void visit(Node.PlugIn n) throws JasperException {
+            printAttributes("<jsp:plugin", n.getAttributes(), ">");
+            dumpBody(n);
+            printString("</jsp:plugin>");
+        }
+        
+        @Override
+        public void visit(Node.ParamsAction n) throws JasperException {
+            printAttributes("<jsp:params", n.getAttributes(), ">");
+            dumpBody(n);
+            printString("</jsp:params>");
+        }
+        
+        @Override
+        public void visit(Node.ParamAction n) throws JasperException {
+            printAttributes("<jsp:param", n.getAttributes(), ">");
+            dumpBody(n);
+            printString("</jsp:param>");
+        }
+        
+        @Override
+        public void visit(Node.NamedAttribute n) throws JasperException {
+            printAttributes("<jsp:attribute", n.getAttributes(), ">");
+            dumpBody(n);
+            printString("</jsp:attribute>");
+        }
+
+        @Override
+        public void visit(Node.JspBody n) throws JasperException {
+            printAttributes("<jsp:body", n.getAttributes(), ">");
+            dumpBody(n);
+            printString("</jsp:body>");
+        }
+        
+        @Override
+        public void visit(Node.ELExpression n) throws JasperException {
+            printString( "${" + n.getText() + "}" );
+        }
+
+        @Override
+        public void visit(Node.CustomTag n) throws JasperException {
+            printAttributes("<" + n.getQName(), n.getAttributes(), ">");
+            dumpBody(n);
+            printString("</" + n.getQName() + ">");
+        }
+
+        @Override
+        public void visit(Node.UninterpretedTag n) throws JasperException {
+            String tag = n.getQName();
+            printAttributes("<"+tag, n.getAttributes(), ">");
+            dumpBody(n);
+            printString("</" + tag + ">");
+        }
+
+        @Override
+        public void visit(Node.TemplateText n) throws JasperException {
+            printString(n.getText());
+        }
+
+        private void printIndent() {
+            for (int i=0; i < indent; i++) {
+                System.out.print("  ");
+            }
+        }
+    }
+
+    public static void dump(Node n) {
+        try {
+            n.accept(new DumpVisitor());        
+        } catch (JasperException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static void dump(Node.Nodes page) {
+        try {
+            page.visit(new DumpVisitor());
+        } catch (JasperException e) {
+            e.printStackTrace();
+        }
+    }
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ELFunctionMapper.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ELFunctionMapper.java
new file mode 100644
index 0000000..a3aba09
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ELFunctionMapper.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.jasper.compiler;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import javax.servlet.jsp.tagext.FunctionInfo;
+
+import org.apache.jasper.Constants;
+import org.apache.jasper.JasperException;
+
+/**
+ * This class generates functions mappers for the EL expressions in the page.
+ * Instead of a global mapper, a mapper is used for each call to EL
+ * evaluator, thus avoiding the prefix overlapping and redefinition
+ * issues.
+ *
+ * @author Kin-man Chung
+ */
+
+public class ELFunctionMapper {
+    private int currFunc = 0;
+    StringBuilder ds;  // Contains codes to initialize the functions mappers.
+    StringBuilder ss;  // Contains declarations of the functions mappers.
+
+    /**
+     * Creates the functions mappers for all EL expressions in the JSP page.
+     *
+     * @param page The current compilation unit.
+     */
+    public static void map(Node.Nodes page) 
+                throws JasperException {
+
+        ELFunctionMapper map = new ELFunctionMapper();
+        map.ds = new StringBuilder();
+        map.ss = new StringBuilder();
+
+        page.visit(map.new ELFunctionVisitor());
+
+        // Append the declarations to the root node
+        String ds = map.ds.toString();
+        if (ds.length() > 0) {
+            Node root = page.getRoot();
+            new Node.Declaration(map.ss.toString(), null, root);
+            new Node.Declaration("static {\n" + ds + "}\n", null, root);
+        }
+    }
+
+    /**
+     * A visitor for the page.  The places where EL is allowed are scanned
+     * for functions, and if found functions mappers are created.
+     */
+    class ELFunctionVisitor extends Node.Visitor {
+        
+        /**
+         * Use a global name map to facilitate reuse of function maps.
+         * The key used is prefix:function:uri.
+         */
+        private HashMap<String, String> gMap = new HashMap<String, String>();
+
+        @Override
+        public void visit(Node.ParamAction n) throws JasperException {
+            doMap(n.getValue());
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.IncludeAction n) throws JasperException {
+            doMap(n.getPage());
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.ForwardAction n) throws JasperException {
+            doMap(n.getPage());
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.SetProperty n) throws JasperException {
+            doMap(n.getValue());
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.UseBean n) throws JasperException {
+            doMap(n.getBeanName());
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.PlugIn n) throws JasperException {
+            doMap(n.getHeight());
+            doMap(n.getWidth());
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.JspElement n) throws JasperException {
+
+            Node.JspAttribute[] attrs = n.getJspAttributes();
+            for (int i = 0; attrs != null && i < attrs.length; i++) {
+                doMap(attrs[i]);
+            }
+            doMap(n.getNameAttribute());
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.UninterpretedTag n) throws JasperException {
+
+            Node.JspAttribute[] attrs = n.getJspAttributes();
+            for (int i = 0; attrs != null && i < attrs.length; i++) {
+                doMap(attrs[i]);
+            }
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.CustomTag n) throws JasperException {
+            Node.JspAttribute[] attrs = n.getJspAttributes();
+            for (int i = 0; attrs != null && i < attrs.length; i++) {
+                doMap(attrs[i]);
+            }
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.ELExpression n) throws JasperException {
+            doMap(n.getEL());
+        }
+
+        private void doMap(Node.JspAttribute attr) 
+                throws JasperException {
+            if (attr != null) {
+                doMap(attr.getEL());
+            }
+        }
+
+        /**
+         * Creates function mappers, if needed, from ELNodes
+         */
+        private void doMap(ELNode.Nodes el) 
+                throws JasperException {
+
+            // Only care about functions in ELNode's
+            class Fvisitor extends ELNode.Visitor {
+                ArrayList<ELNode.Function> funcs =
+                    new ArrayList<ELNode.Function>();
+                HashMap<String, String> keyMap = new HashMap<String, String>();
+                @Override
+                public void visit(ELNode.Function n) throws JasperException {
+                    String key = n.getPrefix() + ":" + n.getName();
+                    if (! keyMap.containsKey(key)) {
+                        keyMap.put(key,"");
+                        funcs.add(n);
+                    }
+                }
+            }
+
+            if (el == null) {
+                return;
+            }
+
+            // First locate all unique functions in this EL
+            Fvisitor fv = new Fvisitor();
+            el.visit(fv);
+            ArrayList<ELNode.Function> functions = fv.funcs;
+
+            if (functions.size() == 0) {
+                return;
+            }
+
+            // Reuse a previous map if possible
+            String decName = matchMap(functions);
+            if (decName != null) {
+                el.setMapName(decName);
+                return;
+            }
+        
+            // Generate declaration for the map statically
+            decName = getMapName();
+            ss.append("private static org.apache.jasper.runtime.ProtectedFunctionMapper " + decName + ";\n");
+
+            ds.append("  " + decName + "= ");
+            ds.append("org.apache.jasper.runtime.ProtectedFunctionMapper");
+
+            // Special case if there is only one function in the map
+            String funcMethod = null;
+            if (functions.size() == 1) {
+                funcMethod = ".getMapForFunction";
+            } else {
+                ds.append(".getInstance();\n");
+                funcMethod = "  " + decName + ".mapFunction";
+            }
+
+            // Setup arguments for either getMapForFunction or mapFunction
+            for (int i = 0; i < functions.size(); i++) {
+                ELNode.Function f = functions.get(i);
+                FunctionInfo funcInfo = f.getFunctionInfo();
+                String key = f.getPrefix()+ ":" + f.getName();
+                ds.append(funcMethod + "(\"" + key + "\", " +
+                        getCanonicalName(funcInfo.getFunctionClass()) +
+                        ".class, " + '\"' + f.getMethodName() + "\", " +
+                        "new Class[] {");
+                String params[] = f.getParameters();
+                for (int k = 0; k < params.length; k++) {
+                    if (k != 0) {
+                        ds.append(", ");
+                    }
+                    int iArray = params[k].indexOf('[');
+                    if (iArray < 0) {
+                        ds.append(params[k] + ".class");
+                    }
+                    else {
+                        String baseType = params[k].substring(0, iArray);
+                        ds.append("java.lang.reflect.Array.newInstance(");
+                        ds.append(baseType);
+                        ds.append(".class,");
+
+                        // Count the number of array dimension
+                        int aCount = 0;
+                        for (int jj = iArray; jj < params[k].length(); jj++ ) {
+                            if (params[k].charAt(jj) == '[') {
+                                aCount++;
+                            }
+                        }
+                        if (aCount == 1) {
+                            ds.append("0).getClass()");
+                        } else {
+                            ds.append("new int[" + aCount + "]).getClass()");
+                        }
+                    }
+                }
+                ds.append("});\n");
+                // Put the current name in the global function map
+                gMap.put(f.getPrefix() + ':' + f.getName() + ':' + f.getUri(),
+                         decName);
+            }
+            el.setMapName(decName);
+        }
+
+        /**
+         * Find the name of the function mapper for an EL.  Reuse a
+         * previously generated one if possible.
+         * @param functions An ArrayList of ELNode.Function instances that
+         *                  represents the functions in an EL
+         * @return A previous generated function mapper name that can be used
+         *         by this EL; null if none found.
+         */
+        private String matchMap(ArrayList<ELNode.Function> functions) {
+
+            String mapName = null;
+            for (int i = 0; i < functions.size(); i++) {
+                ELNode.Function f = functions.get(i);
+                String temName = gMap.get(f.getPrefix() + ':' + f.getName() +
+                        ':' + f.getUri());
+                if (temName == null) {
+                    return null;
+                }
+                if (mapName == null) {
+                    mapName = temName;
+                } else if (!temName.equals(mapName)) {
+                    // If not all in the previous match, then no match.
+                    return null;
+                }
+            }
+            return mapName;
+        }
+
+        /*
+         * @return An unique name for a function mapper.
+         */
+        private String getMapName() {
+            return "_jspx_fnmap_" + currFunc++;
+        }
+
+        /**
+         * Convert a binary class name into a canonical one that can be used
+         * when generating Java source code.
+         * 
+         * @param className Binary class name
+         * @return          Canonical equivalent
+         */
+        private String getCanonicalName(String className) throws JasperException {
+            Class<?> clazz;
+            
+            ClassLoader tccl;
+            if (Constants.IS_SECURITY_ENABLED) {
+                PrivilegedAction<ClassLoader> pa = new PrivilegedGetTccl();
+                tccl = AccessController.doPrivileged(pa);
+            } else {
+                tccl = Thread.currentThread().getContextClassLoader();
+            }
+
+            try {
+                clazz = Class.forName(className, false, tccl);
+            } catch (ClassNotFoundException e) {
+                throw new JasperException(e);
+            }
+            return clazz.getCanonicalName();
+        }
+    }
+    
+    private static class PrivilegedGetTccl
+            implements PrivilegedAction<ClassLoader> {
+
+        @Override
+        public ClassLoader run() {
+            return Thread.currentThread().getContextClassLoader();
+        }
+    }
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ELNode.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ELNode.java
new file mode 100644
index 0000000..0bd5971
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ELNode.java
@@ -0,0 +1,269 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.servlet.jsp.tagext.FunctionInfo;
+
+import org.apache.jasper.JasperException;
+
+/**
+ * This class defines internal representation for an EL Expression
+ *
+ * It currently only defines functions.  It can be expanded to define
+ * all the components of an EL expression, if need to.
+ *
+ * @author Kin-man Chung
+ */
+
+abstract class ELNode {
+
+    public abstract void accept(Visitor v) throws JasperException;
+
+    /**
+     * Child classes
+     */
+
+
+    /**
+     * Represents an EL expression: anything in ${ and }.
+     */
+    public static class Root extends ELNode {
+
+        private ELNode.Nodes expr;
+    private char type;
+
+        Root(ELNode.Nodes expr, char type) {
+            this.expr = expr;
+        this.type = type;
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public ELNode.Nodes getExpression() {
+            return expr;
+        }
+
+    public char getType() {
+        return type;
+    }
+    }
+
+    /**
+     * Represents text outside of EL expression.
+     */
+    public static class Text extends ELNode {
+
+        private String text;
+
+        Text(String text) {
+            this.text = text;
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public String getText() {
+            return text;
+        }
+    }
+
+    /**
+     * Represents anything in EL expression, other than functions, including
+     * function arguments etc
+     */
+    public static class ELText extends ELNode {
+
+        private String text;
+
+        ELText(String text) {
+            this.text = text;
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public String getText() {
+            return text;
+        }
+    }
+
+    /**
+     * Represents a function
+     * Currently only include the prefix and function name, but not its
+     * arguments.
+     */
+    public static class Function extends ELNode {
+
+        private String prefix;
+        private String name;
+        private String uri;
+        private FunctionInfo functionInfo;
+        private String methodName;
+        private String[] parameters;
+
+        Function(String prefix, String name) {
+            this.prefix = prefix;
+            this.name = name;
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public String getPrefix() {
+            return prefix;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void setUri(String uri) {
+            this.uri = uri;
+        }
+
+        public String getUri() {
+            return uri;
+        }
+
+        public void setFunctionInfo(FunctionInfo f) {
+            this.functionInfo = f;
+        }
+
+        public FunctionInfo getFunctionInfo() {
+            return functionInfo;
+        }
+
+        public void setMethodName(String methodName) {
+            this.methodName = methodName;
+        }
+
+        public String getMethodName() {
+            return methodName;
+        }
+
+        public void setParameters(String[] parameters) {
+            this.parameters = parameters;
+        }
+
+        public String[] getParameters() {
+            return parameters;
+        }
+    }
+
+    /**
+     * An ordered list of ELNode.
+     */
+    public static class Nodes {
+
+        /* Name used for creating a map for the functions in this
+           EL expression, for communication to Generator.
+         */
+        String mapName = null;        // The function map associated this EL
+        private List<ELNode> list;
+
+        public Nodes() {
+            list = new ArrayList<ELNode>();
+        }
+
+        public void add(ELNode en) {
+            list.add(en);
+        }
+
+        /**
+         * Visit the nodes in the list with the supplied visitor
+         * @param v The visitor used
+         */
+        public void visit(Visitor v) throws JasperException {
+            Iterator<ELNode> iter = list.iterator();
+            while (iter.hasNext()) {
+                ELNode n = iter.next();
+                n.accept(v);
+            }
+        }
+
+        public Iterator<ELNode> iterator() {
+            return list.iterator();
+        }
+
+        public boolean isEmpty() {
+            return list.size() == 0;
+        }
+
+        /**
+         * @return true if the expression contains a ${...}
+         */
+        public boolean containsEL() {
+            Iterator<ELNode> iter = list.iterator();
+            while (iter.hasNext()) {
+                ELNode n = iter.next();
+                if (n instanceof Root) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public void setMapName(String name) {
+            this.mapName = name;
+        }
+
+        public String getMapName() {
+            return mapName;
+        }
+    
+    }
+
+    /*
+     * A visitor class for traversing ELNodes
+     */
+    public static class Visitor {
+
+        public void visit(Root n) throws JasperException {
+            n.getExpression().visit(this);
+        }
+
+        @SuppressWarnings("unused")
+        public void visit(Function n) throws JasperException {
+            // NOOP by default
+        }
+
+        @SuppressWarnings("unused")
+        public void visit(Text n) throws JasperException {
+            // NOOP by default
+        }
+
+        @SuppressWarnings("unused")
+        public void visit(ELText n) throws JasperException {
+            // NOOP by default
+        }
+    }
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ELParser.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ELParser.java
new file mode 100644
index 0000000..4a64b56
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ELParser.java
@@ -0,0 +1,401 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/**
+ * This class implements a parser for EL expressions.
+ * 
+ * It takes strings of the form xxx${..}yyy${..}zzz etc, and turn it into a
+ * ELNode.Nodes.
+ * 
+ * Currently, it only handles text outside ${..} and functions in ${ ..}.
+ * 
+ * @author Kin-man Chung
+ */
+
+public class ELParser {
+
+    private Token curToken;  // current token
+    private Token prevToken; // previous token
+
+    private ELNode.Nodes expr;
+
+    private ELNode.Nodes ELexpr;
+
+    private int index; // Current index of the expression
+
+    private String expression; // The EL expression
+    
+    private char type;
+
+    private boolean escapeBS; // is '\' an escape char in text outside EL?
+
+    private final boolean isDeferredSyntaxAllowedAsLiteral;
+
+    private static final String reservedWords[] = { "and", "div", "empty",
+            "eq", "false", "ge", "gt", "instanceof", "le", "lt", "mod", "ne",
+            "not", "null", "or", "true" };
+
+    public ELParser(String expression, boolean isDeferredSyntaxAllowedAsLiteral) {
+        index = 0;
+        this.expression = expression;
+        this.isDeferredSyntaxAllowedAsLiteral = isDeferredSyntaxAllowedAsLiteral;
+        expr = new ELNode.Nodes();
+    }
+
+    /**
+     * Parse an EL expression
+     * 
+     * @param expression
+     *            The input expression string of the form Char* ('${' Char*
+     *            '}')* Char*
+     * @param isDeferredSyntaxAllowedAsLiteral
+     *                      Are deferred expressions treated as literals?
+     * @return Parsed EL expression in ELNode.Nodes
+     */
+    public static ELNode.Nodes parse(String expression,
+            boolean isDeferredSyntaxAllowedAsLiteral) {
+        ELParser parser = new ELParser(expression,
+                isDeferredSyntaxAllowedAsLiteral);
+        while (parser.hasNextChar()) {
+            String text = parser.skipUntilEL();
+            if (text.length() > 0) {
+                parser.expr.add(new ELNode.Text(text));
+            }
+            ELNode.Nodes elexpr = parser.parseEL();
+            if (!elexpr.isEmpty()) {
+                parser.expr.add(new ELNode.Root(elexpr, parser.type));
+            }
+        }
+        return parser.expr;
+    }
+
+    /**
+     * Parse an EL expression string '${...}'. Currently only separates the EL
+     * into functions and everything else.
+     * 
+     * @return An ELNode.Nodes representing the EL expression
+     * 
+     * TODO: Can this be refactored to use the standard EL implementation?
+     */
+    private ELNode.Nodes parseEL() {
+
+        StringBuilder buf = new StringBuilder();
+        ELexpr = new ELNode.Nodes();
+        while (hasNext()) {
+            curToken = nextToken();
+            if (curToken instanceof Char) {
+                if (curToken.toChar() == '}') {
+                    break;
+                }
+                buf.append(curToken.toChar());
+            } else {
+                // Output whatever is in buffer
+                if (buf.length() > 0) {
+                    ELexpr.add(new ELNode.ELText(buf.toString()));
+                }
+                if (!parseFunction()) {
+                    ELexpr.add(new ELNode.ELText(curToken.toString()));
+                }
+            }
+        }
+        if (buf.length() > 0) {
+            ELexpr.add(new ELNode.ELText(buf.toString()));
+        }
+
+        return ELexpr;
+    }
+
+    /**
+     * Parse for a function FunctionInvokation ::= (identifier ':')? identifier
+     * '(' (Expression (,Expression)*)? ')' Note: currently we don't parse
+     * arguments
+     */
+    private boolean parseFunction() {
+        if (!(curToken instanceof Id) || isELReserved(curToken.toString()) ||
+                prevToken instanceof Char && prevToken.toChar() == '.') {
+            return false;
+        }
+        String s1 = null; // Function prefix
+        String s2 = curToken.toString(); // Function name
+        int mark = getIndex();
+        if (hasNext()) {
+            curToken = nextToken();
+            if (curToken.toChar() == ':') {
+                if (hasNext()) {
+                    Token t2 = nextToken();
+                    if (t2 instanceof Id) {
+                        s1 = s2;
+                        s2 = t2.toString();
+                        if (hasNext()) {
+                            curToken = nextToken();
+                        }
+                    }
+                }
+            }
+            if (curToken.toChar() == '(') {
+                ELexpr.add(new ELNode.Function(s1, s2));
+                return true;
+            }
+        }
+        setIndex(mark);
+        return false;
+    }
+
+    /**
+     * Test if an id is a reserved word in EL
+     */
+    private boolean isELReserved(String id) {
+        int i = 0;
+        int j = reservedWords.length;
+        while (i < j) {
+            int k = (i + j) / 2;
+            int result = reservedWords[k].compareTo(id);
+            if (result == 0) {
+                return true;
+            }
+            if (result < 0) {
+                i = k + 1;
+            } else {
+                j = k;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Skip until an EL expression ('${' || '#{') is reached, allowing escape
+     * sequences '\\' and '\$' and '\#'.
+     * 
+     * @return The text string up to the EL expression
+     */
+    private String skipUntilEL() {
+        char prev = 0;
+        StringBuilder buf = new StringBuilder();
+        while (hasNextChar()) {
+            char ch = nextChar();
+            if (prev == '\\') {
+                prev = 0;
+                if (ch == '\\') {
+                    buf.append('\\');
+                    if (!escapeBS)
+                        prev = '\\';
+                } else if (ch == '$'
+                        || (!isDeferredSyntaxAllowedAsLiteral && ch == '#')) {
+                    buf.append(ch);
+                }
+                // else error!
+            } else if (prev == '$'
+                    || (!isDeferredSyntaxAllowedAsLiteral && prev == '#')) {
+                if (ch == '{') {
+                    this.type = prev;
+                    prev = 0;
+                    break;
+                }
+                buf.append(prev);
+                prev = 0;
+            }
+            if (ch == '\\' || ch == '$'
+                    || (!isDeferredSyntaxAllowedAsLiteral && ch == '#')) {
+                prev = ch;
+            } else {
+                buf.append(ch);
+            }
+        }
+        if (prev != 0) {
+            buf.append(prev);
+        }
+        return buf.toString();
+    }
+
+    /*
+     * @return true if there is something left in EL expression buffer other
+     * than white spaces.
+     */
+    private boolean hasNext() {
+        skipSpaces();
+        return hasNextChar();
+    }
+
+    /*
+     * @return The next token in the EL expression buffer.
+     */
+    private Token nextToken() {
+        prevToken = curToken;
+        skipSpaces();
+        if (hasNextChar()) {
+            char ch = nextChar();
+            if (Character.isJavaIdentifierStart(ch)) {
+                StringBuilder buf = new StringBuilder();
+                buf.append(ch);
+                while ((ch = peekChar()) != -1
+                        && Character.isJavaIdentifierPart(ch)) {
+                    buf.append(ch);
+                    nextChar();
+                }
+                return new Id(buf.toString());
+            }
+
+            if (ch == '\'' || ch == '"') {
+                return parseQuotedChars(ch);
+            } else {
+                // For now...
+                return new Char(ch);
+            }
+        }
+        return null;
+    }
+
+    /*
+     * Parse a string in single or double quotes, allowing for escape sequences
+     * '\\', and ('\"', or "\'")
+     */
+    private Token parseQuotedChars(char quote) {
+        StringBuilder buf = new StringBuilder();
+        buf.append(quote);
+        while (hasNextChar()) {
+            char ch = nextChar();
+            if (ch == '\\') {
+                ch = nextChar();
+                if (ch == '\\' || ch == quote) {
+                    buf.append(ch);
+                }
+                // else error!
+            } else if (ch == quote) {
+                buf.append(ch);
+                break;
+            } else {
+                buf.append(ch);
+            }
+        }
+        return new QuotedString(buf.toString());
+    }
+
+    /*
+     * A collection of low level parse methods dealing with character in the EL
+     * expression buffer.
+     */
+
+    private void skipSpaces() {
+        while (hasNextChar()) {
+            if (expression.charAt(index) > ' ')
+                break;
+            index++;
+        }
+    }
+
+    private boolean hasNextChar() {
+        return index < expression.length();
+    }
+
+    private char nextChar() {
+        if (index >= expression.length()) {
+            return (char) -1;
+        }
+        return expression.charAt(index++);
+    }
+
+    private char peekChar() {
+        if (index >= expression.length()) {
+            return (char) -1;
+        }
+        return expression.charAt(index);
+    }
+
+    private int getIndex() {
+        return index;
+    }
+
+    private void setIndex(int i) {
+        index = i;
+    }
+
+    /*
+     * Represents a token in EL expression string
+     */
+    private static class Token {
+
+        char toChar() {
+            return 0;
+        }
+
+        @Override
+        public String toString() {
+            return "";
+        }
+    }
+
+    /*
+     * Represents an ID token in EL
+     */
+    private static class Id extends Token {
+        String id;
+
+        Id(String id) {
+            this.id = id;
+        }
+
+        @Override
+        public String toString() {
+            return id;
+        }
+    }
+
+    /*
+     * Represents a character token in EL
+     */
+    private static class Char extends Token {
+
+        private char ch;
+
+        Char(char ch) {
+            this.ch = ch;
+        }
+
+        @Override
+        char toChar() {
+            return ch;
+        }
+
+        @Override
+        public String toString() {
+            return (new Character(ch)).toString();
+        }
+    }
+
+    /*
+     * Represents a quoted (single or double) string token in EL
+     */
+    private static class QuotedString extends Token {
+
+        private String value;
+
+        QuotedString(String v) {
+            this.value = v;
+        }
+
+        @Override
+        public String toString() {
+            return value;
+        }
+    }
+
+    public char getType() {
+        return type;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ErrorDispatcher.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ErrorDispatcher.java
new file mode 100644
index 0000000..395a54f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ErrorDispatcher.java
@@ -0,0 +1,616 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.compiler;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.xml.sax.SAXException;
+
+/**
+ * Class responsible for dispatching JSP parse and javac compilation errors
+ * to the configured error handler.
+ *
+ * This class is also responsible for localizing any error codes before they
+ * are passed on to the configured error handler.
+ * 
+ * In the case of a Java compilation error, the compiler error message is
+ * parsed into an array of JavacErrorDetail instances, which is passed on to 
+ * the configured error handler.
+ *
+ * @author Jan Luehe
+ * @author Kin-man Chung
+ */
+public class ErrorDispatcher {
+
+    // Custom error handler
+    private ErrorHandler errHandler;
+
+    // Indicates whether the compilation was initiated by JspServlet or JspC
+    private boolean jspcMode = false;
+
+
+    /*
+     * Constructor.
+     *
+     * @param jspcMode true if compilation has been initiated by JspC, false
+     * otherwise
+     */
+    public ErrorDispatcher(boolean jspcMode) {
+        // XXX check web.xml for custom error handler
+        errHandler = new DefaultErrorHandler();
+        this.jspcMode = jspcMode;
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param errCode Error code
+     */
+    public void jspError(String errCode) throws JasperException {
+        dispatch(null, errCode, null, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param where Error location
+     * @param errCode Error code
+     */
+    public void jspError(Mark where, String errCode) throws JasperException {
+        dispatch(where, errCode, null, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param n Node that caused the error
+     * @param errCode Error code
+     */
+    public void jspError(Node n, String errCode) throws JasperException {
+        dispatch(n.getStart(), errCode, null, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param errCode Error code
+     * @param arg Argument for parametric replacement
+     */
+    public void jspError(String errCode, String arg) throws JasperException {
+        dispatch(null, errCode, new Object[] {arg}, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param where Error location
+     * @param errCode Error code
+     * @param arg Argument for parametric replacement
+     */
+    public void jspError(Mark where, String errCode, String arg)
+                throws JasperException {
+        dispatch(where, errCode, new Object[] {arg}, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param n Node that caused the error
+     * @param errCode Error code
+     * @param arg Argument for parametric replacement
+     */
+    public void jspError(Node n, String errCode, String arg)
+                throws JasperException {
+        dispatch(n.getStart(), errCode, new Object[] {arg}, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param errCode Error code
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     */
+    public void jspError(String errCode, String arg1, String arg2)
+                throws JasperException {
+        dispatch(null, errCode, new Object[] {arg1, arg2}, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param errCode Error code
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     * @param arg3 Third argument for parametric replacement
+     */
+    public void jspError(String errCode, String arg1, String arg2, String arg3)
+                throws JasperException {
+        dispatch(null, errCode, new Object[] {arg1, arg2, arg3}, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param where Error location
+     * @param errCode Error code
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     */
+    public void jspError(Mark where, String errCode, String arg1, String arg2)
+                throws JasperException {
+        dispatch(where, errCode, new Object[] {arg1, arg2}, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param where Error location
+     * @param errCode Error code
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     * @param arg3 Third argument for parametric replacement
+     */
+
+    public void jspError(Mark where, String errCode, String arg1, String arg2,
+                         String arg3)
+                throws JasperException {
+        dispatch(where, errCode, new Object[] {arg1, arg2, arg3}, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param n Node that caused the error
+     * @param errCode Error code
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     */
+
+    public void jspError(Node n, String errCode, String arg1, String arg2)
+                throws JasperException {
+        dispatch(n.getStart(), errCode, new Object[] {arg1, arg2}, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param n Node that caused the error
+     * @param errCode Error code
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     * @param arg3 Third argument for parametric replacement
+     */
+
+    public void jspError(Node n, String errCode, String arg1, String arg2,
+                         String arg3)
+                throws JasperException {
+        dispatch(n.getStart(), errCode, new Object[] {arg1, arg2, arg3}, null);
+    }
+
+    /*
+     * Dispatches the given parsing exception to the configured error handler.
+     *
+     * @param e Parsing exception
+     */
+    public void jspError(Exception e) throws JasperException {
+        dispatch(null, null, null, e);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param errCode Error code
+     * @param arg Argument for parametric replacement
+     * @param e Parsing exception
+     */
+    public void jspError(String errCode, String arg, Exception e)
+                throws JasperException {
+        dispatch(null, errCode, new Object[] {arg}, e);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param n Node that caused the error
+     * @param errCode Error code
+     * @param arg Argument for parametric replacement
+     * @param e Parsing exception
+     */
+    public void jspError(Node n, String errCode, String arg, Exception e)
+                throws JasperException {
+        dispatch(n.getStart(), errCode, new Object[] {arg}, e);
+    }
+
+    /**
+     * Parses the given error message into an array of javac compilation error
+     * messages (one per javac compilation error line number).
+     *
+     * @param errMsg Error message
+     * @param fname Name of Java source file whose compilation failed
+     * @param page Node representation of JSP page from which the Java source
+     * file was generated
+     *
+     * @return Array of javac compilation errors, or null if the given error
+     * message does not contain any compilation error line numbers
+     */
+    public static JavacErrorDetail[] parseJavacErrors(String errMsg,
+                                                      String fname,
+                                                      Node.Nodes page)
+            throws JasperException, IOException {
+
+        return parseJavacMessage(errMsg, fname, page);
+    }
+
+    /*
+     * Dispatches the given javac compilation errors to the configured error
+     * handler.
+     *
+     * @param javacErrors Array of javac compilation errors
+     */
+    public void javacError(JavacErrorDetail[] javacErrors)
+            throws JasperException {
+
+        errHandler.javacError(javacErrors);
+    }
+
+
+    /*
+     * Dispatches the given compilation error report and exception to the
+     * configured error handler.
+     *
+     * @param errorReport Compilation error report
+     * @param e Compilation exception
+     */
+    public void javacError(String errorReport, Exception e)
+                throws JasperException {
+
+        errHandler.javacError(errorReport, e);
+    }
+
+
+    //*********************************************************************
+    // Private utility methods
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param where Error location
+     * @param errCode Error code
+     * @param args Arguments for parametric replacement
+     * @param e Parsing exception
+     */
+    private void dispatch(Mark where, String errCode, Object[] args,
+                          Exception e) throws JasperException {
+        String file = null;
+        String errMsg = null;
+        int line = -1;
+        int column = -1;
+        boolean hasLocation = false;
+
+        // Localize
+        if (errCode != null) {
+            errMsg = Localizer.getMessage(errCode, args);
+        } else if (e != null) {
+            // give a hint about what's wrong
+            errMsg = e.getMessage();
+        }
+
+        // Get error location
+        if (where != null) {
+            if (jspcMode) {
+                // Get the full URL of the resource that caused the error
+                try {
+                    file = where.getURL().toString();
+                } catch (MalformedURLException me) {
+                    // Fallback to using context-relative path
+                    file = where.getFile();
+                }
+            } else {
+                // Get the context-relative resource path, so as to not
+                // disclose any local filesystem details
+                file = where.getFile();
+            }
+            line = where.getLineNumber();
+            column = where.getColumnNumber();
+            hasLocation = true;
+        }
+
+        // Get nested exception
+        Exception nestedEx = e;
+        if ((e instanceof SAXException)
+                && (((SAXException) e).getException() != null)) {
+            nestedEx = ((SAXException) e).getException();
+        }
+
+        if (hasLocation) {
+            errHandler.jspError(file, line, column, errMsg, nestedEx);
+        } else {
+            errHandler.jspError(errMsg, nestedEx);
+        }
+    }
+
+    /*
+     * Parses the given Java compilation error message, which may contain one
+     * or more compilation errors, into an array of JavacErrorDetail instances.
+     *
+     * Each JavacErrorDetail instance contains the information about a single
+     * compilation error.
+     *
+     * @param errMsg Compilation error message that was generated by the
+     * javac compiler
+     * @param fname Name of Java source file whose compilation failed
+     * @param page Node representation of JSP page from which the Java source
+     * file was generated
+     *
+     * @return Array of JavacErrorDetail instances corresponding to the
+     * compilation errors
+     */
+    private static JavacErrorDetail[] parseJavacMessage(
+                                String errMsg, String fname, Node.Nodes page)
+                throws IOException, JasperException {
+
+        ArrayList<JavacErrorDetail> errors = new ArrayList<JavacErrorDetail>();
+        StringBuilder errMsgBuf = null;
+        int lineNum = -1;
+        JavacErrorDetail javacError = null;
+        
+        BufferedReader reader = new BufferedReader(new StringReader(errMsg));
+        
+        /*
+         * Parse compilation errors. Each compilation error consists of a file
+         * path and error line number, followed by a number of lines describing
+         * the error.
+         */
+        String line = null;
+        while ((line = reader.readLine()) != null) {
+            
+            /*
+             * Error line number is delimited by set of colons.
+             * Ignore colon following drive letter on Windows (fromIndex = 2).
+             * XXX Handle deprecation warnings that don't have line info
+             */
+            int beginColon = line.indexOf(':', 2); 
+            int endColon = line.indexOf(':', beginColon + 1);
+            if ((beginColon >= 0) && (endColon >= 0)) {
+                if (javacError != null) {
+                    // add previous error to error vector
+                    errors.add(javacError);
+                }
+                
+                String lineNumStr = line.substring(beginColon + 1, endColon);
+                try {
+                    lineNum = Integer.parseInt(lineNumStr);
+                } catch (NumberFormatException e) {
+                    lineNum = -1;
+                }
+                
+                errMsgBuf = new StringBuilder();
+                
+                javacError = createJavacError(fname, page, errMsgBuf, lineNum);
+            }
+            
+            // Ignore messages preceding first error
+            if (errMsgBuf != null) {
+                errMsgBuf.append(line);
+                errMsgBuf.append("\n");
+            }
+        }
+        
+        // Add last error to error vector
+        if (javacError != null) {
+            errors.add(javacError);
+        } 
+        
+        reader.close();
+        
+        JavacErrorDetail[] errDetails = null;
+        if (errors.size() > 0) {
+            errDetails = new JavacErrorDetail[errors.size()];
+            errors.toArray(errDetails);
+        }
+        
+        return errDetails;
+    }
+
+
+    /**
+     * @param fname
+     * @param page
+     * @param errMsgBuf
+     * @param lineNum
+     * @return JavacErrorDetail The error details
+     * @throws JasperException
+     */
+    public static JavacErrorDetail createJavacError(String fname,
+            Node.Nodes page, StringBuilder errMsgBuf, int lineNum)
+    throws JasperException {
+        return createJavacError(fname, page, errMsgBuf, lineNum, null);
+    }
+    
+    
+    /**
+     * @param fname
+     * @param page
+     * @param errMsgBuf
+     * @param lineNum
+     * @param ctxt
+     * @return JavacErrorDetail The error details
+     * @throws JasperException
+     */
+    public static JavacErrorDetail createJavacError(String fname,
+            Node.Nodes page, StringBuilder errMsgBuf, int lineNum,
+            JspCompilationContext ctxt) throws JasperException {
+        JavacErrorDetail javacError;
+        // Attempt to map javac error line number to line in JSP page
+        ErrorVisitor errVisitor = new ErrorVisitor(lineNum);
+        page.visit(errVisitor);
+        Node errNode = errVisitor.getJspSourceNode();
+        if ((errNode != null) && (errNode.getStart() != null)) {
+            // If this is a scriplet node then there is a one to one mapping
+            // between JSP lines and Java lines
+            if (errVisitor.getJspSourceNode() instanceof Node.Scriptlet) {
+                javacError = new JavacErrorDetail(
+                        fname,
+                        lineNum,
+                        errNode.getStart().getFile(),
+                        errNode.getStart().getLineNumber() + lineNum -
+                            errVisitor.getJspSourceNode().getBeginJavaLine(),
+                        errMsgBuf,
+                        ctxt);
+            } else {
+                javacError = new JavacErrorDetail(
+                        fname,
+                        lineNum,
+                        errNode.getStart().getFile(),
+                        errNode.getStart().getLineNumber(),
+                        errMsgBuf,
+                        ctxt);
+            }
+        } else {
+            /*
+             * javac error line number cannot be mapped to JSP page
+             * line number. For example, this is the case if a 
+             * scriptlet is missing a closing brace, which causes
+             * havoc with the try-catch-finally block that the code
+             * generator places around all generated code: As a result
+             * of this, the javac error line numbers will be outside
+             * the range of begin and end java line numbers that were
+             * generated for the scriptlet, and therefore cannot be
+             * mapped to the start line number of the scriptlet in the
+             * JSP page.
+             * Include just the javac error info in the error detail.
+             */
+            javacError = new JavacErrorDetail(
+                    fname,
+                    lineNum,
+                    errMsgBuf);
+        }
+        return javacError;
+    }
+
+
+    /*
+     * Visitor responsible for mapping a line number in the generated servlet
+     * source code to the corresponding JSP node.
+     */
+    static class ErrorVisitor extends Node.Visitor {
+
+        // Java source line number to be mapped
+        private int lineNum;
+
+        /*
+         * JSP node whose Java source code range in the generated servlet
+         * contains the Java source line number to be mapped
+         */
+        Node found;
+
+        /*
+         * Constructor.
+         *
+         * @param lineNum Source line number in the generated servlet code
+         */
+        public ErrorVisitor(int lineNum) {
+            this.lineNum = lineNum;
+        }
+
+        @Override
+        public void doVisit(Node n) throws JasperException {
+            if ((lineNum >= n.getBeginJavaLine())
+                    && (lineNum < n.getEndJavaLine())) {
+                found = n;
+            }
+        }
+
+        /*
+         * Gets the JSP node to which the source line number in the generated
+         * servlet code was mapped.
+         *
+         * @return JSP node to which the source line number in the generated
+         * servlet code was mapped
+         */
+        public Node getJspSourceNode() {
+            return found;
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ErrorHandler.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ErrorHandler.java
new file mode 100644
index 0000000..36ad3aa
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ErrorHandler.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.compiler;
+
+import org.apache.jasper.JasperException;
+
+/**
+ * Interface for handling JSP parse and javac compilation errors.
+ * 
+ * An implementation of this interface may be registered with the
+ * ErrorDispatcher by setting the XXX initialization parameter in the JSP
+ * page compiler and execution servlet in Catalina's web.xml file to the
+ * implementation's fully qualified class name.
+ *
+ * @author Jan Luehe
+ * @author Kin-man Chung
+ */
+public interface ErrorHandler {
+
+    /**
+     * Processes the given JSP parse error.
+     *
+     * @param fname Name of the JSP file in which the parse error occurred
+     * @param line Parse error line number
+     * @param column Parse error column number
+     * @param msg Parse error message
+     * @param exception Parse exception
+     */
+    public void jspError(String fname, int line, int column, String msg,
+            Exception exception) throws JasperException;
+
+    /**
+     * Processes the given JSP parse error.
+     *
+     * @param msg Parse error message
+     * @param exception Parse exception
+     */
+    public void jspError(String msg, Exception exception)
+            throws JasperException;
+
+    /**
+     * Processes the given javac compilation errors.
+     *
+     * @param details Array of JavacErrorDetail instances corresponding to the
+     * compilation errors
+     */
+    public void javacError(JavacErrorDetail[] details)
+            throws JasperException;
+
+    /**
+     * Processes the given javac error report and exception.
+     *
+     * @param errorReport Compilation error report
+     * @param exception Compilation exception
+     */
+    public void javacError(String errorReport, Exception exception)
+            throws JasperException;
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Generator.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Generator.java
new file mode 100644
index 0000000..4cec63c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Generator.java
@@ -0,0 +1,4241 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.el.MethodExpression;
+import javax.el.ValueExpression;
+import javax.servlet.jsp.tagext.TagAttributeInfo;
+import javax.servlet.jsp.tagext.TagInfo;
+import javax.servlet.jsp.tagext.TagVariableInfo;
+import javax.servlet.jsp.tagext.VariableInfo;
+
+import org.apache.jasper.Constants;
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.apache.jasper.compiler.Node.NamedAttribute;
+import org.apache.jasper.runtime.JspRuntimeLibrary;
+import org.xml.sax.Attributes;
+
+/**
+ * Generate Java source from Nodes
+ *
+ * @author Anil K. Vijendran
+ * @author Danno Ferrin
+ * @author Mandar Raje
+ * @author Rajiv Mordani
+ * @author Pierre Delisle
+ *
+ * Tomcat 4.1.x and Tomcat 5:
+ * @author Kin-man Chung
+ * @author Jan Luehe
+ * @author Shawn Bayern
+ * @author Mark Roth
+ * @author Denis Benoit
+ *
+ * Tomcat 6.x
+ * @author Jacob Hookom
+ * @author Remy Maucherat
+ */
+
+class Generator {
+
+    private static final Class<?>[] OBJECT_CLASS = { Object.class };
+
+    private static final String VAR_EXPRESSIONFACTORY =
+        System.getProperty("org.apache.jasper.compiler.Generator.VAR_EXPRESSIONFACTORY", "_el_expressionfactory");
+    private static final String VAR_INSTANCEMANAGER =
+        System.getProperty("org.apache.jasper.compiler.Generator.VAR_INSTANCEMANAGER", "_jsp_instancemanager");
+
+    /* System property that controls if the requirement to have the object
+     * used in jsp:getProperty action to be previously "introduced"
+     * to the JSP processor (see JSP.5.3) is enforced.
+     */ 
+    private static final boolean STRICT_GET_PROPERTY = Boolean.valueOf(
+            System.getProperty(
+                    "org.apache.jasper.compiler.Generator.STRICT_GET_PROPERTY",
+                    "true")).booleanValue();
+
+    private ServletWriter out;
+
+    private ArrayList<GenBuffer> methodsBuffered;
+
+    private FragmentHelperClass fragmentHelperClass;
+
+    private ErrorDispatcher err;
+
+    private BeanRepository beanInfo;
+    
+    private Set<String> varInfoNames;
+
+    private JspCompilationContext ctxt;
+
+    private boolean isPoolingEnabled;
+
+    private boolean breakAtLF;
+
+    private String jspIdPrefix;
+
+    private int jspId;
+
+    private PageInfo pageInfo;
+
+    private Vector<String> tagHandlerPoolNames;
+
+    private GenBuffer charArrayBuffer;
+
+    /**
+     * @param s
+     *            the input string
+     * @return quoted and escaped string, per Java rule
+     */
+    static String quote(String s) {
+
+        if (s == null)
+            return "null";
+
+        return '"' + escape(s) + '"';
+    }
+
+    /**
+     * @param s
+     *            the input string
+     * @return escaped string, per Java rule
+     */
+    static String escape(String s) {
+
+        if (s == null)
+            return "";
+
+        StringBuilder b = new StringBuilder();
+        for (int i = 0; i < s.length(); i++) {
+            char c = s.charAt(i);
+            if (c == '"')
+                b.append('\\').append('"');
+            else if (c == '\\')
+                b.append('\\').append('\\');
+            else if (c == '\n')
+                b.append('\\').append('n');
+            else if (c == '\r')
+                b.append('\\').append('r');
+            else
+                b.append(c);
+        }
+        return b.toString();
+    }
+
+    /**
+     * Single quote and escape a character
+     */
+    static String quote(char c) {
+
+        StringBuilder b = new StringBuilder();
+        b.append('\'');
+        if (c == '\'')
+            b.append('\\').append('\'');
+        else if (c == '\\')
+            b.append('\\').append('\\');
+        else if (c == '\n')
+            b.append('\\').append('n');
+        else if (c == '\r')
+            b.append('\\').append('r');
+        else
+            b.append(c);
+        b.append('\'');
+        return b.toString();
+    }
+
+    private String createJspId() {
+        if (this.jspIdPrefix == null) {
+            StringBuilder sb = new StringBuilder(32);
+            String name = ctxt.getServletJavaFileName();
+            sb.append("jsp_");
+            // Cast to long to avoid issue with Integer.MIN_VALUE
+            sb.append(Math.abs((long) name.hashCode()));
+            sb.append('_');
+            this.jspIdPrefix = sb.toString();
+        }
+        return this.jspIdPrefix + (this.jspId++);
+    }
+
+    /**
+     * Generates declarations. This includes "info" of the page directive, and
+     * scriptlet declarations.
+     */
+    private void generateDeclarations(Node.Nodes page) throws JasperException {
+
+        class DeclarationVisitor extends Node.Visitor {
+
+            private boolean getServletInfoGenerated = false;
+
+            /*
+             * Generates getServletInfo() method that returns the value of the
+             * page directive's 'info' attribute, if present.
+             *
+             * The Validator has already ensured that if the translation unit
+             * contains more than one page directive with an 'info' attribute,
+             * their values match.
+             */
+            @Override
+            public void visit(Node.PageDirective n) throws JasperException {
+
+                if (getServletInfoGenerated) {
+                    return;
+                }
+
+                String info = n.getAttributeValue("info");
+                if (info == null)
+                    return;
+
+                getServletInfoGenerated = true;
+                out.printil("public java.lang.String getServletInfo() {");
+                out.pushIndent();
+                out.printin("return ");
+                out.print(quote(info));
+                out.println(";");
+                out.popIndent();
+                out.printil("}");
+                out.println();
+            }
+
+            @Override
+            public void visit(Node.Declaration n) throws JasperException {
+                n.setBeginJavaLine(out.getJavaLine());
+                out.printMultiLn(n.getText());
+                out.println();
+                n.setEndJavaLine(out.getJavaLine());
+            }
+
+            // Custom Tags may contain declarations from tag plugins.
+            @Override
+            public void visit(Node.CustomTag n) throws JasperException {
+                if (n.useTagPlugin()) {
+                    if (n.getAtSTag() != null) {
+                        n.getAtSTag().visit(this);
+                    }
+                    visitBody(n);
+                    if (n.getAtETag() != null) {
+                        n.getAtETag().visit(this);
+                    }
+                } else {
+                    visitBody(n);
+                }
+            }
+        }
+
+        out.println();
+        page.visit(new DeclarationVisitor());
+    }
+
+    /**
+     * Compiles list of tag handler pool names.
+     */
+    private void compileTagHandlerPoolList(Node.Nodes page)
+            throws JasperException {
+
+        class TagHandlerPoolVisitor extends Node.Visitor {
+
+            private Vector<String> names;
+
+            /*
+             * Constructor
+             *
+             * @param v Vector of tag handler pool names to populate
+             */
+            TagHandlerPoolVisitor(Vector<String> v) {
+                names = v;
+            }
+
+            /*
+             * Gets the name of the tag handler pool for the given custom tag
+             * and adds it to the list of tag handler pool names unless it is
+             * already contained in it.
+             */
+            @Override
+            public void visit(Node.CustomTag n) throws JasperException {
+
+                if (!n.implementsSimpleTag()) {
+                    String name = createTagHandlerPoolName(n.getPrefix(), n
+                            .getLocalName(), n.getAttributes(), 
+                            n.getNamedAttributeNodes(), n.hasEmptyBody());
+                    n.setTagHandlerPoolName(name);
+                    if (!names.contains(name)) {
+                        names.add(name);
+                    }
+                }
+                visitBody(n);
+            }
+
+            /*
+             * Creates the name of the tag handler pool whose tag handlers may
+             * be (re)used to service this action.
+             *
+             * @return The name of the tag handler pool
+             */
+            private String createTagHandlerPoolName(String prefix,
+                    String shortName, Attributes attrs, Node.Nodes namedAttrs,
+                    boolean hasEmptyBody) {
+                StringBuilder poolName = new StringBuilder(64);
+                poolName.append("_jspx_tagPool_").append(prefix).append('_')
+                        .append(shortName);
+
+                if (attrs != null) {
+                    String[] attrNames =
+                        new String[attrs.getLength() + namedAttrs.size()];
+                    for (int i = 0; i < attrNames.length; i++) {
+                        attrNames[i] = attrs.getQName(i);
+                    }
+                    for (int i = 0; i < namedAttrs.size(); i++) {
+                        attrNames[attrs.getLength() + i] =
+                            ((NamedAttribute) namedAttrs.getNode(i)).getQName();
+                    }
+                    Arrays.sort(attrNames, Collections.reverseOrder());
+                    if (attrNames.length > 0) {
+                        poolName.append('&');
+                    }
+                    for (int i = 0; i < attrNames.length; i++) {
+                        poolName.append('_');
+                        poolName.append(attrNames[i]);
+                    }
+                }
+                if (hasEmptyBody) {
+                    poolName.append("_nobody");
+                }
+                return JspUtil.makeJavaIdentifier(poolName.toString());
+            }
+        }
+
+        page.visit(new TagHandlerPoolVisitor(tagHandlerPoolNames));
+    }
+
+    private void declareTemporaryScriptingVars(Node.Nodes page)
+            throws JasperException {
+
+        class ScriptingVarVisitor extends Node.Visitor {
+
+            private Vector<String> vars;
+
+            ScriptingVarVisitor() {
+                vars = new Vector<String>();
+            }
+
+            @Override
+            public void visit(Node.CustomTag n) throws JasperException {
+                // XXX - Actually there is no need to declare those
+                // "_jspx_" + varName + "_" + nestingLevel variables when we are
+                // inside a JspFragment.
+
+                if (n.getCustomNestingLevel() > 0) {
+                    TagVariableInfo[] tagVarInfos = n.getTagVariableInfos();
+                    VariableInfo[] varInfos = n.getVariableInfos();
+
+                    if (varInfos.length > 0) {
+                        for (int i = 0; i < varInfos.length; i++) {
+                            String varName = varInfos[i].getVarName();
+                            String tmpVarName = "_jspx_" + varName + "_"
+                                    + n.getCustomNestingLevel();
+                            if (!vars.contains(tmpVarName)) {
+                                vars.add(tmpVarName);
+                                out.printin(varInfos[i].getClassName());
+                                out.print(" ");
+                                out.print(tmpVarName);
+                                out.print(" = ");
+                                out.print(null);
+                                out.println(";");
+                            }
+                        }
+                    } else {
+                        for (int i = 0; i < tagVarInfos.length; i++) {
+                            String varName = tagVarInfos[i].getNameGiven();
+                            if (varName == null) {
+                                varName = n.getTagData().getAttributeString(
+                                        tagVarInfos[i].getNameFromAttribute());
+                            } else if (tagVarInfos[i].getNameFromAttribute() != null) {
+                                // alias
+                                continue;
+                            }
+                            String tmpVarName = "_jspx_" + varName + "_"
+                                    + n.getCustomNestingLevel();
+                            if (!vars.contains(tmpVarName)) {
+                                vars.add(tmpVarName);
+                                out.printin(tagVarInfos[i].getClassName());
+                                out.print(" ");
+                                out.print(tmpVarName);
+                                out.print(" = ");
+                                out.print(null);
+                                out.println(";");
+                            }
+                        }
+                    }
+                }
+
+                visitBody(n);
+            }
+        }
+
+        page.visit(new ScriptingVarVisitor());
+    }
+
+    /**
+     * Generates the _jspInit() method for instantiating the tag handler pools.
+     * For tag file, _jspInit has to be invoked manually, and the ServletConfig
+     * object explicitly passed.
+     *
+     * In JSP 2.1, we also instantiate an ExpressionFactory
+     */
+    private void generateInit() {
+
+        if (ctxt.isTagFile()) {
+            out.printil("private void _jspInit(javax.servlet.ServletConfig config) {");
+        } else {
+            out.printil("public void _jspInit() {");
+        }
+
+        out.pushIndent();
+        if (isPoolingEnabled) {
+            for (int i = 0; i < tagHandlerPoolNames.size(); i++) {
+                out.printin(tagHandlerPoolNames.elementAt(i));
+                out.print(" = org.apache.jasper.runtime.TagHandlerPool.getTagHandlerPool(");
+                if (ctxt.isTagFile()) {
+                    out.print("config");
+                } else {
+                    out.print("getServletConfig()");
+                }
+                out.println(");");
+            }
+        }
+
+        out.printin(VAR_EXPRESSIONFACTORY);
+        out.print(" = _jspxFactory.getJspApplicationContext(");
+        if (ctxt.isTagFile()) {
+            out.print("config");
+        } else {
+            out.print("getServletConfig()");
+        }
+        out.println(".getServletContext()).getExpressionFactory();");
+
+        out.printin(VAR_INSTANCEMANAGER);
+        out.print(" = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(");
+        if (ctxt.isTagFile()) {
+            out.print("config");
+        } else {
+            out.print("getServletConfig()");
+        }
+        out.println(");");
+
+        out.popIndent();
+        out.printil("}");
+        out.println();
+    }
+
+    /**
+     * Generates the _jspDestroy() method which is responsible for calling the
+     * release() method on every tag handler in any of the tag handler pools.
+     */
+    private void generateDestroy() {
+
+        out.printil("public void _jspDestroy() {");
+        out.pushIndent();
+
+        if (isPoolingEnabled) {
+            for (int i = 0; i < tagHandlerPoolNames.size(); i++) {
+                                out.printin(tagHandlerPoolNames.elementAt(i));
+                                out.println(".release();");
+            }
+        }
+
+        out.popIndent();
+        out.printil("}");
+        out.println();
+    }
+
+    /**
+     * Generate preamble package name (shared by servlet and tag handler
+     * preamble generation)
+     */
+    private void genPreamblePackage(String packageName) {
+        if (!"".equals(packageName) && packageName != null) {
+            out.printil("package " + packageName + ";");
+            out.println();
+        }
+    }
+
+    /**
+     * Generate preamble imports (shared by servlet and tag handler preamble
+     * generation)
+     */
+    private void genPreambleImports() {
+        Iterator<String> iter = pageInfo.getImports().iterator();
+        while (iter.hasNext()) {
+            out.printin("import ");
+            out.print(iter.next());
+            out.println(";");
+        }
+
+        out.println();
+    }
+
+    /**
+     * Generation of static initializers in preamble. For example, dependent
+     * list, el function map, prefix map. (shared by servlet and tag handler
+     * preamble generation)
+     */
+    private void genPreambleStaticInitializers() {
+        out.printil("private static final javax.servlet.jsp.JspFactory _jspxFactory =");
+        out.printil("        javax.servlet.jsp.JspFactory.getDefaultFactory();");
+        out.println();
+
+        // Static data for getDependants()
+        out.printil("private static java.util.List<java.lang.String> _jspx_dependants;");
+        out.println();
+        List<String> dependants = pageInfo.getDependants();
+        Iterator<String> iter = dependants.iterator();
+        if (!dependants.isEmpty()) {
+            out.printil("static {");
+            out.pushIndent();
+            out.printin("_jspx_dependants = new java.util.ArrayList<java.lang.String>(");
+            out.print("" + dependants.size());
+            out.println(");");
+            while (iter.hasNext()) {
+                out.printin("_jspx_dependants.add(\"");
+                out.print(iter.next());
+                out.println("\");");
+            }
+            out.popIndent();
+            out.printil("}");
+            out.println();
+        }
+    }
+
+    /**
+     * Declare tag handler pools (tags of the same type and with the same
+     * attribute set share the same tag handler pool) (shared by servlet and tag
+     * handler preamble generation)
+     *
+     * In JSP 2.1, we also scope an instance of ExpressionFactory
+     */
+    private void genPreambleClassVariableDeclarations() {
+        if (isPoolingEnabled && !tagHandlerPoolNames.isEmpty()) {
+            for (int i = 0; i < tagHandlerPoolNames.size(); i++) {
+                out.printil("private org.apache.jasper.runtime.TagHandlerPool "
+                        + tagHandlerPoolNames.elementAt(i) + ";");
+            }
+            out.println();
+        }
+        out.printin("private javax.el.ExpressionFactory ");
+        out.print(VAR_EXPRESSIONFACTORY);
+        out.println(";");
+        out.printin("private org.apache.tomcat.InstanceManager ");
+        out.print(VAR_INSTANCEMANAGER);
+        out.println(";");
+        out.println();
+    }
+
+    /**
+     * Declare general-purpose methods (shared by servlet and tag handler
+     * preamble generation)
+     */
+    private void genPreambleMethods() {
+        // Method used to get compile time file dependencies
+        out.printil("public java.util.List<java.lang.String> getDependants() {");
+        out.pushIndent();
+        out.printil("return _jspx_dependants;");
+        out.popIndent();
+        out.printil("}");
+        out.println();
+
+        generateInit();
+        generateDestroy();
+    }
+
+    /**
+     * Generates the beginning of the static portion of the servlet.
+     */
+    private void generatePreamble(Node.Nodes page) throws JasperException {
+
+        String servletPackageName = ctxt.getServletPackageName();
+        String servletClassName = ctxt.getServletClassName();
+        String serviceMethodName = Constants.SERVICE_METHOD_NAME;
+
+        // First the package name:
+        genPreamblePackage(servletPackageName);
+
+        // Generate imports
+        genPreambleImports();
+
+        // Generate class declaration
+        out.printin("public final class ");
+        out.print(servletClassName);
+        out.print(" extends ");
+        out.println(pageInfo.getExtends());
+        out.printin("    implements org.apache.jasper.runtime.JspSourceDependent");
+        if (!pageInfo.isThreadSafe()) {
+            out.println(",");
+            out.printin("                 javax.servlet.SingleThreadModel");
+        }
+        out.println(" {");
+        out.pushIndent();
+
+        // Class body begins here
+        generateDeclarations(page);
+
+        // Static initializations here
+        genPreambleStaticInitializers();
+
+        // Class variable declarations
+        genPreambleClassVariableDeclarations();
+
+        // Methods here
+        genPreambleMethods();
+
+        // Now the service method
+        out.printin("public void ");
+        out.print(serviceMethodName);
+        out.println("(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)");
+        out.println("        throws java.io.IOException, javax.servlet.ServletException {");
+
+        out.pushIndent();
+        out.println();
+
+        // Local variable declarations
+        out.printil("final javax.servlet.jsp.PageContext pageContext;");
+
+        if (pageInfo.isSession())
+            out.printil("javax.servlet.http.HttpSession session = null;");
+
+        if (pageInfo.isErrorPage()) {
+            out.printil("java.lang.Throwable exception = org.apache.jasper.runtime.JspRuntimeLibrary.getThrowable(request);");
+            out.printil("if (exception != null) {");
+            out.pushIndent();
+            out.printil("response.setStatus(javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR);");
+            out.popIndent();
+            out.printil("}");
+        }
+
+        out.printil("final javax.servlet.ServletContext application;");
+        out.printil("final javax.servlet.ServletConfig config;");
+        out.printil("javax.servlet.jsp.JspWriter out = null;");
+        out.printil("final java.lang.Object page = this;");
+
+        out.printil("javax.servlet.jsp.JspWriter _jspx_out = null;");
+        out.printil("javax.servlet.jsp.PageContext _jspx_page_context = null;");
+        out.println();
+
+        declareTemporaryScriptingVars(page);
+        out.println();
+
+        out.printil("try {");
+        out.pushIndent();
+
+        out.printin("response.setContentType(");
+        out.print(quote(pageInfo.getContentType()));
+        out.println(");");
+
+        if (ctxt.getOptions().isXpoweredBy()) {
+            out.printil("response.addHeader(\"X-Powered-By\", \"JSP/2.1\");");
+        }
+
+        out.printil("pageContext = _jspxFactory.getPageContext(this, request, response,");
+        out.printin("\t\t\t");
+        out.print(quote(pageInfo.getErrorPage()));
+        out.print(", " + pageInfo.isSession());
+        out.print(", " + pageInfo.getBuffer());
+        out.print(", " + pageInfo.isAutoFlush());
+        out.println(");");
+        out.printil("_jspx_page_context = pageContext;");
+
+        out.printil("application = pageContext.getServletContext();");
+        out.printil("config = pageContext.getServletConfig();");
+
+        if (pageInfo.isSession())
+            out.printil("session = pageContext.getSession();");
+        out.printil("out = pageContext.getOut();");
+        out.printil("_jspx_out = out;");
+        out.println();
+    }
+
+    /**
+     * Generates an XML Prolog, which includes an XML declaration and an XML
+     * doctype declaration.
+     */
+    private void generateXmlProlog(Node.Nodes page) {
+
+        /*
+         * An XML declaration is generated under the following conditions: -
+         * 'omit-xml-declaration' attribute of <jsp:output> action is set to
+         * "no" or "false" - JSP document without a <jsp:root>
+         */
+        String omitXmlDecl = pageInfo.getOmitXmlDecl();
+        if ((omitXmlDecl != null && !JspUtil.booleanValue(omitXmlDecl))
+                || (omitXmlDecl == null && page.getRoot().isXmlSyntax()
+                        && !pageInfo.hasJspRoot() && !ctxt.isTagFile())) {
+            String cType = pageInfo.getContentType();
+            String charSet = cType.substring(cType.indexOf("charset=") + 8);
+            out.printil("out.write(\"<?xml version=\\\"1.0\\\" encoding=\\\""
+                    + charSet + "\\\"?>\\n\");");
+        }
+
+        /*
+         * Output a DOCTYPE declaration if the doctype-root-element appears. If
+         * doctype-public appears: <!DOCTYPE name PUBLIC "doctypePublic"
+         * "doctypeSystem"> else <!DOCTYPE name SYSTEM "doctypeSystem" >
+         */
+
+        String doctypeName = pageInfo.getDoctypeName();
+        if (doctypeName != null) {
+            String doctypePublic = pageInfo.getDoctypePublic();
+            String doctypeSystem = pageInfo.getDoctypeSystem();
+            out.printin("out.write(\"<!DOCTYPE ");
+            out.print(doctypeName);
+            if (doctypePublic == null) {
+                out.print(" SYSTEM \\\"");
+            } else {
+                out.print(" PUBLIC \\\"");
+                out.print(doctypePublic);
+                out.print("\\\" \\\"");
+            }
+            out.print(doctypeSystem);
+            out.println("\\\">\\n\");");
+        }
+    }
+
+    /**
+     * A visitor that generates codes for the elements in the page.
+     */
+    class GenerateVisitor extends Node.Visitor {
+
+        /*
+         * Hashtable containing introspection information on tag handlers:
+         * <key>: tag prefix <value>: hashtable containing introspection on tag
+         * handlers: <key>: tag short name <value>: introspection info of tag
+         * handler for <prefix:shortName> tag
+         */
+        private Hashtable<String,Hashtable<String,TagHandlerInfo>> handlerInfos;
+
+        private Hashtable<String,Integer> tagVarNumbers;
+
+        private String parent;
+
+        private boolean isSimpleTagParent; // Is parent a SimpleTag?
+
+        private String pushBodyCountVar;
+
+        private String simpleTagHandlerVar;
+
+        private boolean isSimpleTagHandler;
+
+        private boolean isFragment;
+
+        private boolean isTagFile;
+
+        private ServletWriter out;
+
+        private ArrayList<GenBuffer> methodsBuffered;
+
+        private FragmentHelperClass fragmentHelperClass;
+
+        private int methodNesting;
+
+        private int charArrayCount;
+
+        private HashMap<String,String> textMap;
+
+        /**
+         * Constructor.
+         */
+        public GenerateVisitor(boolean isTagFile, ServletWriter out,
+                ArrayList<GenBuffer> methodsBuffered,
+                FragmentHelperClass fragmentHelperClass) {
+
+            this.isTagFile = isTagFile;
+            this.out = out;
+            this.methodsBuffered = methodsBuffered;
+            this.fragmentHelperClass = fragmentHelperClass;
+            methodNesting = 0;
+            handlerInfos =
+                new Hashtable<String,Hashtable<String,TagHandlerInfo>>();
+            tagVarNumbers = new Hashtable<String,Integer>();
+            textMap = new HashMap<String,String>();
+        }
+
+        /**
+         * Returns an attribute value, optionally URL encoded. If the value is a
+         * runtime expression, the result is the expression itself, as a string.
+         * If the result is an EL expression, we insert a call to the
+         * interpreter. If the result is a Named Attribute we insert the
+         * generated variable name. Otherwise the result is a string literal,
+         * quoted and escaped.
+         *
+         * @param attr
+         *            An JspAttribute object
+         * @param encode
+         *            true if to be URL encoded
+         * @param expectedType
+         *            the expected type for an EL evaluation (ignored for
+         *            attributes that aren't EL expressions)
+         */
+        private String attributeValue(Node.JspAttribute attr, boolean encode,
+                Class<?> expectedType) {
+            String v = attr.getValue();
+            if (!attr.isNamedAttribute() && (v == null))
+                return "";
+
+            if (attr.isExpression()) {
+                if (encode) {
+                    return "org.apache.jasper.runtime.JspRuntimeLibrary.URLEncode(String.valueOf("
+                            + v + "), request.getCharacterEncoding())";
+                }
+                return v;
+            } else if (attr.isELInterpreterInput()) {
+                v = JspUtil.interpreterCall(this.isTagFile, v, expectedType,
+                        attr.getEL().getMapName(), false);
+                if (encode) {
+                    return "org.apache.jasper.runtime.JspRuntimeLibrary.URLEncode("
+                            + v + ", request.getCharacterEncoding())";
+                }
+                return v;
+            } else if (attr.isNamedAttribute()) {
+                return attr.getNamedAttributeNode().getTemporaryVariableName();
+            } else {
+                if (encode) {
+                    return "org.apache.jasper.runtime.JspRuntimeLibrary.URLEncode("
+                            + quote(v) + ", request.getCharacterEncoding())";
+                }
+                return quote(v);
+            }
+        }
+
+
+        /**
+         * Prints the attribute value specified in the param action, in the form
+         * of name=value string.
+         *
+         * @param n
+         *            the parent node for the param action nodes.
+         */
+        private void printParams(Node n, String pageParam, boolean literal)
+                throws JasperException {
+
+            class ParamVisitor extends Node.Visitor {
+                String separator;
+
+                ParamVisitor(String separator) {
+                    this.separator = separator;
+                }
+
+                @Override
+                public void visit(Node.ParamAction n) throws JasperException {
+
+                    out.print(" + ");
+                    out.print(separator);
+                    out.print(" + ");
+                    out.print("org.apache.jasper.runtime.JspRuntimeLibrary."
+                            + "URLEncode(" + quote(n.getTextAttribute("name"))
+                            + ", request.getCharacterEncoding())");
+                    out.print("+ \"=\" + ");
+                    out.print(attributeValue(n.getValue(), true, String.class));
+
+                    // The separator is '&' after the second use
+                    separator = "\"&\"";
+                }
+            }
+
+            String sep;
+            if (literal) {
+                sep = pageParam.indexOf('?') > 0 ? "\"&\"" : "\"?\"";
+            } else {
+                sep = "((" + pageParam + ").indexOf('?')>0? '&': '?')";
+            }
+            if (n.getBody() != null) {
+                n.getBody().visit(new ParamVisitor(sep));
+            }
+        }
+
+        @Override
+        public void visit(Node.Expression n) throws JasperException {
+            n.setBeginJavaLine(out.getJavaLine());
+            out.printin("out.print(");
+            out.printMultiLn(n.getText());
+            out.println(");");
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        @Override
+        public void visit(Node.Scriptlet n) throws JasperException {
+            n.setBeginJavaLine(out.getJavaLine());
+            out.printMultiLn(n.getText());
+            out.println();
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        @Override
+        public void visit(Node.ELExpression n) throws JasperException {
+            n.setBeginJavaLine(out.getJavaLine());
+            if (!pageInfo.isELIgnored() && (n.getEL() != null)) {
+                out.printil("out.write("
+                        + JspUtil.interpreterCall(this.isTagFile, n.getType() +
+                                "{" + n.getText() + "}", String.class,
+                                n.getEL().getMapName(), false) + ");");
+            } else {
+                out.printil("out.write("
+                        + quote(n.getType() + "{" + n.getText() + "}") + ");");
+            }
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        @Override
+        public void visit(Node.IncludeAction n) throws JasperException {
+
+            String flush = n.getTextAttribute("flush");
+            Node.JspAttribute page = n.getPage();
+
+            boolean isFlush = false; // default to false;
+            if ("true".equals(flush))
+                isFlush = true;
+
+            n.setBeginJavaLine(out.getJavaLine());
+
+            String pageParam;
+            if (page.isNamedAttribute()) {
+                // If the page for jsp:include was specified via
+                // jsp:attribute, first generate code to evaluate
+                // that body.
+                pageParam = generateNamedAttributeValue(page
+                        .getNamedAttributeNode());
+            } else {
+                pageParam = attributeValue(page, false, String.class);
+            }
+
+            // If any of the params have their values specified by
+            // jsp:attribute, prepare those values first.
+            Node jspBody = findJspBody(n);
+            if (jspBody != null) {
+                prepareParams(jspBody);
+            } else {
+                prepareParams(n);
+            }
+
+            out.printin("org.apache.jasper.runtime.JspRuntimeLibrary.include(request, response, "
+                    + pageParam);
+            printParams(n, pageParam, page.isLiteral());
+            out.println(", out, " + isFlush + ");");
+
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        /**
+         * Scans through all child nodes of the given parent for &lt;param&gt;
+         * subelements. For each &lt;param&gt; element, if its value is specified via
+         * a Named Attribute (&lt;jsp:attribute&gt;), generate the code to evaluate
+         * those bodies first.
+         * <p>
+         * If parent is null, simply returns.
+         */
+        private void prepareParams(Node parent) throws JasperException {
+            if (parent == null)
+                return;
+
+            Node.Nodes subelements = parent.getBody();
+            if (subelements != null) {
+                for (int i = 0; i < subelements.size(); i++) {
+                    Node n = subelements.getNode(i);
+                    if (n instanceof Node.ParamAction) {
+                        Node.Nodes paramSubElements = n.getBody();
+                        for (int j = 0; (paramSubElements != null)
+                                && (j < paramSubElements.size()); j++) {
+                            Node m = paramSubElements.getNode(j);
+                            if (m instanceof Node.NamedAttribute) {
+                                generateNamedAttributeValue((Node.NamedAttribute) m);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        /**
+         * Finds the &lt;jsp:body&gt; subelement of the given parent node. If not
+         * found, null is returned.
+         */
+        private Node.JspBody findJspBody(Node parent) {
+            Node.JspBody result = null;
+
+            Node.Nodes subelements = parent.getBody();
+            for (int i = 0; (subelements != null) && (i < subelements.size()); i++) {
+                Node n = subelements.getNode(i);
+                if (n instanceof Node.JspBody) {
+                    result = (Node.JspBody) n;
+                    break;
+                }
+            }
+
+            return result;
+        }
+
+        @Override
+        public void visit(Node.ForwardAction n) throws JasperException {
+            Node.JspAttribute page = n.getPage();
+
+            n.setBeginJavaLine(out.getJavaLine());
+
+            out.printil("if (true) {"); // So that javac won't complain about
+            out.pushIndent(); // codes after "return"
+
+            String pageParam;
+            if (page.isNamedAttribute()) {
+                // If the page for jsp:forward was specified via
+                // jsp:attribute, first generate code to evaluate
+                // that body.
+                pageParam = generateNamedAttributeValue(page
+                        .getNamedAttributeNode());
+            } else {
+                pageParam = attributeValue(page, false, String.class);
+            }
+
+            // If any of the params have their values specified by
+            // jsp:attribute, prepare those values first.
+            Node jspBody = findJspBody(n);
+            if (jspBody != null) {
+                prepareParams(jspBody);
+            } else {
+                prepareParams(n);
+            }
+
+            out.printin("_jspx_page_context.forward(");
+            out.print(pageParam);
+            printParams(n, pageParam, page.isLiteral());
+            out.println(");");
+            if (isTagFile || isFragment) {
+                out.printil("throw new javax.servlet.jsp.SkipPageException();");
+            } else {
+                out.printil((methodNesting > 0) ? "return true;" : "return;");
+            }
+            out.popIndent();
+            out.printil("}");
+
+            n.setEndJavaLine(out.getJavaLine());
+            // XXX Not sure if we can eliminate dead codes after this.
+        }
+
+        @Override
+        public void visit(Node.GetProperty n) throws JasperException {
+            String name = n.getTextAttribute("name");
+            String property = n.getTextAttribute("property");
+
+            n.setBeginJavaLine(out.getJavaLine());
+
+            if (beanInfo.checkVariable(name)) {
+                // Bean is defined using useBean, introspect at compile time
+                Class<?> bean = beanInfo.getBeanType(name);
+                String beanName = bean.getCanonicalName();
+                java.lang.reflect.Method meth = JspRuntimeLibrary
+                        .getReadMethod(bean, property);
+                String methodName = meth.getName();
+                out.printil("out.write(org.apache.jasper.runtime.JspRuntimeLibrary.toString("
+                        + "((("
+                        + beanName
+                        + ")_jspx_page_context.findAttribute("
+                        + "\""
+                        + name + "\"))." + methodName + "())));");
+            } else if (!STRICT_GET_PROPERTY || varInfoNames.contains(name)) {
+                // The object is a custom action with an associated
+                // VariableInfo entry for this name.
+                // Get the class name and then introspect at runtime.
+                out.printil("out.write(org.apache.jasper.runtime.JspRuntimeLibrary.toString"
+                        + "(org.apache.jasper.runtime.JspRuntimeLibrary.handleGetProperty"
+                        + "(_jspx_page_context.findAttribute(\""
+                        + name
+                        + "\"), \""
+                        + property
+                        + "\")));");
+            } else {
+                StringBuilder msg = new StringBuilder();
+                msg.append("file:");
+                msg.append(n.getStart());
+                msg.append(" jsp:getProperty for bean with name '");
+                msg.append(name);
+                msg.append(
+                        "'. Name was not previously introduced as per JSP.5.3");
+                
+                throw new JasperException(msg.toString());
+            }
+
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        @Override
+        public void visit(Node.SetProperty n) throws JasperException {
+            String name = n.getTextAttribute("name");
+            String property = n.getTextAttribute("property");
+            String param = n.getTextAttribute("param");
+            Node.JspAttribute value = n.getValue();
+
+            n.setBeginJavaLine(out.getJavaLine());
+
+            if ("*".equals(property)) {
+                out.printil("org.apache.jasper.runtime.JspRuntimeLibrary.introspect("
+                        + "_jspx_page_context.findAttribute("
+                        + "\""
+                        + name + "\"), request);");
+            } else if (value == null) {
+                if (param == null)
+                    param = property; // default to same as property
+                out.printil("org.apache.jasper.runtime.JspRuntimeLibrary.introspecthelper("
+                        + "_jspx_page_context.findAttribute(\""
+                        + name
+                        + "\"), \""
+                        + property
+                        + "\", request.getParameter(\""
+                        + param
+                        + "\"), "
+                        + "request, \""
+                        + param
+                        + "\", false);");
+            } else if (value.isExpression()) {
+                out.printil("org.apache.jasper.runtime.JspRuntimeLibrary.handleSetProperty("
+                        + "_jspx_page_context.findAttribute(\""
+                        + name
+                        + "\"), \"" + property + "\",");
+                out.print(attributeValue(value, false, null));
+                out.println(");");
+            } else if (value.isELInterpreterInput()) {
+                // We've got to resolve the very call to the interpreter
+                // at runtime since we don't know what type to expect
+                // in the general case; we thus can't hard-wire the call
+                // into the generated code. (XXX We could, however,
+                // optimize the case where the bean is exposed with
+                // <jsp:useBean>, much as the code here does for
+                // getProperty.)
+
+                // The following holds true for the arguments passed to
+                // JspRuntimeLibrary.handleSetPropertyExpression():
+                // - 'pageContext' is a VariableResolver.
+                // - 'this' (either the generated Servlet or the generated tag
+                // handler for Tag files) is a FunctionMapper.
+                out.printil("org.apache.jasper.runtime.JspRuntimeLibrary.handleSetPropertyExpression("
+                        + "_jspx_page_context.findAttribute(\""
+                        + name
+                        + "\"), \""
+                        + property
+                        + "\", "
+                        + quote(value.getValue())
+                        + ", "
+                        + "_jspx_page_context, "
+                        + value.getEL().getMapName() + ");");
+            } else if (value.isNamedAttribute()) {
+                // If the value for setProperty was specified via
+                // jsp:attribute, first generate code to evaluate
+                // that body.
+                String valueVarName = generateNamedAttributeValue(value
+                        .getNamedAttributeNode());
+                out.printil("org.apache.jasper.runtime.JspRuntimeLibrary.introspecthelper("
+                        + "_jspx_page_context.findAttribute(\""
+                        + name
+                        + "\"), \""
+                        + property
+                        + "\", "
+                        + valueVarName
+                        + ", null, null, false);");
+            } else {
+                out.printin("org.apache.jasper.runtime.JspRuntimeLibrary.introspecthelper("
+                        + "_jspx_page_context.findAttribute(\""
+                        + name
+                        + "\"), \"" + property + "\", ");
+                out.print(attributeValue(value, false, null));
+                out.println(", null, null, false);");
+            }
+
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        @Override
+        public void visit(Node.UseBean n) throws JasperException {
+
+            String name = n.getTextAttribute("id");
+            String scope = n.getTextAttribute("scope");
+            String klass = n.getTextAttribute("class");
+            String type = n.getTextAttribute("type");
+            Node.JspAttribute beanName = n.getBeanName();
+
+            // If "class" is specified, try an instantiation at compile time
+            boolean generateNew = false;
+            String canonicalName = null; // Canonical name for klass
+            if (klass != null) {
+                try {
+                    Class<?> bean = ctxt.getClassLoader().loadClass(klass);
+                    if (klass.indexOf('$') >= 0) {
+                        // Obtain the canonical type name
+                        canonicalName = bean.getCanonicalName();
+                    } else {
+                        canonicalName = klass;
+                    }
+                    int modifiers = bean.getModifiers();
+                    if (!Modifier.isPublic(modifiers)
+                            || Modifier.isInterface(modifiers)
+                            || Modifier.isAbstract(modifiers)) {
+                        throw new Exception("Invalid bean class modifier");
+                    }
+                    // Check that there is a 0 arg constructor
+                    bean.getConstructor(new Class[] {});
+                    // At compile time, we have determined that the bean class
+                    // exists, with a public zero constructor, new() can be
+                    // used for bean instantiation.
+                    generateNew = true;
+                } catch (Exception e) {
+                    // Cannot instantiate the specified class, either a
+                    // compilation error or a runtime error will be raised,
+                    // depending on a compiler flag.
+                    if (ctxt.getOptions()
+                            .getErrorOnUseBeanInvalidClassAttribute()) {
+                        err.jspError(n, "jsp.error.invalid.bean", klass);
+                    }
+                    if (canonicalName == null) {
+                        // Doing our best here to get a canonical name
+                        // from the binary name, should work 99.99% of time.
+                        canonicalName = klass.replace('$', '.');
+                    }
+                }
+                if (type == null) {
+                    // if type is unspecified, use "class" as type of bean
+                    type = canonicalName;
+                }
+            }
+
+            // JSP.5.1, Sematics, para 1 - lock not required for request or
+            // page scope
+            String scopename = "javax.servlet.jsp.PageContext.PAGE_SCOPE"; // Default to page
+            String lock = null;
+
+            if ("request".equals(scope)) {
+                scopename = "javax.servlet.jsp.PageContext.REQUEST_SCOPE";
+            } else if ("session".equals(scope)) {
+                scopename = "javax.servlet.jsp.PageContext.SESSION_SCOPE";
+                lock = "session";
+            } else if ("application".equals(scope)) {
+                scopename = "javax.servlet.jsp.PageContext.APPLICATION_SCOPE";
+                lock = "application";
+            }
+
+            n.setBeginJavaLine(out.getJavaLine());
+
+            // Declare bean
+            out.printin(type);
+            out.print(' ');
+            out.print(name);
+            out.println(" = null;");
+
+            // Lock (if required) while getting or creating bean
+            if (lock != null) {
+                out.printin("synchronized (");
+                out.print(lock);
+                out.println(") {");
+                out.pushIndent();
+            }
+
+            // Locate bean from context
+            out.printin(name);
+            out.print(" = (");
+            out.print(type);
+            out.print(") _jspx_page_context.getAttribute(");
+            out.print(quote(name));
+            out.print(", ");
+            out.print(scopename);
+            out.println(");");
+
+            // Create bean
+            /*
+             * Check if bean is already there
+             */
+            out.printin("if (");
+            out.print(name);
+            out.println(" == null){");
+            out.pushIndent();
+            if (klass == null && beanName == null) {
+                /*
+                 * If both class name and beanName is not specified, the bean
+                 * must be found locally, otherwise it's an error
+                 */
+                out.printin("throw new java.lang.InstantiationException(\"bean ");
+                out.print(name);
+                out.println(" not found within scope\");");
+            } else {
+                /*
+                 * Instantiate the bean if it is not in the specified scope.
+                 */
+                if (!generateNew) {
+                    String binaryName;
+                    if (beanName != null) {
+                        if (beanName.isNamedAttribute()) {
+                            // If the value for beanName was specified via
+                            // jsp:attribute, first generate code to evaluate
+                            // that body.
+                            binaryName = generateNamedAttributeValue(beanName
+                                    .getNamedAttributeNode());
+                        } else {
+                            binaryName = attributeValue(beanName, false,
+                                    String.class);
+                        }
+                    } else {
+                        // Implies klass is not null
+                        binaryName = quote(klass);
+                    }
+                    out.printil("try {");
+                    out.pushIndent();
+                    out.printin(name);
+                    out.print(" = (");
+                    out.print(type);
+                    out.print(") java.beans.Beans.instantiate(");
+                    out.print("this.getClass().getClassLoader(), ");
+                    out.print(binaryName);
+                    out.println(");");
+                    out.popIndent();
+                    /*
+                     * Note: Beans.instantiate throws ClassNotFoundException if
+                     * the bean class is abstract.
+                     */
+                    out.printil("} catch (java.lang.ClassNotFoundException exc) {");
+                    out.pushIndent();
+                    out.printil("throw new InstantiationException(exc.getMessage());");
+                    out.popIndent();
+                    out.printil("} catch (java.lang.Exception exc) {");
+                    out.pushIndent();
+                    out.printin("throw new javax.servlet.ServletException(");
+                    out.print("\"Cannot create bean of class \" + ");
+                    out.print(binaryName);
+                    out.println(", exc);");
+                    out.popIndent();
+                    out.printil("}"); // close of try
+                } else {
+                    // Implies klass is not null
+                    // Generate codes to instantiate the bean class
+                    out.printin(name);
+                    out.print(" = new ");
+                    out.print(canonicalName);
+                    out.println("();");
+                }
+                /*
+                 * Set attribute for bean in the specified scope
+                 */
+                out.printin("_jspx_page_context.setAttribute(");
+                out.print(quote(name));
+                out.print(", ");
+                out.print(name);
+                out.print(", ");
+                out.print(scopename);
+                out.println(");");
+
+                // Only visit the body when bean is instantiated
+                visitBody(n);
+            }
+            out.popIndent();
+            out.printil("}");
+
+            // End of lock block
+            if (lock != null) {
+                out.popIndent();
+                out.printil("}");
+            }
+
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        /**
+         * @return a string for the form 'attr = "value"'
+         */
+        private String makeAttr(String attr, String value) {
+            if (value == null)
+                return "";
+
+            return " " + attr + "=\"" + value + '\"';
+        }
+
+        @Override
+        public void visit(Node.PlugIn n) throws JasperException {
+
+            /**
+             * A visitor to handle &lt;jsp:param&gt; in a plugin
+             */
+            class ParamVisitor extends Node.Visitor {
+
+                private boolean ie;
+
+                ParamVisitor(boolean ie) {
+                    this.ie = ie;
+                }
+
+                @Override
+                public void visit(Node.ParamAction n) throws JasperException {
+
+                    String name = n.getTextAttribute("name");
+                    if (name.equalsIgnoreCase("object"))
+                        name = "java_object";
+                    else if (name.equalsIgnoreCase("type"))
+                        name = "java_type";
+
+                    n.setBeginJavaLine(out.getJavaLine());
+                    // XXX - Fixed a bug here - value used to be output
+                    // inline, which is only okay if value is not an EL
+                    // expression. Also, key/value pairs for the
+                    // embed tag were not being generated correctly.
+                    // Double check that this is now the correct behavior.
+                    if (ie) {
+                        // We want something of the form
+                        // out.println( "<param name=\"blah\"
+                        // value=\"" + ... + "\">" );
+                        out.printil("out.write( \"<param name=\\\""
+                                + escape(name)
+                                + "\\\" value=\\\"\" + "
+                                + attributeValue(n.getValue(), false,
+                                        String.class) + " + \"\\\">\" );");
+                        out.printil("out.write(\"\\n\");");
+                    } else {
+                        // We want something of the form
+                        // out.print( " blah=\"" + ... + "\"" );
+                        out.printil("out.write( \" "
+                                + escape(name)
+                                + "=\\\"\" + "
+                                + attributeValue(n.getValue(), false,
+                                        String.class) + " + \"\\\"\" );");
+                    }
+
+                    n.setEndJavaLine(out.getJavaLine());
+                }
+            }
+
+            String type = n.getTextAttribute("type");
+            String code = n.getTextAttribute("code");
+            String name = n.getTextAttribute("name");
+            Node.JspAttribute height = n.getHeight();
+            Node.JspAttribute width = n.getWidth();
+            String hspace = n.getTextAttribute("hspace");
+            String vspace = n.getTextAttribute("vspace");
+            String align = n.getTextAttribute("align");
+            String iepluginurl = n.getTextAttribute("iepluginurl");
+            String nspluginurl = n.getTextAttribute("nspluginurl");
+            String codebase = n.getTextAttribute("codebase");
+            String archive = n.getTextAttribute("archive");
+            String jreversion = n.getTextAttribute("jreversion");
+
+            String widthStr = null;
+            if (width != null) {
+                if (width.isNamedAttribute()) {
+                    widthStr = generateNamedAttributeValue(width
+                            .getNamedAttributeNode());
+                } else {
+                    widthStr = attributeValue(width, false, String.class);
+                }
+            }
+
+            String heightStr = null;
+            if (height != null) {
+                if (height.isNamedAttribute()) {
+                    heightStr = generateNamedAttributeValue(height
+                            .getNamedAttributeNode());
+                } else {
+                    heightStr = attributeValue(height, false, String.class);
+                }
+            }
+
+            if (iepluginurl == null)
+                iepluginurl = Constants.IE_PLUGIN_URL;
+            if (nspluginurl == null)
+                nspluginurl = Constants.NS_PLUGIN_URL;
+
+            n.setBeginJavaLine(out.getJavaLine());
+
+            // If any of the params have their values specified by
+            // jsp:attribute, prepare those values first.
+            // Look for a params node and prepare its param subelements:
+            Node.JspBody jspBody = findJspBody(n);
+            if (jspBody != null) {
+                Node.Nodes subelements = jspBody.getBody();
+                if (subelements != null) {
+                    for (int i = 0; i < subelements.size(); i++) {
+                        Node m = subelements.getNode(i);
+                        if (m instanceof Node.ParamsAction) {
+                            prepareParams(m);
+                            break;
+                        }
+                    }
+                }
+            }
+
+            // XXX - Fixed a bug here - width and height can be set
+            // dynamically. Double-check if this generation is correct.
+
+            // IE style plugin
+            // <object ...>
+            // First compose the runtime output string
+            String s0 = "<object"
+                    + makeAttr("classid", ctxt.getOptions().getIeClassId())
+                    + makeAttr("name", name);
+
+            String s1 = "";
+            if (width != null) {
+                s1 = " + \" width=\\\"\" + " + widthStr + " + \"\\\"\"";
+            }
+
+            String s2 = "";
+            if (height != null) {
+                s2 = " + \" height=\\\"\" + " + heightStr + " + \"\\\"\"";
+            }
+
+            String s3 = makeAttr("hspace", hspace) + makeAttr("vspace", vspace)
+                    + makeAttr("align", align)
+                    + makeAttr("codebase", iepluginurl) + '>';
+
+            // Then print the output string to the java file
+            out.printil("out.write(" + quote(s0) + s1 + s2 + " + " + quote(s3)
+                    + ");");
+            out.printil("out.write(\"\\n\");");
+
+            // <param > for java_code
+            s0 = "<param name=\"java_code\"" + makeAttr("value", code) + '>';
+            out.printil("out.write(" + quote(s0) + ");");
+            out.printil("out.write(\"\\n\");");
+
+            // <param > for java_codebase
+            if (codebase != null) {
+                s0 = "<param name=\"java_codebase\""
+                        + makeAttr("value", codebase) + '>';
+                out.printil("out.write(" + quote(s0) + ");");
+                out.printil("out.write(\"\\n\");");
+            }
+
+            // <param > for java_archive
+            if (archive != null) {
+                s0 = "<param name=\"java_archive\""
+                        + makeAttr("value", archive) + '>';
+                out.printil("out.write(" + quote(s0) + ");");
+                out.printil("out.write(\"\\n\");");
+            }
+
+            // <param > for type
+            s0 = "<param name=\"type\""
+                    + makeAttr("value", "application/x-java-"
+                            + type
+                            + ((jreversion == null) ? "" : ";version="
+                                    + jreversion)) + '>';
+            out.printil("out.write(" + quote(s0) + ");");
+            out.printil("out.write(\"\\n\");");
+
+            /*
+             * generate a <param> for each <jsp:param> in the plugin body
+             */
+            if (n.getBody() != null)
+                n.getBody().visit(new ParamVisitor(true));
+
+            /*
+             * Netscape style plugin part
+             */
+            out.printil("out.write(" + quote("<comment>") + ");");
+            out.printil("out.write(\"\\n\");");
+            s0 = "<EMBED"
+                    + makeAttr("type", "application/x-java-"
+                            + type
+                            + ((jreversion == null) ? "" : ";version="
+                                    + jreversion)) + makeAttr("name", name);
+
+            // s1 and s2 are the same as before.
+
+            s3 = makeAttr("hspace", hspace) + makeAttr("vspace", vspace)
+                    + makeAttr("align", align)
+                    + makeAttr("pluginspage", nspluginurl)
+                    + makeAttr("java_code", code)
+                    + makeAttr("java_codebase", codebase)
+                    + makeAttr("java_archive", archive);
+            out.printil("out.write(" + quote(s0) + s1 + s2 + " + " + quote(s3)
+                    + ");");
+
+            /*
+             * Generate a 'attr = "value"' for each <jsp:param> in plugin body
+             */
+            if (n.getBody() != null)
+                n.getBody().visit(new ParamVisitor(false));
+
+            out.printil("out.write(" + quote("/>") + ");");
+            out.printil("out.write(\"\\n\");");
+
+            out.printil("out.write(" + quote("<noembed>") + ");");
+            out.printil("out.write(\"\\n\");");
+
+            /*
+             * Fallback
+             */
+            if (n.getBody() != null) {
+                visitBody(n);
+                out.printil("out.write(\"\\n\");");
+            }
+
+            out.printil("out.write(" + quote("</noembed>") + ");");
+            out.printil("out.write(\"\\n\");");
+
+            out.printil("out.write(" + quote("</comment>") + ");");
+            out.printil("out.write(\"\\n\");");
+
+            out.printil("out.write(" + quote("</object>") + ");");
+            out.printil("out.write(\"\\n\");");
+
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        @Override
+        public void visit(Node.NamedAttribute n) throws JasperException {
+            // Don't visit body of this tag - we already did earlier.
+        }
+
+        @Override
+        public void visit(Node.CustomTag n) throws JasperException {
+
+            // Use plugin to generate more efficient code if there is one.
+            if (n.useTagPlugin()) {
+                generateTagPlugin(n);
+                return;
+            }
+
+            TagHandlerInfo handlerInfo = getTagHandlerInfo(n);
+
+            // Create variable names
+            String baseVar = createTagVarName(n.getQName(), n.getPrefix(), n
+                    .getLocalName());
+            String tagEvalVar = "_jspx_eval_" + baseVar;
+            String tagHandlerVar = "_jspx_th_" + baseVar;
+            String tagPushBodyCountVar = "_jspx_push_body_count_" + baseVar;
+
+            // If the tag contains no scripting element, generate its codes
+            // to a method.
+            ServletWriter outSave = null;
+            Node.ChildInfo ci = n.getChildInfo();
+            if (ci.isScriptless() && !ci.hasScriptingVars()) {
+                // The tag handler and its body code can reside in a separate
+                // method if it is scriptless and does not have any scripting
+                // variable defined.
+
+                String tagMethod = "_jspx_meth_" + baseVar;
+
+                // Generate a call to this method
+                out.printin("if (");
+                out.print(tagMethod);
+                out.print("(");
+                if (parent != null) {
+                    out.print(parent);
+                    out.print(", ");
+                }
+                out.print("_jspx_page_context");
+                if (pushBodyCountVar != null) {
+                    out.print(", ");
+                    out.print(pushBodyCountVar);
+                }
+                out.println("))");
+                out.pushIndent();
+                out.printil((methodNesting > 0) ? "return true;" : "return;");
+                out.popIndent();
+
+                // Set up new buffer for the method
+                outSave = out;
+                /*
+                 * For fragments, their bodies will be generated in fragment
+                 * helper classes, and the Java line adjustments will be done
+                 * there, hence they are set to null here to avoid double
+                 * adjustments.
+                 */
+                GenBuffer genBuffer = new GenBuffer(n,
+                        n.implementsSimpleTag() ? null : n.getBody());
+                methodsBuffered.add(genBuffer);
+                out = genBuffer.getOut();
+
+                methodNesting++;
+                // Generate code for method declaration
+                out.println();
+                out.pushIndent();
+                out.printin("private boolean ");
+                out.print(tagMethod);
+                out.print("(");
+                if (parent != null) {
+                    out.print("javax.servlet.jsp.tagext.JspTag ");
+                    out.print(parent);
+                    out.print(", ");
+                }
+                out.print("javax.servlet.jsp.PageContext _jspx_page_context");
+                if (pushBodyCountVar != null) {
+                    out.print(", int[] ");
+                    out.print(pushBodyCountVar);
+                }
+                out.println(")");
+                out.printil("        throws java.lang.Throwable {");
+                out.pushIndent();
+
+                // Initialize local variables used in this method.
+                if (!isTagFile) {
+                    out.printil("javax.servlet.jsp.PageContext pageContext = _jspx_page_context;");
+                }
+                out.printil("javax.servlet.jsp.JspWriter out = _jspx_page_context.getOut();");
+                generateLocalVariables(out, n);
+            }
+
+            // Add the named objects to the list of 'introduced' names to enable
+            // a later test as per JSP.5.3
+            VariableInfo[] infos = n.getVariableInfos();
+            if (infos != null && infos.length > 0) {
+                for (int i = 0; i < infos.length; i++) {
+                    VariableInfo info = infos[i];
+                    if (info != null && info.getVarName() != null)
+                        pageInfo.getVarInfoNames().add(info.getVarName());
+                }
+            }
+            TagVariableInfo[] tagInfos = n.getTagVariableInfos();
+            if (tagInfos != null && tagInfos.length > 0) {
+                for (int i = 0; i < tagInfos.length; i++) {
+                    TagVariableInfo tagInfo = tagInfos[i];
+                    if (tagInfo != null) {
+                        String name = tagInfo.getNameGiven();
+                        if (name == null) {
+                            String nameFromAttribute =
+                                tagInfo.getNameFromAttribute();
+                            name = n.getAttributeValue(nameFromAttribute);
+                        }
+                        pageInfo.getVarInfoNames().add(name);
+                    }
+                }
+            }
+            
+            
+            if (n.implementsSimpleTag()) {
+                generateCustomDoTag(n, handlerInfo, tagHandlerVar);
+            } else {
+                /*
+                 * Classic tag handler: Generate code for start element, body,
+                 * and end element
+                 */
+                generateCustomStart(n, handlerInfo, tagHandlerVar, tagEvalVar,
+                        tagPushBodyCountVar);
+
+                // visit body
+                String tmpParent = parent;
+                parent = tagHandlerVar;
+                boolean isSimpleTagParentSave = isSimpleTagParent;
+                isSimpleTagParent = false;
+                String tmpPushBodyCountVar = null;
+                if (n.implementsTryCatchFinally()) {
+                    tmpPushBodyCountVar = pushBodyCountVar;
+                    pushBodyCountVar = tagPushBodyCountVar;
+                }
+                boolean tmpIsSimpleTagHandler = isSimpleTagHandler;
+                isSimpleTagHandler = false;
+
+                visitBody(n);
+
+                parent = tmpParent;
+                isSimpleTagParent = isSimpleTagParentSave;
+                if (n.implementsTryCatchFinally()) {
+                    pushBodyCountVar = tmpPushBodyCountVar;
+                }
+                isSimpleTagHandler = tmpIsSimpleTagHandler;
+
+                generateCustomEnd(n, tagHandlerVar, tagEvalVar,
+                        tagPushBodyCountVar);
+            }
+
+            if (ci.isScriptless() && !ci.hasScriptingVars()) {
+                // Generate end of method
+                if (methodNesting > 0) {
+                    out.printil("return false;");
+                }
+                out.popIndent();
+                out.printil("}");
+                out.popIndent();
+
+                methodNesting--;
+
+                // restore previous writer
+                out = outSave;
+            }
+            
+        }
+
+        private static final String DOUBLE_QUOTE = "\\\"";
+
+        @Override
+        public void visit(Node.UninterpretedTag n) throws JasperException {
+
+            n.setBeginJavaLine(out.getJavaLine());
+
+            /*
+             * Write begin tag
+             */
+            out.printin("out.write(\"<");
+            out.print(n.getQName());
+
+            Attributes attrs = n.getNonTaglibXmlnsAttributes();
+            if (attrs != null) {
+                for (int i = 0; i < attrs.getLength(); i++) {
+                    out.print(" ");
+                    out.print(attrs.getQName(i));
+                    out.print("=");
+                    out.print(DOUBLE_QUOTE);
+                    out.print(attrs.getValue(i).replace("\"", "&quot;"));
+                    out.print(DOUBLE_QUOTE);
+                }
+            }
+
+            attrs = n.getAttributes();
+            if (attrs != null) {
+                Node.JspAttribute[] jspAttrs = n.getJspAttributes();
+                for (int i = 0; i < attrs.getLength(); i++) {
+                    out.print(" ");
+                    out.print(attrs.getQName(i));
+                    out.print("=");
+                    if (jspAttrs[i].isELInterpreterInput()) {
+                        out.print("\\\"\" + ");
+                        out.print(attributeValue(jspAttrs[i], false,
+                                String.class));
+                        out.print(" + \"\\\"");
+                    } else {
+                        out.print(DOUBLE_QUOTE);
+                        out.print(attrs.getValue(i).replace("\"", "&quot;"));
+                        out.print(DOUBLE_QUOTE);
+                    }
+                }
+            }
+
+            if (n.getBody() != null) {
+                out.println(">\");");
+
+                // Visit tag body
+                visitBody(n);
+
+                /*
+                 * Write end tag
+                 */
+                out.printin("out.write(\"</");
+                out.print(n.getQName());
+                out.println(">\");");
+            } else {
+                out.println("/>\");");
+            }
+
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        @Override
+        public void visit(Node.JspElement n) throws JasperException {
+
+            n.setBeginJavaLine(out.getJavaLine());
+
+            // Compute attribute value string for XML-style and named
+            // attributes
+            Hashtable<String,String> map = new Hashtable<String,String>();
+            Node.JspAttribute[] attrs = n.getJspAttributes();
+            for (int i = 0; attrs != null && i < attrs.length; i++) {
+                String value = null;
+                String nvp = null;
+                if (attrs[i].isNamedAttribute()) {
+                    NamedAttribute attr = attrs[i].getNamedAttributeNode();
+                    Node.JspAttribute omitAttr = attr.getOmit();
+                    String omit;
+                    if (omitAttr == null) {
+                        omit = "false";
+                    } else {
+                        omit = attributeValue(omitAttr, false, boolean.class);
+                        if ("true".equals(omit)) {
+                            continue;
+                        }
+                    }
+                    value = generateNamedAttributeValue(
+                            attrs[i].getNamedAttributeNode());
+                    if ("false".equals(omit)) {
+                        nvp = " + \" " + attrs[i].getName() + "=\\\"\" + " +
+                                value + " + \"\\\"\"";
+                    } else {
+                        nvp = " + (java.lang.Boolean.valueOf(" + omit + ")?\"\":\" " +
+                                attrs[i].getName() + "=\\\"\" + " + value +
+                                " + \"\\\"\")";
+                    }
+                } else {
+                    value = attributeValue(attrs[i], false, Object.class);
+                    nvp = " + \" " + attrs[i].getName() + "=\\\"\" + " +
+                            value + " + \"\\\"\"";
+                }
+                map.put(attrs[i].getName(), nvp);
+            }
+
+            // Write begin tag, using XML-style 'name' attribute as the
+            // element name
+            String elemName = attributeValue(n.getNameAttribute(), false,
+                    String.class);
+            out.printin("out.write(\"<\"");
+            out.print(" + " + elemName);
+
+            // Write remaining attributes
+            Enumeration<String> enumeration = map.keys();
+            while (enumeration.hasMoreElements()) {
+                String attrName = enumeration.nextElement();
+                out.print(map.get(attrName));
+            }
+
+            // Does the <jsp:element> have nested tags other than
+            // <jsp:attribute>
+            boolean hasBody = false;
+            Node.Nodes subelements = n.getBody();
+            if (subelements != null) {
+                for (int i = 0; i < subelements.size(); i++) {
+                    Node subelem = subelements.getNode(i);
+                    if (!(subelem instanceof Node.NamedAttribute)) {
+                        hasBody = true;
+                        break;
+                    }
+                }
+            }
+            if (hasBody) {
+                out.println(" + \">\");");
+
+                // Smap should not include the body
+                n.setEndJavaLine(out.getJavaLine());
+
+                // Visit tag body
+                visitBody(n);
+
+                // Write end tag
+                out.printin("out.write(\"</\"");
+                out.print(" + " + elemName);
+                out.println(" + \">\");");
+            } else {
+                out.println(" + \"/>\");");
+                n.setEndJavaLine(out.getJavaLine());
+            }
+        }
+
+        @Override
+        public void visit(Node.TemplateText n) throws JasperException {
+
+            String text = n.getText();
+
+            int textSize = text.length();
+            if (textSize == 0) {
+                return;
+            }
+
+            if (textSize <= 3) {
+                // Special case small text strings
+                n.setBeginJavaLine(out.getJavaLine());
+                int lineInc = 0;
+                for (int i = 0; i < textSize; i++) {
+                    char ch = text.charAt(i);
+                    out.printil("out.write(" + quote(ch) + ");");
+                    if (i > 0) {
+                        n.addSmap(lineInc);
+                    }
+                    if (ch == '\n') {
+                        lineInc++;
+                    }
+                }
+                n.setEndJavaLine(out.getJavaLine());
+                return;
+            }
+
+            if (ctxt.getOptions().genStringAsCharArray()) {
+                // Generate Strings as char arrays, for performance
+                ServletWriter caOut;
+                if (charArrayBuffer == null) {
+                    charArrayBuffer = new GenBuffer();
+                    caOut = charArrayBuffer.getOut();
+                    caOut.pushIndent();
+                    textMap = new HashMap<String,String>();
+                } else {
+                    caOut = charArrayBuffer.getOut();
+                }
+                // UTF-8 is up to 4 bytes per character
+                // String constants are limited to 64k bytes
+                // Limit string constants here to 16k characters
+                int textIndex = 0;
+                int textLength = text.length();
+                while (textIndex < textLength) {
+                    int len = 0;
+                    if (textLength - textIndex > 16384) {
+                        len = 16384;
+                    } else {
+                        len = textLength - textIndex;
+                    }
+                    String output = text.substring(textIndex, textIndex + len);
+                    String charArrayName = textMap.get(output);
+                    if (charArrayName == null) {
+                        charArrayName = "_jspx_char_array_" + charArrayCount++;
+                        textMap.put(output, charArrayName);
+                        caOut.printin("static char[] ");
+                        caOut.print(charArrayName);
+                        caOut.print(" = ");
+                        caOut.print(quote(output));
+                        caOut.println(".toCharArray();");
+                    }
+    
+                    n.setBeginJavaLine(out.getJavaLine());
+                    out.printil("out.write(" + charArrayName + ");");
+                    n.setEndJavaLine(out.getJavaLine());
+                    
+                    textIndex = textIndex + len;
+                }
+                return;
+            }
+
+            n.setBeginJavaLine(out.getJavaLine());
+
+            out.printin();
+            StringBuilder sb = new StringBuilder("out.write(\"");
+            int initLength = sb.length();
+            int count = JspUtil.CHUNKSIZE;
+            int srcLine = 0; // relative to starting source line
+            for (int i = 0; i < text.length(); i++) {
+                char ch = text.charAt(i);
+                --count;
+                switch (ch) {
+                case '"':
+                    sb.append('\\').append('\"');
+                    break;
+                case '\\':
+                    sb.append('\\').append('\\');
+                    break;
+                case '\r':
+                    sb.append('\\').append('r');
+                    break;
+                case '\n':
+                    sb.append('\\').append('n');
+                    srcLine++;
+
+                    if (breakAtLF || count < 0) {
+                        // Generate an out.write() when see a '\n' in template
+                        sb.append("\");");
+                        out.println(sb.toString());
+                        if (i < text.length() - 1) {
+                            out.printin();
+                        }
+                        sb.setLength(initLength);
+                        count = JspUtil.CHUNKSIZE;
+                    }
+                    // add a Smap for this line
+                    n.addSmap(srcLine);
+                    break;
+                case '\t': // Not sure we need this
+                    sb.append('\\').append('t');
+                    break;
+                default:
+                    sb.append(ch);
+                }
+            }
+
+            if (sb.length() > initLength) {
+                sb.append("\");");
+                out.println(sb.toString());
+            }
+
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        @Override
+        public void visit(Node.JspBody n) throws JasperException {
+            if (n.getBody() != null) {
+                if (isSimpleTagHandler) {
+                    out.printin(simpleTagHandlerVar);
+                    out.print(".setJspBody(");
+                    generateJspFragment(n, simpleTagHandlerVar);
+                    out.println(");");
+                } else {
+                    visitBody(n);
+                }
+            }
+        }
+
+        @Override
+        public void visit(Node.InvokeAction n) throws JasperException {
+
+            n.setBeginJavaLine(out.getJavaLine());
+
+            // Copy virtual page scope of tag file to page scope of invoking
+            // page
+            out.printil("((org.apache.jasper.runtime.JspContextWrapper) this.jspContext).syncBeforeInvoke();");
+            String varReaderAttr = n.getTextAttribute("varReader");
+            String varAttr = n.getTextAttribute("var");
+            if (varReaderAttr != null || varAttr != null) {
+                out.printil("_jspx_sout = new java.io.StringWriter();");
+            } else {
+                out.printil("_jspx_sout = null;");
+            }
+
+            // Invoke fragment, unless fragment is null
+            out.printin("if (");
+            out.print(toGetterMethod(n.getTextAttribute("fragment")));
+            out.println(" != null) {");
+            out.pushIndent();
+            out.printin(toGetterMethod(n.getTextAttribute("fragment")));
+            out.println(".invoke(_jspx_sout);");
+            out.popIndent();
+            out.printil("}");
+
+            // Store varReader in appropriate scope
+            if (varReaderAttr != null || varAttr != null) {
+                String scopeName = n.getTextAttribute("scope");
+                out.printin("_jspx_page_context.setAttribute(");
+                if (varReaderAttr != null) {
+                    out.print(quote(varReaderAttr));
+                    out.print(", new java.io.StringReader(_jspx_sout.toString())");
+                } else {
+                    out.print(quote(varAttr));
+                    out.print(", _jspx_sout.toString()");
+                }
+                if (scopeName != null) {
+                    out.print(", ");
+                    out.print(getScopeConstant(scopeName));
+                }
+                out.println(");");
+            }
+
+            // Restore EL context
+            out.printil("jspContext.getELContext().putContext(javax.servlet.jsp.JspContext.class,getJspContext());");
+
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        @Override
+        public void visit(Node.DoBodyAction n) throws JasperException {
+
+            n.setBeginJavaLine(out.getJavaLine());
+
+            // Copy virtual page scope of tag file to page scope of invoking
+            // page
+            out.printil("((org.apache.jasper.runtime.JspContextWrapper) this.jspContext).syncBeforeInvoke();");
+
+            // Invoke body
+            String varReaderAttr = n.getTextAttribute("varReader");
+            String varAttr = n.getTextAttribute("var");
+            if (varReaderAttr != null || varAttr != null) {
+                out.printil("_jspx_sout = new java.io.StringWriter();");
+            } else {
+                out.printil("_jspx_sout = null;");
+            }
+            out.printil("if (getJspBody() != null)");
+            out.pushIndent();
+            out.printil("getJspBody().invoke(_jspx_sout);");
+            out.popIndent();
+
+            // Store varReader in appropriate scope
+            if (varReaderAttr != null || varAttr != null) {
+                String scopeName = n.getTextAttribute("scope");
+                out.printin("_jspx_page_context.setAttribute(");
+                if (varReaderAttr != null) {
+                    out.print(quote(varReaderAttr));
+                    out.print(", new java.io.StringReader(_jspx_sout.toString())");
+                } else {
+                    out.print(quote(varAttr));
+                    out.print(", _jspx_sout.toString()");
+                }
+                if (scopeName != null) {
+                    out.print(", ");
+                    out.print(getScopeConstant(scopeName));
+                }
+                out.println(");");
+            }
+
+            // Restore EL context
+            out.printil("jspContext.getELContext().putContext(javax.servlet.jsp.JspContext.class,getJspContext());");
+
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        @Override
+        public void visit(Node.AttributeGenerator n) throws JasperException {
+            Node.CustomTag tag = n.getTag();
+            Node.JspAttribute[] attrs = tag.getJspAttributes();
+            for (int i = 0; attrs != null && i < attrs.length; i++) {
+                if (attrs[i].getName().equals(n.getName())) {
+                    out.print(evaluateAttribute(getTagHandlerInfo(tag),
+                            attrs[i], tag, null));
+                    break;
+                }
+            }
+        }
+
+        private TagHandlerInfo getTagHandlerInfo(Node.CustomTag n)
+                throws JasperException {
+            Hashtable<String,TagHandlerInfo> handlerInfosByShortName =
+                handlerInfos.get(n.getPrefix());
+            if (handlerInfosByShortName == null) {
+                handlerInfosByShortName =
+                    new Hashtable<String,TagHandlerInfo>();
+                handlerInfos.put(n.getPrefix(), handlerInfosByShortName);
+            }
+            TagHandlerInfo handlerInfo =
+                handlerInfosByShortName.get(n.getLocalName());
+            if (handlerInfo == null) {
+                handlerInfo = new TagHandlerInfo(n, n.getTagHandlerClass(), err);
+                handlerInfosByShortName.put(n.getLocalName(), handlerInfo);
+            }
+            return handlerInfo;
+        }
+
+        private void generateTagPlugin(Node.CustomTag n) throws JasperException {
+            if (n.getAtSTag() != null) {
+                n.getAtSTag().visit(this);
+            }
+            visitBody(n);
+            if (n.getAtETag() != null) {
+                n.getAtETag().visit(this);
+            }
+        }
+
+        private void generateCustomStart(Node.CustomTag n,
+                TagHandlerInfo handlerInfo, String tagHandlerVar,
+                String tagEvalVar, String tagPushBodyCountVar)
+                throws JasperException {
+
+            Class<?> tagHandlerClass =
+                handlerInfo.getTagHandlerClass();
+
+            out.printin("//  ");
+            out.println(n.getQName());
+            n.setBeginJavaLine(out.getJavaLine());
+
+            // Declare AT_BEGIN scripting variables
+            declareScriptingVars(n, VariableInfo.AT_BEGIN);
+            saveScriptingVars(n, VariableInfo.AT_BEGIN);
+
+            String tagHandlerClassName = tagHandlerClass.getCanonicalName();
+            if (isPoolingEnabled && !(n.implementsJspIdConsumer())) {
+                out.printin(tagHandlerClassName);
+                out.print(" ");
+                out.print(tagHandlerVar);
+                out.print(" = ");
+                out.print("(");
+                out.print(tagHandlerClassName);
+                out.print(") ");
+                out.print(n.getTagHandlerPoolName());
+                out.print(".get(");
+                out.print(tagHandlerClassName);
+                out.println(".class);");
+            } else {
+                writeNewInstance(tagHandlerVar, tagHandlerClassName);
+            }
+
+            // includes setting the context
+            generateSetters(n, tagHandlerVar, handlerInfo, false);
+
+            // JspIdConsumer (after context has been set)
+            if (n.implementsJspIdConsumer()) {
+                out.printin(tagHandlerVar);
+                out.print(".setJspId(\"");
+                out.print(createJspId());
+                out.println("\");");
+            }
+
+            if (n.implementsTryCatchFinally()) {
+                out.printin("int[] ");
+                out.print(tagPushBodyCountVar);
+                out.println(" = new int[] { 0 };");
+                out.printil("try {");
+                out.pushIndent();
+            }
+            out.printin("int ");
+            out.print(tagEvalVar);
+            out.print(" = ");
+            out.print(tagHandlerVar);
+            out.println(".doStartTag();");
+
+            if (!n.implementsBodyTag()) {
+                // Synchronize AT_BEGIN scripting variables
+                syncScriptingVars(n, VariableInfo.AT_BEGIN);
+            }
+
+            if (!n.hasEmptyBody()) {
+                out.printin("if (");
+                out.print(tagEvalVar);
+                out.println(" != javax.servlet.jsp.tagext.Tag.SKIP_BODY) {");
+                out.pushIndent();
+
+                // Declare NESTED scripting variables
+                declareScriptingVars(n, VariableInfo.NESTED);
+                saveScriptingVars(n, VariableInfo.NESTED);
+
+                if (n.implementsBodyTag()) {
+                    out.printin("if (");
+                    out.print(tagEvalVar);
+                    out.println(" != javax.servlet.jsp.tagext.Tag.EVAL_BODY_INCLUDE) {");
+                    // Assume EVAL_BODY_BUFFERED
+                    out.pushIndent();
+                    out.printil("out = _jspx_page_context.pushBody();");
+                    if (n.implementsTryCatchFinally()) {
+                        out.printin(tagPushBodyCountVar);
+                        out.println("[0]++;");
+                    } else if (pushBodyCountVar != null) {
+                        out.printin(pushBodyCountVar);
+                        out.println("[0]++;");
+                    }
+                    out.printin(tagHandlerVar);
+                    out.println(".setBodyContent((javax.servlet.jsp.tagext.BodyContent) out);");
+                    out.printin(tagHandlerVar);
+                    out.println(".doInitBody();");
+
+                    out.popIndent();
+                    out.printil("}");
+
+                    // Synchronize AT_BEGIN and NESTED scripting variables
+                    syncScriptingVars(n, VariableInfo.AT_BEGIN);
+                    syncScriptingVars(n, VariableInfo.NESTED);
+
+                } else {
+                    // Synchronize NESTED scripting variables
+                    syncScriptingVars(n, VariableInfo.NESTED);
+                }
+
+                if (n.implementsIterationTag()) {
+                    out.printil("do {");
+                    out.pushIndent();
+                }
+            }
+            // Map the Java lines that handles start of custom tags to the
+            // JSP line for this tag
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        private void writeNewInstance(String tagHandlerVar, String tagHandlerClassName) {
+            if (Constants.USE_INSTANCE_MANAGER_FOR_TAGS) {
+                out.printin(tagHandlerClassName);
+                out.print(" ");
+                out.print(tagHandlerVar);
+                out.print(" = (");
+                out.print(tagHandlerClassName);
+                out.print(")");
+                out.print(VAR_INSTANCEMANAGER);
+                out.print(".newInstance(\"");
+                out.print(tagHandlerClassName);
+                out.println("\", this.getClass().getClassLoader());");
+            } else {
+                out.printin(tagHandlerClassName);
+                out.print(" ");
+                out.print(tagHandlerVar);
+                out.print(" = (");
+                out.print("new ");
+                out.print(tagHandlerClassName);
+                out.println("());");
+                out.printin(VAR_INSTANCEMANAGER);
+                out.print(".newInstance(");
+                out.print(tagHandlerVar);
+                out.println(");");
+            }
+        }
+
+        private void writeDestroyInstance(String tagHandlerVar) {
+            out.printin(VAR_INSTANCEMANAGER);
+            out.print(".destroyInstance(");
+            out.print(tagHandlerVar);
+            out.println(");");
+        }
+
+        private void generateCustomEnd(Node.CustomTag n, String tagHandlerVar,
+                String tagEvalVar, String tagPushBodyCountVar) {
+
+            if (!n.hasEmptyBody()) {
+                if (n.implementsIterationTag()) {
+                    out.printin("int evalDoAfterBody = ");
+                    out.print(tagHandlerVar);
+                    out.println(".doAfterBody();");
+
+                    // Synchronize AT_BEGIN and NESTED scripting variables
+                    syncScriptingVars(n, VariableInfo.AT_BEGIN);
+                    syncScriptingVars(n, VariableInfo.NESTED);
+
+                    out.printil("if (evalDoAfterBody != javax.servlet.jsp.tagext.BodyTag.EVAL_BODY_AGAIN)");
+                    out.pushIndent();
+                    out.printil("break;");
+                    out.popIndent();
+
+                    out.popIndent();
+                    out.printil("} while (true);");
+                }
+
+                restoreScriptingVars(n, VariableInfo.NESTED);
+
+                if (n.implementsBodyTag()) {
+                    out.printin("if (");
+                    out.print(tagEvalVar);
+                    out.println(" != javax.servlet.jsp.tagext.Tag.EVAL_BODY_INCLUDE) {");
+                    out.pushIndent();
+                    out.printil("out = _jspx_page_context.popBody();");
+                    if (n.implementsTryCatchFinally()) {
+                        out.printin(tagPushBodyCountVar);
+                        out.println("[0]--;");
+                    } else if (pushBodyCountVar != null) {
+                        out.printin(pushBodyCountVar);
+                        out.println("[0]--;");
+                    }
+                    out.popIndent();
+                    out.printil("}");
+                }
+
+                out.popIndent(); // EVAL_BODY
+                out.printil("}");
+            }
+
+            out.printin("if (");
+            out.print(tagHandlerVar);
+            out.println(".doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) {");
+            out.pushIndent();
+            if (!n.implementsTryCatchFinally()) {
+                if (isPoolingEnabled && !(n.implementsJspIdConsumer())) {
+                    out.printin(n.getTagHandlerPoolName());
+                    out.print(".reuse(");
+                    out.print(tagHandlerVar);
+                    out.println(");");
+                } else {
+                    out.printin(tagHandlerVar);
+                    out.println(".release();");
+                    writeDestroyInstance(tagHandlerVar);
+                }
+            }
+            if (isTagFile || isFragment) {
+                out.printil("throw new javax.servlet.jsp.SkipPageException();");
+            } else {
+                out.printil((methodNesting > 0) ? "return true;" : "return;");
+            }
+            out.popIndent();
+            out.printil("}");
+            // Synchronize AT_BEGIN scripting variables
+            syncScriptingVars(n, VariableInfo.AT_BEGIN);
+
+            // TryCatchFinally
+            if (n.implementsTryCatchFinally()) {
+                out.popIndent(); // try
+                out.printil("} catch (java.lang.Throwable _jspx_exception) {");
+                out.pushIndent();
+
+                out.printin("while (");
+                out.print(tagPushBodyCountVar);
+                out.println("[0]-- > 0)");
+                out.pushIndent();
+                out.printil("out = _jspx_page_context.popBody();");
+                out.popIndent();
+
+                out.printin(tagHandlerVar);
+                out.println(".doCatch(_jspx_exception);");
+                out.popIndent();
+                out.printil("} finally {");
+                out.pushIndent();
+                out.printin(tagHandlerVar);
+                out.println(".doFinally();");
+            }
+
+            if (isPoolingEnabled && !(n.implementsJspIdConsumer())) {
+                out.printin(n.getTagHandlerPoolName());
+                out.print(".reuse(");
+                out.print(tagHandlerVar);
+                out.println(");");
+            } else {
+                out.printin(tagHandlerVar);
+                out.println(".release();");
+                writeDestroyInstance(tagHandlerVar);
+            }
+
+            if (n.implementsTryCatchFinally()) {
+                out.popIndent();
+                out.printil("}");
+            }
+
+            // Declare and synchronize AT_END scripting variables (must do this
+            // outside the try/catch/finally block)
+            declareScriptingVars(n, VariableInfo.AT_END);
+            syncScriptingVars(n, VariableInfo.AT_END);
+
+            restoreScriptingVars(n, VariableInfo.AT_BEGIN);
+        }
+
+        private void generateCustomDoTag(Node.CustomTag n,
+                TagHandlerInfo handlerInfo, String tagHandlerVar)
+                throws JasperException {
+
+            Class<?> tagHandlerClass =
+                handlerInfo.getTagHandlerClass();
+
+            n.setBeginJavaLine(out.getJavaLine());
+            out.printin("//  ");
+            out.println(n.getQName());
+
+            // Declare AT_BEGIN scripting variables
+            declareScriptingVars(n, VariableInfo.AT_BEGIN);
+            saveScriptingVars(n, VariableInfo.AT_BEGIN);
+
+            String tagHandlerClassName = tagHandlerClass.getCanonicalName();
+            writeNewInstance(tagHandlerVar, tagHandlerClassName);
+
+            generateSetters(n, tagHandlerVar, handlerInfo, true);
+
+            // JspIdConsumer (after context has been set)
+            if (n.implementsJspIdConsumer()) {
+                out.printin(tagHandlerVar);
+                out.print(".setJspId(\"");
+                out.print(createJspId());
+                out.println("\");");
+            }
+
+            // Set the body
+            if (findJspBody(n) == null) {
+                /*
+                 * Encapsulate body of custom tag invocation in JspFragment and
+                 * pass it to tag handler's setJspBody(), unless tag body is
+                 * empty
+                 */
+                if (!n.hasEmptyBody()) {
+                    out.printin(tagHandlerVar);
+                    out.print(".setJspBody(");
+                    generateJspFragment(n, tagHandlerVar);
+                    out.println(");");
+                }
+            } else {
+                /*
+                 * Body of tag is the body of the <jsp:body> element. The visit
+                 * method for that element is going to encapsulate that
+                 * element's body in a JspFragment and pass it to the tag
+                 * handler's setJspBody()
+                 */
+                String tmpTagHandlerVar = simpleTagHandlerVar;
+                simpleTagHandlerVar = tagHandlerVar;
+                boolean tmpIsSimpleTagHandler = isSimpleTagHandler;
+                isSimpleTagHandler = true;
+                visitBody(n);
+                simpleTagHandlerVar = tmpTagHandlerVar;
+                isSimpleTagHandler = tmpIsSimpleTagHandler;
+            }
+
+            out.printin(tagHandlerVar);
+            out.println(".doTag();");
+
+            restoreScriptingVars(n, VariableInfo.AT_BEGIN);
+
+            // Synchronize AT_BEGIN scripting variables
+            syncScriptingVars(n, VariableInfo.AT_BEGIN);
+
+            // Declare and synchronize AT_END scripting variables
+            declareScriptingVars(n, VariableInfo.AT_END);
+            syncScriptingVars(n, VariableInfo.AT_END);
+
+            // Resource injection
+            writeDestroyInstance(tagHandlerVar);
+
+            n.setEndJavaLine(out.getJavaLine());
+        }
+
+        private void declareScriptingVars(Node.CustomTag n, int scope) {
+            if (isFragment) {
+                // No need to declare Java variables, if we inside a
+                // JspFragment, because a fragment is always scriptless.
+                return;
+            }
+
+            List<Object> vec = n.getScriptingVars(scope);
+            if (vec != null) {
+                for (int i = 0; i < vec.size(); i++) {
+                    Object elem = vec.get(i);
+                    if (elem instanceof VariableInfo) {
+                        VariableInfo varInfo = (VariableInfo) elem;
+                        if (varInfo.getDeclare()) {
+                            out.printin(varInfo.getClassName());
+                            out.print(" ");
+                            out.print(varInfo.getVarName());
+                            out.println(" = null;");
+                        }
+                    } else {
+                        TagVariableInfo tagVarInfo = (TagVariableInfo) elem;
+                        if (tagVarInfo.getDeclare()) {
+                            String varName = tagVarInfo.getNameGiven();
+                            if (varName == null) {
+                                varName = n.getTagData().getAttributeString(
+                                        tagVarInfo.getNameFromAttribute());
+                            } else if (tagVarInfo.getNameFromAttribute() != null) {
+                                // alias
+                                continue;
+                            }
+                            out.printin(tagVarInfo.getClassName());
+                            out.print(" ");
+                            out.print(varName);
+                            out.println(" = null;");
+                        }
+                    }
+                }
+            }
+        }
+
+        /*
+         * This method is called as part of the custom tag's start element.
+         *
+         * If the given custom tag has a custom nesting level greater than 0,
+         * save the current values of its scripting variables to temporary
+         * variables, so those values may be restored in the tag's end element.
+         * This way, the scripting variables may be synchronized by the given
+         * tag without affecting their original values.
+         */
+        private void saveScriptingVars(Node.CustomTag n, int scope) {
+            if (n.getCustomNestingLevel() == 0) {
+                return;
+            }
+            if (isFragment) {
+                // No need to declare Java variables, if we inside a
+                // JspFragment, because a fragment is always scriptless.
+                // Thus, there is no need to save/ restore/ sync them.
+                // Note, that JspContextWrapper.syncFoo() methods will take
+                // care of saving/ restoring/ sync'ing of JspContext attributes.
+                return;
+            }
+
+            TagVariableInfo[] tagVarInfos = n.getTagVariableInfos();
+            VariableInfo[] varInfos = n.getVariableInfos();
+            if ((varInfos.length == 0) && (tagVarInfos.length == 0)) {
+                return;
+            }
+
+            List<Object> declaredVariables = n.getScriptingVars(scope);
+
+            if (varInfos.length > 0) {
+                for (int i = 0; i < varInfos.length; i++) {
+                    if (varInfos[i].getScope() != scope)
+                        continue;
+                    // If the scripting variable has been declared, skip codes
+                    // for saving and restoring it.
+                    if (declaredVariables.contains(varInfos[i]))
+                        continue;
+                    String varName = varInfos[i].getVarName();
+                    String tmpVarName = "_jspx_" + varName + "_"
+                            + n.getCustomNestingLevel();
+                    out.printin(tmpVarName);
+                    out.print(" = ");
+                    out.print(varName);
+                    out.println(";");
+                }
+            } else {
+                for (int i = 0; i < tagVarInfos.length; i++) {
+                    if (tagVarInfos[i].getScope() != scope)
+                        continue;
+                    // If the scripting variable has been declared, skip codes
+                    // for saving and restoring it.
+                    if (declaredVariables.contains(tagVarInfos[i]))
+                        continue;
+                    String varName = tagVarInfos[i].getNameGiven();
+                    if (varName == null) {
+                        varName = n.getTagData().getAttributeString(
+                                tagVarInfos[i].getNameFromAttribute());
+                    } else if (tagVarInfos[i].getNameFromAttribute() != null) {
+                        // alias
+                        continue;
+                    }
+                    String tmpVarName = "_jspx_" + varName + "_"
+                            + n.getCustomNestingLevel();
+                    out.printin(tmpVarName);
+                    out.print(" = ");
+                    out.print(varName);
+                    out.println(";");
+                }
+            }
+        }
+
+        /*
+         * This method is called as part of the custom tag's end element.
+         *
+         * If the given custom tag has a custom nesting level greater than 0,
+         * restore its scripting variables to their original values that were
+         * saved in the tag's start element.
+         */
+        private void restoreScriptingVars(Node.CustomTag n, int scope) {
+            if (n.getCustomNestingLevel() == 0) {
+                return;
+            }
+            if (isFragment) {
+                // No need to declare Java variables, if we inside a
+                // JspFragment, because a fragment is always scriptless.
+                // Thus, there is no need to save/ restore/ sync them.
+                // Note, that JspContextWrapper.syncFoo() methods will take
+                // care of saving/ restoring/ sync'ing of JspContext attributes.
+                return;
+            }
+
+            TagVariableInfo[] tagVarInfos = n.getTagVariableInfos();
+            VariableInfo[] varInfos = n.getVariableInfos();
+            if ((varInfos.length == 0) && (tagVarInfos.length == 0)) {
+                return;
+            }
+
+            List<Object> declaredVariables = n.getScriptingVars(scope);
+
+            if (varInfos.length > 0) {
+                for (int i = 0; i < varInfos.length; i++) {
+                    if (varInfos[i].getScope() != scope)
+                        continue;
+                    // If the scripting variable has been declared, skip codes
+                    // for saving and restoring it.
+                    if (declaredVariables.contains(varInfos[i]))
+                        continue;
+                    String varName = varInfos[i].getVarName();
+                    String tmpVarName = "_jspx_" + varName + "_"
+                            + n.getCustomNestingLevel();
+                    out.printin(varName);
+                    out.print(" = ");
+                    out.print(tmpVarName);
+                    out.println(";");
+                }
+            } else {
+                for (int i = 0; i < tagVarInfos.length; i++) {
+                    if (tagVarInfos[i].getScope() != scope)
+                        continue;
+                    // If the scripting variable has been declared, skip codes
+                    // for saving and restoring it.
+                    if (declaredVariables.contains(tagVarInfos[i]))
+                        continue;
+                    String varName = tagVarInfos[i].getNameGiven();
+                    if (varName == null) {
+                        varName = n.getTagData().getAttributeString(
+                                tagVarInfos[i].getNameFromAttribute());
+                    } else if (tagVarInfos[i].getNameFromAttribute() != null) {
+                        // alias
+                        continue;
+                    }
+                    String tmpVarName = "_jspx_" + varName + "_"
+                            + n.getCustomNestingLevel();
+                    out.printin(varName);
+                    out.print(" = ");
+                    out.print(tmpVarName);
+                    out.println(";");
+                }
+            }
+        }
+
+        /*
+         * Synchronizes the scripting variables of the given custom tag for the
+         * given scope.
+         */
+        private void syncScriptingVars(Node.CustomTag n, int scope) {
+            if (isFragment) {
+                // No need to declare Java variables, if we inside a
+                // JspFragment, because a fragment is always scriptless.
+                // Thus, there is no need to save/ restore/ sync them.
+                // Note, that JspContextWrapper.syncFoo() methods will take
+                // care of saving/ restoring/ sync'ing of JspContext attributes.
+                return;
+            }
+
+            TagVariableInfo[] tagVarInfos = n.getTagVariableInfos();
+            VariableInfo[] varInfos = n.getVariableInfos();
+
+            if ((varInfos.length == 0) && (tagVarInfos.length == 0)) {
+                return;
+            }
+
+            if (varInfos.length > 0) {
+                for (int i = 0; i < varInfos.length; i++) {
+                    if (varInfos[i].getScope() == scope) {
+                        out.printin(varInfos[i].getVarName());
+                        out.print(" = (");
+                        out.print(varInfos[i].getClassName());
+                        out.print(") _jspx_page_context.findAttribute(");
+                        out.print(quote(varInfos[i].getVarName()));
+                        out.println(");");
+                    }
+                }
+            } else {
+                for (int i = 0; i < tagVarInfos.length; i++) {
+                    if (tagVarInfos[i].getScope() == scope) {
+                        String name = tagVarInfos[i].getNameGiven();
+                        if (name == null) {
+                            name = n.getTagData().getAttributeString(
+                                    tagVarInfos[i].getNameFromAttribute());
+                        } else if (tagVarInfos[i].getNameFromAttribute() != null) {
+                            // alias
+                            continue;
+                        }
+                        out.printin(name);
+                        out.print(" = (");
+                        out.print(tagVarInfos[i].getClassName());
+                        out.print(") _jspx_page_context.findAttribute(");
+                        out.print(quote(name));
+                        out.println(");");
+                    }
+                }
+            }
+        }
+
+        private String getJspContextVar() {
+            if (this.isTagFile) {
+                return "this.getJspContext()";
+            }
+            return "_jspx_page_context";
+        }
+
+        private String getExpressionFactoryVar() {
+            return VAR_EXPRESSIONFACTORY;
+        }
+
+        /*
+         * Creates a tag variable name by concatenating the given prefix and
+         * shortName and encoded to make the resultant string a valid Java
+         * Identifier.
+         */
+        private String createTagVarName(String fullName, String prefix,
+                String shortName) {
+
+            String varName;
+            synchronized (tagVarNumbers) {
+                varName = prefix + "_" + shortName + "_";
+                if (tagVarNumbers.get(fullName) != null) {
+                    Integer i = tagVarNumbers.get(fullName);
+                    varName = varName + i.intValue();
+                    tagVarNumbers.put(fullName,
+                            Integer.valueOf(i.intValue() + 1));
+                } else {
+                    tagVarNumbers.put(fullName, Integer.valueOf(1));
+                    varName = varName + "0";
+                }
+            }
+            return JspUtil.makeJavaIdentifier(varName);
+        }
+
+        @SuppressWarnings("null")
+        private String evaluateAttribute(TagHandlerInfo handlerInfo,
+                Node.JspAttribute attr, Node.CustomTag n, String tagHandlerVar)
+                throws JasperException {
+
+            String attrValue = attr.getValue();
+            if (attrValue == null) {
+                if (attr.isNamedAttribute()) {
+                    if (n.checkIfAttributeIsJspFragment(attr.getName())) {
+                        // XXX - no need to generate temporary variable here
+                        attrValue = generateNamedAttributeJspFragment(attr
+                                .getNamedAttributeNode(), tagHandlerVar);
+                    } else {
+                        attrValue = generateNamedAttributeValue(attr
+                                .getNamedAttributeNode());
+                    }
+                } else {
+                    return null;
+                }
+            }
+
+            String localName = attr.getLocalName();
+
+            Method m = null;
+            Class<?>[] c = null;
+            if (attr.isDynamic()) {
+                c = OBJECT_CLASS;
+            } else {
+                m = handlerInfo.getSetterMethod(localName);
+                if (m == null) {
+                    err.jspError(n, "jsp.error.unable.to_find_method", attr
+                            .getName());
+                }
+                c = m.getParameterTypes();
+                // XXX assert(c.length > 0)
+            }
+
+            if (attr.isExpression()) {
+                // Do nothing
+            } else if (attr.isNamedAttribute()) {
+                if (!n.checkIfAttributeIsJspFragment(attr.getName())
+                        && !attr.isDynamic()) {
+                    attrValue = convertString(c[0], attrValue, localName,
+                            handlerInfo.getPropertyEditorClass(localName), true);
+                }
+            } else if (attr.isELInterpreterInput()) {
+
+                // results buffer
+                StringBuilder sb = new StringBuilder(64);
+
+                TagAttributeInfo tai = attr.getTagAttributeInfo();
+
+                // generate elContext reference
+                sb.append(getJspContextVar());
+                sb.append(".getELContext()");
+                String elContext = sb.toString();
+                if (attr.getEL() != null && attr.getEL().getMapName() != null) {
+                    sb.setLength(0);
+                    sb.append("new org.apache.jasper.el.ELContextWrapper(");
+                    sb.append(elContext);
+                    sb.append(',');
+                    sb.append(attr.getEL().getMapName());
+                    sb.append(')');
+                    elContext = sb.toString();
+                }
+
+                // reset buffer
+                sb.setLength(0);
+
+                // create our mark
+                sb.append(n.getStart().toString());
+                sb.append(" '");
+                sb.append(attrValue);
+                sb.append('\'');
+                String mark = sb.toString();
+
+                // reset buffer
+                sb.setLength(0);
+
+                // depending on type
+                if (attr.isDeferredInput()
+                        || ((tai != null) && ValueExpression.class.getName().equals(tai.getTypeName()))) {
+                    sb.append("new org.apache.jasper.el.JspValueExpression(");
+                    sb.append(quote(mark));
+                    sb.append(',');
+                    sb.append(getExpressionFactoryVar());
+                    sb.append(".createValueExpression(");
+                    if (attr.getEL() != null) { // optimize
+                        sb.append(elContext);
+                        sb.append(',');
+                    }
+                    sb.append(quote(attrValue));
+                    sb.append(',');
+                    sb.append(JspUtil.toJavaSourceTypeFromTld(attr.getExpectedTypeName()));
+                    sb.append("))");
+                    // should the expression be evaluated before passing to
+                    // the setter?
+                    boolean evaluate = false;
+                    if (tai != null && tai.canBeRequestTime()) {
+                        evaluate = true; // JSP.2.3.2
+                    }
+                    if (attr.isDeferredInput()) {
+                        evaluate = false; // JSP.2.3.3
+                    }
+                    if (attr.isDeferredInput() && tai != null &&
+                            tai.canBeRequestTime()) {
+                        evaluate = !attrValue.contains("#{"); // JSP.2.3.5
+                    }
+                    if (evaluate) {
+                        sb.append(".getValue(");
+                        sb.append(getJspContextVar());
+                        sb.append(".getELContext()");
+                        sb.append(")");
+                    }
+                    attrValue = sb.toString();
+                } else if (attr.isDeferredMethodInput()
+                        || ((tai != null) && MethodExpression.class.getName().equals(tai.getTypeName()))) {
+                    sb.append("new org.apache.jasper.el.JspMethodExpression(");
+                    sb.append(quote(mark));
+                    sb.append(',');
+                    sb.append(getExpressionFactoryVar());
+                    sb.append(".createMethodExpression(");
+                    sb.append(elContext);
+                    sb.append(',');
+                    sb.append(quote(attrValue));
+                    sb.append(',');
+                    sb.append(JspUtil.toJavaSourceTypeFromTld(attr.getExpectedTypeName()));
+                    sb.append(',');
+                    sb.append("new java.lang.Class[] {");
+
+                    String[] p = attr.getParameterTypeNames();
+                    for (int i = 0; i < p.length; i++) {
+                        sb.append(JspUtil.toJavaSourceTypeFromTld(p[i]));
+                        sb.append(',');
+                    }
+                    if (p.length > 0) {
+                        sb.setLength(sb.length() - 1);
+                    }
+
+                    sb.append("}))");
+                    attrValue = sb.toString();
+                } else {
+                    // run attrValue through the expression interpreter
+                    String mapName = (attr.getEL() != null) ? attr.getEL()
+                            .getMapName() : null;
+                    attrValue = JspUtil.interpreterCall(this.isTagFile, attrValue,
+                            c[0], mapName, false);
+                }
+            } else {
+                attrValue = convertString(c[0], attrValue, localName,
+                        handlerInfo.getPropertyEditorClass(localName), false);
+            }
+            return attrValue;
+        }
+
+        /**
+         * Generate code to create a map for the alias variables
+         *
+         * @return the name of the map
+         */
+        private String generateAliasMap(Node.CustomTag n,
+                String tagHandlerVar) {
+
+            TagVariableInfo[] tagVars = n.getTagVariableInfos();
+            String aliasMapVar = null;
+
+            boolean aliasSeen = false;
+            for (int i = 0; i < tagVars.length; i++) {
+
+                String nameFrom = tagVars[i].getNameFromAttribute();
+                if (nameFrom != null) {
+                    String aliasedName = n.getAttributeValue(nameFrom);
+                    if (aliasedName == null)
+                        continue;
+
+                    if (!aliasSeen) {
+                        out.printin("java.util.HashMap ");
+                        aliasMapVar = tagHandlerVar + "_aliasMap";
+                        out.print(aliasMapVar);
+                        out.println(" = new java.util.HashMap();");
+                        aliasSeen = true;
+                    }
+                    out.printin(aliasMapVar);
+                    out.print(".put(");
+                    out.print(quote(tagVars[i].getNameGiven()));
+                    out.print(", ");
+                    out.print(quote(aliasedName));
+                    out.println(");");
+                }
+            }
+            return aliasMapVar;
+        }
+
+        private void generateSetters(Node.CustomTag n, String tagHandlerVar,
+                TagHandlerInfo handlerInfo, boolean simpleTag)
+                throws JasperException {
+
+            // Set context
+            if (simpleTag) {
+                // Generate alias map
+                String aliasMapVar = null;
+                if (n.isTagFile()) {
+                    aliasMapVar = generateAliasMap(n, tagHandlerVar);
+                }
+                out.printin(tagHandlerVar);
+                if (aliasMapVar == null) {
+                    out.println(".setJspContext(_jspx_page_context);");
+                } else {
+                    out.print(".setJspContext(_jspx_page_context, ");
+                    out.print(aliasMapVar);
+                    out.println(");");
+                }
+            } else {
+                out.printin(tagHandlerVar);
+                out.println(".setPageContext(_jspx_page_context);");
+            }
+
+            // Set parent
+            if (isTagFile && parent == null) {
+                out.printin(tagHandlerVar);
+                out.print(".setParent(");
+                out.print("new javax.servlet.jsp.tagext.TagAdapter(");
+                out.print("(javax.servlet.jsp.tagext.SimpleTag) this ));");
+            } else if (!simpleTag) {
+                out.printin(tagHandlerVar);
+                out.print(".setParent(");
+                if (parent != null) {
+                    if (isSimpleTagParent) {
+                        out.print("new javax.servlet.jsp.tagext.TagAdapter(");
+                        out.print("(javax.servlet.jsp.tagext.SimpleTag) ");
+                        out.print(parent);
+                        out.println("));");
+                    } else {
+                        out.print("(javax.servlet.jsp.tagext.Tag) ");
+                        out.print(parent);
+                        out.println(");");
+                    }
+                } else {
+                    out.println("null);");
+                }
+            } else {
+                // The setParent() method need not be called if the value being
+                // passed is null, since SimpleTag instances are not reused
+                if (parent != null) {
+                    out.printin(tagHandlerVar);
+                    out.print(".setParent(");
+                    out.print(parent);
+                    out.println(");");
+                }
+            }
+
+            // need to handle deferred values and methods
+            Node.JspAttribute[] attrs = n.getJspAttributes();
+            for (int i = 0; attrs != null && i < attrs.length; i++) {
+                String attrValue = evaluateAttribute(handlerInfo, attrs[i], n,
+                        tagHandlerVar);
+
+                Mark m = n.getStart();
+                out.printil("// "+m.getFile()+"("+m.getLineNumber()+","+m.getColumnNumber()+") "+ attrs[i].getTagAttributeInfo());
+                if (attrs[i].isDynamic()) {
+                    out.printin(tagHandlerVar);
+                    out.print(".");
+                    out.print("setDynamicAttribute(");
+                    String uri = attrs[i].getURI();
+                    if ("".equals(uri) || (uri == null)) {
+                        out.print("null");
+                    } else {
+                        out.print("\"" + attrs[i].getURI() + "\"");
+                    }
+                    out.print(", \"");
+                    out.print(attrs[i].getLocalName());
+                    out.print("\", ");
+                    out.print(attrValue);
+                    out.println(");");
+                } else {
+                    out.printin(tagHandlerVar);
+                    out.print(".");
+                    out.print(handlerInfo.getSetterMethod(
+                            attrs[i].getLocalName()).getName());
+                    out.print("(");
+                    out.print(attrValue);
+                    out.println(");");
+                }
+            }
+        }
+
+        /*
+         * @param c The target class to which to coerce the given string @param
+         * s The string value @param attrName The name of the attribute whose
+         * value is being supplied @param propEditorClass The property editor
+         * for the given attribute @param isNamedAttribute true if the given
+         * attribute is a named attribute (that is, specified using the
+         * jsp:attribute standard action), and false otherwise
+         */
+        private String convertString(Class<?> c, String s, String attrName,
+                Class<?> propEditorClass, boolean isNamedAttribute) {
+
+            String quoted = s;
+            if (!isNamedAttribute) {
+                quoted = quote(s);
+            }
+
+            if (propEditorClass != null) {
+                String className = c.getCanonicalName();
+                return "("
+                        + className
+                        + ")org.apache.jasper.runtime.JspRuntimeLibrary.getValueFromBeanInfoPropertyEditor("
+                        + className + ".class, \"" + attrName + "\", " + quoted
+                        + ", " + propEditorClass.getCanonicalName() + ".class)";
+            } else if (c == String.class) {
+                return quoted;
+            } else if (c == boolean.class) {
+                return JspUtil.coerceToPrimitiveBoolean(s, isNamedAttribute);
+            } else if (c == Boolean.class) {
+                return JspUtil.coerceToBoolean(s, isNamedAttribute);
+            } else if (c == byte.class) {
+                return JspUtil.coerceToPrimitiveByte(s, isNamedAttribute);
+            } else if (c == Byte.class) {
+                return JspUtil.coerceToByte(s, isNamedAttribute);
+            } else if (c == char.class) {
+                return JspUtil.coerceToChar(s, isNamedAttribute);
+            } else if (c == Character.class) {
+                return JspUtil.coerceToCharacter(s, isNamedAttribute);
+            } else if (c == double.class) {
+                return JspUtil.coerceToPrimitiveDouble(s, isNamedAttribute);
+            } else if (c == Double.class) {
+                return JspUtil.coerceToDouble(s, isNamedAttribute);
+            } else if (c == float.class) {
+                return JspUtil.coerceToPrimitiveFloat(s, isNamedAttribute);
+            } else if (c == Float.class) {
+                return JspUtil.coerceToFloat(s, isNamedAttribute);
+            } else if (c == int.class) {
+                return JspUtil.coerceToInt(s, isNamedAttribute);
+            } else if (c == Integer.class) {
+                return JspUtil.coerceToInteger(s, isNamedAttribute);
+            } else if (c == short.class) {
+                return JspUtil.coerceToPrimitiveShort(s, isNamedAttribute);
+            } else if (c == Short.class) {
+                return JspUtil.coerceToShort(s, isNamedAttribute);
+            } else if (c == long.class) {
+                return JspUtil.coerceToPrimitiveLong(s, isNamedAttribute);
+            } else if (c == Long.class) {
+                return JspUtil.coerceToLong(s, isNamedAttribute);
+            } else if (c == Object.class) {
+                return "new String(" + quoted + ")";
+            } else {
+                String className = c.getCanonicalName();
+                return "("
+                        + className
+                        + ")org.apache.jasper.runtime.JspRuntimeLibrary.getValueFromPropertyEditorManager("
+                        + className + ".class, \"" + attrName + "\", " + quoted
+                        + ")";
+            }
+        }
+
+        /*
+         * Converts the scope string representation, whose possible values are
+         * "page", "request", "session", and "application", to the corresponding
+         * scope constant.
+         */
+        private String getScopeConstant(String scope) {
+            String scopeName = "javax.servlet.jsp.PageContext.PAGE_SCOPE"; // Default to page
+
+            if ("request".equals(scope)) {
+                scopeName = "javax.servlet.jsp.PageContext.REQUEST_SCOPE";
+            } else if ("session".equals(scope)) {
+                scopeName = "javax.servlet.jsp.PageContext.SESSION_SCOPE";
+            } else if ("application".equals(scope)) {
+                scopeName = "javax.servlet.jsp.PageContext.APPLICATION_SCOPE";
+            }
+
+            return scopeName;
+        }
+
+        /**
+         * Generates anonymous JspFragment inner class which is passed as an
+         * argument to SimpleTag.setJspBody().
+         */
+        private void generateJspFragment(Node n, String tagHandlerVar)
+                throws JasperException {
+            // XXX - A possible optimization here would be to check to see
+            // if the only child of the parent node is TemplateText. If so,
+            // we know there won't be any parameters, etc, so we can
+            // generate a low-overhead JspFragment that just echoes its
+            // body. The implementation of this fragment can come from
+            // the org.apache.jasper.runtime package as a support class.
+            FragmentHelperClass.Fragment fragment = fragmentHelperClass
+                    .openFragment(n, methodNesting);
+            ServletWriter outSave = out;
+            out = fragment.getGenBuffer().getOut();
+            String tmpParent = parent;
+            parent = "_jspx_parent";
+            boolean isSimpleTagParentSave = isSimpleTagParent;
+            isSimpleTagParent = true;
+            boolean tmpIsFragment = isFragment;
+            isFragment = true;
+            String pushBodyCountVarSave = pushBodyCountVar;
+            if (pushBodyCountVar != null) {
+                // Use a fixed name for push body count, to simplify code gen
+                pushBodyCountVar = "_jspx_push_body_count";
+            }
+            visitBody(n);
+            out = outSave;
+            parent = tmpParent;
+            isSimpleTagParent = isSimpleTagParentSave;
+            isFragment = tmpIsFragment;
+            pushBodyCountVar = pushBodyCountVarSave;
+            fragmentHelperClass.closeFragment(fragment, methodNesting);
+            // XXX - Need to change pageContext to jspContext if
+            // we're not in a place where pageContext is defined (e.g.
+            // in a fragment or in a tag file.
+            out.print("new " + fragmentHelperClass.getClassName() + "( "
+                    + fragment.getId() + ", _jspx_page_context, "
+                    + tagHandlerVar + ", " + pushBodyCountVar + ")");
+        }
+
+        /**
+         * Generate the code required to obtain the runtime value of the given
+         * named attribute.
+         *
+         * @return The name of the temporary variable the result is stored in.
+         */
+        public String generateNamedAttributeValue(Node.NamedAttribute n)
+                throws JasperException {
+
+            String varName = n.getTemporaryVariableName();
+
+            // If the only body element for this named attribute node is
+            // template text, we need not generate an extra call to
+            // pushBody and popBody. Maybe we can further optimize
+            // here by getting rid of the temporary variable, but in
+            // reality it looks like javac does this for us.
+            Node.Nodes body = n.getBody();
+            if (body != null) {
+                boolean templateTextOptimization = false;
+                if (body.size() == 1) {
+                    Node bodyElement = body.getNode(0);
+                    if (bodyElement instanceof Node.TemplateText) {
+                        templateTextOptimization = true;
+                        out.printil("java.lang.String "
+                                + varName
+                                + " = "
+                                + quote(((Node.TemplateText) bodyElement)
+                                                .getText()) + ";");
+                    }
+                }
+
+                // XXX - Another possible optimization would be for
+                // lone EL expressions (no need to pushBody here either).
+
+                if (!templateTextOptimization) {
+                    out.printil("out = _jspx_page_context.pushBody();");
+                    visitBody(n);
+                    out.printil("java.lang.String " + varName + " = "
+                            + "((javax.servlet.jsp.tagext.BodyContent)"
+                            + "out).getString();");
+                    out.printil("out = _jspx_page_context.popBody();");
+                }
+            } else {
+                // Empty body must be treated as ""
+                out.printil("java.lang.String " + varName + " = \"\";");
+            }
+
+            return varName;
+        }
+
+        /**
+         * Similar to generateNamedAttributeValue, but create a JspFragment
+         * instead.
+         *
+         * @param n
+         *            The parent node of the named attribute
+         * @param tagHandlerVar
+         *            The variable the tag handler is stored in, so the fragment
+         *            knows its parent tag.
+         * @return The name of the temporary variable the fragment is stored in.
+         */
+        public String generateNamedAttributeJspFragment(Node.NamedAttribute n,
+                String tagHandlerVar) throws JasperException {
+            String varName = n.getTemporaryVariableName();
+
+            out.printin("javax.servlet.jsp.tagext.JspFragment " + varName
+                    + " = ");
+            generateJspFragment(n, tagHandlerVar);
+            out.println(";");
+
+            return varName;
+        }
+    }
+
+    private static void generateLocalVariables(ServletWriter out, Node n)
+            throws JasperException {
+        Node.ChildInfo ci;
+        if (n instanceof Node.CustomTag) {
+            ci = ((Node.CustomTag) n).getChildInfo();
+        } else if (n instanceof Node.JspBody) {
+            ci = ((Node.JspBody) n).getChildInfo();
+        } else if (n instanceof Node.NamedAttribute) {
+            ci = ((Node.NamedAttribute) n).getChildInfo();
+        } else {
+            // Cannot access err since this method is static, but at
+            // least flag an error.
+            throw new JasperException("Unexpected Node Type");
+            // err.getString(
+            // "jsp.error.internal.unexpected_node_type" ) );
+        }
+
+        if (ci.hasUseBean()) {
+            out.printil("javax.servlet.http.HttpSession session = _jspx_page_context.getSession();");
+            out.printil("javax.servlet.ServletContext application = _jspx_page_context.getServletContext();");
+        }
+        if (ci.hasUseBean() || ci.hasIncludeAction() || ci.hasSetProperty()
+                || ci.hasParamAction()) {
+            out.printil("javax.servlet.http.HttpServletRequest request = (javax.servlet.http.HttpServletRequest)_jspx_page_context.getRequest();");
+        }
+        if (ci.hasIncludeAction()) {
+            out.printil("javax.servlet.http.HttpServletResponse response = (javax.servlet.http.HttpServletResponse)_jspx_page_context.getResponse();");
+        }
+    }
+
+    /**
+     * Common part of postamble, shared by both servlets and tag files.
+     */
+    private void genCommonPostamble() {
+        // Append any methods that were generated in the buffer.
+        for (int i = 0; i < methodsBuffered.size(); i++) {
+            GenBuffer methodBuffer = methodsBuffered.get(i);
+            methodBuffer.adjustJavaLines(out.getJavaLine() - 1);
+            out.printMultiLn(methodBuffer.toString());
+        }
+
+        // Append the helper class
+        if (fragmentHelperClass.isUsed()) {
+            fragmentHelperClass.generatePostamble();
+            fragmentHelperClass.adjustJavaLines(out.getJavaLine() - 1);
+            out.printMultiLn(fragmentHelperClass.toString());
+        }
+
+        // Append char array declarations
+        if (charArrayBuffer != null) {
+            out.printMultiLn(charArrayBuffer.toString());
+        }
+
+        // Close the class definition
+        out.popIndent();
+        out.printil("}");
+    }
+
+    /**
+     * Generates the ending part of the static portion of the servlet.
+     */
+    private void generatePostamble() {
+        out.popIndent();
+        out.printil("} catch (java.lang.Throwable t) {");
+        out.pushIndent();
+        out.printil("if (!(t instanceof javax.servlet.jsp.SkipPageException)){");
+        out.pushIndent();
+        out.printil("out = _jspx_out;");
+        out.printil("if (out != null && out.getBufferSize() != 0)");
+        out.pushIndent();
+        out.printil("try { out.clearBuffer(); } catch (java.io.IOException e) {}");
+        out.popIndent();
+        out.printil("if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);");
+        out.popIndent();
+        out.printil("}");
+        out.popIndent();
+        out.printil("} finally {");
+        out.pushIndent();
+        out.printil("_jspxFactory.releasePageContext(_jspx_page_context);");
+        out.popIndent();
+        out.printil("}");
+
+        // Close the service method
+        out.popIndent();
+        out.printil("}");
+
+        // Generated methods, helper classes, etc.
+        genCommonPostamble();
+    }
+
+    /**
+     * Constructor.
+     */
+    Generator(ServletWriter out, Compiler compiler) {
+        this.out = out;
+        methodsBuffered = new ArrayList<GenBuffer>();
+        charArrayBuffer = null;
+        err = compiler.getErrorDispatcher();
+        ctxt = compiler.getCompilationContext();
+        fragmentHelperClass = new FragmentHelperClass("Helper");
+        pageInfo = compiler.getPageInfo();
+
+        /*
+         * Temporary hack. If a JSP page uses the "extends" attribute of the
+         * page directive, the _jspInit() method of the generated servlet class
+         * will not be called (it is only called for those generated servlets
+         * that extend HttpJspBase, the default), causing the tag handler pools
+         * not to be initialized and resulting in a NPE. The JSP spec needs to
+         * clarify whether containers can override init() and destroy(). For
+         * now, we just disable tag pooling for pages that use "extends".
+         */
+        if (pageInfo.getExtends(false) == null) {
+            isPoolingEnabled = ctxt.getOptions().isPoolingEnabled();
+        } else {
+            isPoolingEnabled = false;
+        }
+        beanInfo = pageInfo.getBeanRepository();
+        varInfoNames = pageInfo.getVarInfoNames();
+        breakAtLF = ctxt.getOptions().getMappedFile();
+        if (isPoolingEnabled) {
+            tagHandlerPoolNames = new Vector<String>();
+        }
+    }
+
+    /**
+     * The main entry for Generator.
+     *
+     * @param out
+     *            The servlet output writer
+     * @param compiler
+     *            The compiler
+     * @param page
+     *            The input page
+     */
+    public static void generate(ServletWriter out, Compiler compiler,
+            Node.Nodes page) throws JasperException {
+
+        Generator gen = new Generator(out, compiler);
+
+        if (gen.isPoolingEnabled) {
+            gen.compileTagHandlerPoolList(page);
+        }
+        if (gen.ctxt.isTagFile()) {
+            JasperTagInfo tagInfo = (JasperTagInfo) gen.ctxt.getTagInfo();
+            gen.generateTagHandlerPreamble(tagInfo, page);
+
+            if (gen.ctxt.isPrototypeMode()) {
+                return;
+            }
+
+            gen.generateXmlProlog(page);
+            gen.fragmentHelperClass.generatePreamble();
+            page.visit(gen.new GenerateVisitor(gen.ctxt.isTagFile(), out,
+                    gen.methodsBuffered, gen.fragmentHelperClass));
+            gen.generateTagHandlerPostamble(tagInfo);
+        } else {
+            gen.generatePreamble(page);
+            gen.generateXmlProlog(page);
+            gen.fragmentHelperClass.generatePreamble();
+            page.visit(gen.new GenerateVisitor(gen.ctxt.isTagFile(), out,
+                    gen.methodsBuffered, gen.fragmentHelperClass));
+            gen.generatePostamble();
+        }
+    }
+
+    /*
+     * Generates tag handler preamble.
+     */
+    private void generateTagHandlerPreamble(JasperTagInfo tagInfo,
+            Node.Nodes tag) throws JasperException {
+
+        // Generate package declaration
+        String className = tagInfo.getTagClassName();
+        int lastIndex = className.lastIndexOf('.');
+        if (lastIndex != -1) {
+            String pkgName = className.substring(0, lastIndex);
+            genPreamblePackage(pkgName);
+            className = className.substring(lastIndex + 1);
+        }
+
+        // Generate imports
+        genPreambleImports();
+
+        // Generate class declaration
+        out.printin("public final class ");
+        out.println(className);
+        out.printil("    extends javax.servlet.jsp.tagext.SimpleTagSupport");
+        out.printin("    implements org.apache.jasper.runtime.JspSourceDependent");
+        if (tagInfo.hasDynamicAttributes()) {
+            out.println(",");
+            out.printin("               javax.servlet.jsp.tagext.DynamicAttributes");
+        }
+        out.println(" {");
+        out.println();
+        out.pushIndent();
+
+        /*
+         * Class body begins here
+         */
+        generateDeclarations(tag);
+
+        // Static initializations here
+        genPreambleStaticInitializers();
+
+        out.printil("private javax.servlet.jsp.JspContext jspContext;");
+
+        // Declare writer used for storing result of fragment/body invocation
+        // if 'varReader' or 'var' attribute is specified
+        out.printil("private java.io.Writer _jspx_sout;");
+
+        // Class variable declarations
+        genPreambleClassVariableDeclarations();
+
+        generateSetJspContext(tagInfo);
+
+        // Tag-handler specific declarations
+        generateTagHandlerAttributes(tagInfo);
+        if (tagInfo.hasDynamicAttributes())
+            generateSetDynamicAttribute();
+
+        // Methods here
+        genPreambleMethods();
+
+        // Now the doTag() method
+        out.printil("public void doTag() throws javax.servlet.jsp.JspException, java.io.IOException {");
+
+        if (ctxt.isPrototypeMode()) {
+            out.printil("}");
+            out.popIndent();
+            out.printil("}");
+            return;
+        }
+
+        out.pushIndent();
+
+        /*
+         * According to the spec, 'pageContext' must not be made available as an
+         * implicit object in tag files. Declare _jspx_page_context, so we can
+         * share the code generator with JSPs.
+         */
+        out.printil("javax.servlet.jsp.PageContext _jspx_page_context = (javax.servlet.jsp.PageContext)jspContext;");
+
+        // Declare implicit objects.
+        out.printil("javax.servlet.http.HttpServletRequest request = "
+                + "(javax.servlet.http.HttpServletRequest) _jspx_page_context.getRequest();");
+        out.printil("javax.servlet.http.HttpServletResponse response = "
+                + "(javax.servlet.http.HttpServletResponse) _jspx_page_context.getResponse();");
+        out.printil("javax.servlet.http.HttpSession session = _jspx_page_context.getSession();");
+        out.printil("javax.servlet.ServletContext application = _jspx_page_context.getServletContext();");
+        out.printil("javax.servlet.ServletConfig config = _jspx_page_context.getServletConfig();");
+        out.printil("javax.servlet.jsp.JspWriter out = jspContext.getOut();");
+        out.printil("_jspInit(config);");
+
+        // set current JspContext on ELContext
+        out.printil("jspContext.getELContext().putContext(javax.servlet.jsp.JspContext.class,jspContext);");
+
+        generatePageScopedVariables(tagInfo);
+
+        declareTemporaryScriptingVars(tag);
+        out.println();
+
+        out.printil("try {");
+        out.pushIndent();
+    }
+
+    private void generateTagHandlerPostamble(TagInfo tagInfo) {
+        out.popIndent();
+
+        // Have to catch Throwable because a classic tag handler
+        // helper method is declared to throw Throwable.
+        out.printil("} catch( java.lang.Throwable t ) {");
+        out.pushIndent();
+        out.printil("if( t instanceof javax.servlet.jsp.SkipPageException )");
+        out.printil("    throw (javax.servlet.jsp.SkipPageException) t;");
+        out.printil("if( t instanceof java.io.IOException )");
+        out.printil("    throw (java.io.IOException) t;");
+        out.printil("if( t instanceof java.lang.IllegalStateException )");
+        out.printil("    throw (java.lang.IllegalStateException) t;");
+        out.printil("if( t instanceof javax.servlet.jsp.JspException )");
+        out.printil("    throw (javax.servlet.jsp.JspException) t;");
+        out.printil("throw new javax.servlet.jsp.JspException(t);");
+        out.popIndent();
+        out.printil("} finally {");
+        out.pushIndent();
+
+        // handle restoring VariableMapper
+        TagAttributeInfo[] attrInfos = tagInfo.getAttributes();
+        for (int i = 0; i < attrInfos.length; i++) {
+            if (attrInfos[i].isDeferredMethod() || attrInfos[i].isDeferredValue()) {
+                out.printin("_el_variablemapper.setVariable(");
+                out.print(quote(attrInfos[i].getName()));
+                out.print(",_el_ve");
+                out.print(i);
+                out.println(");");
+            }
+        }
+
+        // restore nested JspContext on ELContext
+        out.printil("jspContext.getELContext().putContext(javax.servlet.jsp.JspContext.class,super.getJspContext());");
+
+        out.printil("((org.apache.jasper.runtime.JspContextWrapper) jspContext).syncEndTagFile();");
+        if (isPoolingEnabled && !tagHandlerPoolNames.isEmpty()) {
+            out.printil("_jspDestroy();");
+        }
+        out.popIndent();
+        out.printil("}");
+
+        // Close the doTag method
+        out.popIndent();
+        out.printil("}");
+
+        // Generated methods, helper classes, etc.
+        genCommonPostamble();
+    }
+
+    /**
+     * Generates declarations for tag handler attributes, and defines the getter
+     * and setter methods for each.
+     */
+    private void generateTagHandlerAttributes(TagInfo tagInfo) {
+
+        if (tagInfo.hasDynamicAttributes()) {
+            out.printil("private java.util.HashMap _jspx_dynamic_attrs = new java.util.HashMap();");
+        }
+
+        // Declare attributes
+        TagAttributeInfo[] attrInfos = tagInfo.getAttributes();
+        for (int i = 0; i < attrInfos.length; i++) {
+            out.printin("private ");
+            if (attrInfos[i].isFragment()) {
+                out.print("javax.servlet.jsp.tagext.JspFragment ");
+            } else {
+                out.print(JspUtil.toJavaSourceType(attrInfos[i].getTypeName()));
+                out.print(" ");
+            }
+            out.print(attrInfos[i].getName());
+            out.println(";");
+        }
+        out.println();
+
+        // Define attribute getter and setter methods
+        for (int i = 0; i < attrInfos.length; i++) {
+            // getter method
+            out.printin("public ");
+            if (attrInfos[i].isFragment()) {
+                out.print("javax.servlet.jsp.tagext.JspFragment ");
+            } else {
+                out.print(JspUtil.toJavaSourceType(attrInfos[i].getTypeName()));
+                out.print(" ");
+            }
+            out.print(toGetterMethod(attrInfos[i].getName()));
+            out.println(" {");
+            out.pushIndent();
+            out.printin("return this.");
+            out.print(attrInfos[i].getName());
+            out.println(";");
+            out.popIndent();
+            out.printil("}");
+            out.println();
+
+            // setter method
+            out.printin("public void ");
+            out.print(toSetterMethodName(attrInfos[i].getName()));
+            if (attrInfos[i].isFragment()) {
+                out.print("(javax.servlet.jsp.tagext.JspFragment ");
+            } else {
+                out.print("(");
+                out.print(JspUtil.toJavaSourceType(attrInfos[i].getTypeName()));
+                out.print(" ");
+            }
+            out.print(attrInfos[i].getName());
+            out.println(") {");
+            out.pushIndent();
+            out.printin("this.");
+            out.print(attrInfos[i].getName());
+            out.print(" = ");
+            out.print(attrInfos[i].getName());
+            out.println(";");
+            if (ctxt.isTagFile()) {
+                // Tag files should also set jspContext attributes
+                out.printin("jspContext.setAttribute(\"");
+                out.print(attrInfos[i].getName());
+                out.print("\", ");
+                out.print(attrInfos[i].getName());
+                out.println(");");
+            }
+            out.popIndent();
+            out.printil("}");
+            out.println();
+        }
+    }
+
+    /*
+     * Generate setter for JspContext so we can create a wrapper and store both
+     * the original and the wrapper. We need the wrapper to mask the page
+     * context from the tag file and simulate a fresh page context. We need the
+     * original to do things like sync AT_BEGIN and AT_END scripting variables.
+     */
+    private void generateSetJspContext(TagInfo tagInfo) {
+
+        boolean nestedSeen = false;
+        boolean atBeginSeen = false;
+        boolean atEndSeen = false;
+
+        // Determine if there are any aliases
+        boolean aliasSeen = false;
+        TagVariableInfo[] tagVars = tagInfo.getTagVariableInfos();
+        for (int i = 0; i < tagVars.length; i++) {
+            if (tagVars[i].getNameFromAttribute() != null
+                    && tagVars[i].getNameGiven() != null) {
+                aliasSeen = true;
+                break;
+            }
+        }
+
+        if (aliasSeen) {
+            out.printil("public void setJspContext(javax.servlet.jsp.JspContext ctx, java.util.Map aliasMap) {");
+        } else {
+            out.printil("public void setJspContext(javax.servlet.jsp.JspContext ctx) {");
+        }
+        out.pushIndent();
+        out.printil("super.setJspContext(ctx);");
+        out.printil("java.util.ArrayList _jspx_nested = null;");
+        out.printil("java.util.ArrayList _jspx_at_begin = null;");
+        out.printil("java.util.ArrayList _jspx_at_end = null;");
+
+        for (int i = 0; i < tagVars.length; i++) {
+
+            switch (tagVars[i].getScope()) {
+            case VariableInfo.NESTED:
+                if (!nestedSeen) {
+                    out.printil("_jspx_nested = new java.util.ArrayList();");
+                    nestedSeen = true;
+                }
+                out.printin("_jspx_nested.add(");
+                break;
+
+            case VariableInfo.AT_BEGIN:
+                if (!atBeginSeen) {
+                    out.printil("_jspx_at_begin = new java.util.ArrayList();");
+                    atBeginSeen = true;
+                }
+                out.printin("_jspx_at_begin.add(");
+                break;
+
+            case VariableInfo.AT_END:
+                if (!atEndSeen) {
+                    out.printil("_jspx_at_end = new java.util.ArrayList();");
+                    atEndSeen = true;
+                }
+                out.printin("_jspx_at_end.add(");
+                break;
+            } // switch
+
+            out.print(quote(tagVars[i].getNameGiven()));
+            out.println(");");
+        }
+        if (aliasSeen) {
+            out.printil("this.jspContext = new org.apache.jasper.runtime.JspContextWrapper(ctx, _jspx_nested, _jspx_at_begin, _jspx_at_end, aliasMap);");
+        } else {
+            out.printil("this.jspContext = new org.apache.jasper.runtime.JspContextWrapper(ctx, _jspx_nested, _jspx_at_begin, _jspx_at_end, null);");
+        }
+        out.popIndent();
+        out.printil("}");
+        out.println();
+        out.printil("public javax.servlet.jsp.JspContext getJspContext() {");
+        out.pushIndent();
+        out.printil("return this.jspContext;");
+        out.popIndent();
+        out.printil("}");
+    }
+
+    /*
+     * Generates implementation of
+     * javax.servlet.jsp.tagext.DynamicAttributes.setDynamicAttribute() method,
+     * which saves each dynamic attribute that is passed in so that a scoped
+     * variable can later be created for it.
+     */
+    public void generateSetDynamicAttribute() {
+        out.printil("public void setDynamicAttribute(java.lang.String uri, java.lang.String localName, java.lang.Object value) throws javax.servlet.jsp.JspException {");
+        out.pushIndent();
+        /*
+         * According to the spec, only dynamic attributes with no uri are to be
+         * present in the Map; all other dynamic attributes are ignored.
+         */
+        out.printil("if (uri == null)");
+        out.pushIndent();
+        out.printil("_jspx_dynamic_attrs.put(localName, value);");
+        out.popIndent();
+        out.popIndent();
+        out.printil("}");
+    }
+
+    /*
+     * Creates a page-scoped variable for each declared tag attribute. Also, if
+     * the tag accepts dynamic attributes, a page-scoped variable is made
+     * available for each dynamic attribute that was passed in.
+     */
+    private void generatePageScopedVariables(JasperTagInfo tagInfo) {
+
+        // "normal" attributes
+        TagAttributeInfo[] attrInfos = tagInfo.getAttributes();
+        boolean variableMapperVar = false;
+        for (int i = 0; i < attrInfos.length; i++) {
+            String attrName = attrInfos[i].getName();
+
+            // handle assigning deferred vars to VariableMapper, storing
+            // previous values under '_el_ve[i]' for later re-assignment
+            if (attrInfos[i].isDeferredValue() || attrInfos[i].isDeferredMethod()) {
+
+                // we need to scope the modified VariableMapper for consistency and performance
+                if (!variableMapperVar) {
+                    out.printil("javax.el.VariableMapper _el_variablemapper = jspContext.getELContext().getVariableMapper();");
+                    variableMapperVar = true;
+                }
+
+                out.printin("javax.el.ValueExpression _el_ve");
+                out.print(i);
+                out.print(" = _el_variablemapper.setVariable(");
+                out.print(quote(attrName));
+                out.print(',');
+                if (attrInfos[i].isDeferredMethod()) {
+                    out.print(VAR_EXPRESSIONFACTORY);
+                    out.print(".createValueExpression(");
+                    out.print(toGetterMethod(attrName));
+                    out.print(",javax.el.MethodExpression.class)");
+                } else {
+                    out.print(toGetterMethod(attrName));
+                }
+                out.println(");");
+            } else {
+                out.printil("if( " + toGetterMethod(attrName) + " != null ) ");
+                out.pushIndent();
+                out.printin("_jspx_page_context.setAttribute(");
+                out.print(quote(attrName));
+                out.print(", ");
+                out.print(toGetterMethod(attrName));
+                out.println(");");
+                out.popIndent();
+            }
+        }
+
+        // Expose the Map containing dynamic attributes as a page-scoped var
+        if (tagInfo.hasDynamicAttributes()) {
+            out.printin("_jspx_page_context.setAttribute(\"");
+            out.print(tagInfo.getDynamicAttributesMapName());
+            out.print("\", _jspx_dynamic_attrs);");
+        }
+    }
+
+    /*
+     * Generates the getter method for the given attribute name.
+     */
+    private String toGetterMethod(String attrName) {
+        char[] attrChars = attrName.toCharArray();
+        attrChars[0] = Character.toUpperCase(attrChars[0]);
+        return "get" + new String(attrChars) + "()";
+    }
+
+    /*
+     * Generates the setter method name for the given attribute name.
+     */
+    private String toSetterMethodName(String attrName) {
+        char[] attrChars = attrName.toCharArray();
+        attrChars[0] = Character.toUpperCase(attrChars[0]);
+        return "set" + new String(attrChars);
+    }
+
+    /**
+     * Class storing the result of introspecting a custom tag handler.
+     */
+    private static class TagHandlerInfo {
+
+        private Hashtable<String, Method> methodMaps;
+
+        private Hashtable<String, Class<?>> propertyEditorMaps;
+
+        private Class<?> tagHandlerClass;
+
+        /**
+         * Constructor.
+         *
+         * @param n
+         *            The custom tag whose tag handler class is to be
+         *            introspected
+         * @param tagHandlerClass
+         *            Tag handler class
+         * @param err
+         *            Error dispatcher
+         */
+        TagHandlerInfo(Node n, Class<?> tagHandlerClass,
+                ErrorDispatcher err) throws JasperException {
+            this.tagHandlerClass = tagHandlerClass;
+            this.methodMaps = new Hashtable<String, Method>();
+            this.propertyEditorMaps = new Hashtable<String, Class<?>>();
+
+            try {
+                BeanInfo tagClassInfo = Introspector
+                        .getBeanInfo(tagHandlerClass);
+                PropertyDescriptor[] pd = tagClassInfo.getPropertyDescriptors();
+                for (int i = 0; i < pd.length; i++) {
+                    /*
+                     * FIXME: should probably be checking for things like
+                     * pageContext, bodyContent, and parent here -akv
+                     */
+                    if (pd[i].getWriteMethod() != null) {
+                        methodMaps.put(pd[i].getName(), pd[i].getWriteMethod());
+                    }
+                    if (pd[i].getPropertyEditorClass() != null)
+                        propertyEditorMaps.put(pd[i].getName(), pd[i]
+                                .getPropertyEditorClass());
+                }
+            } catch (IntrospectionException ie) {
+                err.jspError(n, "jsp.error.introspect.taghandler",
+                        tagHandlerClass.getName(), ie);
+            }
+        }
+
+        /**
+         * XXX
+         */
+        public Method getSetterMethod(String attrName) {
+            return methodMaps.get(attrName);
+        }
+
+        /**
+         * XXX
+         */
+        public Class<?> getPropertyEditorClass(String attrName) {
+            return propertyEditorMaps.get(attrName);
+        }
+
+        /**
+         * XXX
+         */
+        public Class<?> getTagHandlerClass() {
+            return tagHandlerClass;
+        }
+    }
+
+    /**
+     * A class for generating codes to a buffer. Included here are some support
+     * for tracking source to Java lines mapping.
+     */
+    private static class GenBuffer {
+
+        /*
+         * For a CustomTag, the codes that are generated at the beginning of the
+         * tag may not be in the same buffer as those for the body of the tag.
+         * Two fields are used here to keep this straight. For codes that do not
+         * corresponds to any JSP lines, they should be null.
+         */
+        private Node node;
+
+        private Node.Nodes body;
+
+        private java.io.CharArrayWriter charWriter;
+
+        protected ServletWriter out;
+
+        GenBuffer() {
+            this(null, null);
+        }
+
+        GenBuffer(Node n, Node.Nodes b) {
+            node = n;
+            body = b;
+            if (body != null) {
+                body.setGeneratedInBuffer(true);
+            }
+            charWriter = new java.io.CharArrayWriter();
+            out = new ServletWriter(new java.io.PrintWriter(charWriter));
+        }
+
+        public ServletWriter getOut() {
+            return out;
+        }
+
+        @Override
+        public String toString() {
+            return charWriter.toString();
+        }
+
+        /**
+         * Adjust the Java Lines. This is necessary because the Java lines
+         * stored with the nodes are relative the beginning of this buffer and
+         * need to be adjusted when this buffer is inserted into the source.
+         */
+        public void adjustJavaLines(final int offset) {
+
+            if (node != null) {
+                adjustJavaLine(node, offset);
+            }
+
+            if (body != null) {
+                try {
+                    body.visit(new Node.Visitor() {
+
+                        @Override
+                        public void doVisit(Node n) {
+                            adjustJavaLine(n, offset);
+                        }
+
+                        @Override
+                        public void visit(Node.CustomTag n)
+                                throws JasperException {
+                            Node.Nodes b = n.getBody();
+                            if (b != null && !b.isGeneratedInBuffer()) {
+                                // Don't adjust lines for the nested tags that
+                                // are also generated in buffers, because the
+                                // adjustments will be done elsewhere.
+                                b.visit(this);
+                            }
+                        }
+                    });
+                } catch (JasperException ex) {
+                    // Ignore
+                }
+            }
+        }
+
+        private static void adjustJavaLine(Node n, int offset) {
+            if (n.getBeginJavaLine() > 0) {
+                n.setBeginJavaLine(n.getBeginJavaLine() + offset);
+                n.setEndJavaLine(n.getEndJavaLine() + offset);
+            }
+        }
+    }
+
+    /**
+     * Keeps track of the generated Fragment Helper Class
+     */
+    private static class FragmentHelperClass {
+
+        private static class Fragment {
+            private GenBuffer genBuffer;
+
+            private int id;
+
+            public Fragment(int id, Node node) {
+                this.id = id;
+                genBuffer = new GenBuffer(null, node.getBody());
+            }
+
+            public GenBuffer getGenBuffer() {
+                return this.genBuffer;
+            }
+
+            public int getId() {
+                return this.id;
+            }
+        }
+
+        // True if the helper class should be generated.
+        private boolean used = false;
+
+        private ArrayList<Fragment> fragments = new ArrayList<Fragment>();
+
+        private String className;
+
+        // Buffer for entire helper class
+        private GenBuffer classBuffer = new GenBuffer();
+
+        public FragmentHelperClass(String className) {
+            this.className = className;
+        }
+
+        public String getClassName() {
+            return this.className;
+        }
+
+        public boolean isUsed() {
+            return this.used;
+        }
+
+        public void generatePreamble() {
+            ServletWriter out = this.classBuffer.getOut();
+            out.println();
+            out.pushIndent();
+            // Note: cannot be static, as we need to reference things like
+            // _jspx_meth_*
+            out.printil("private class " + className);
+            out.printil("    extends "
+                    + "org.apache.jasper.runtime.JspFragmentHelper");
+            out.printil("{");
+            out.pushIndent();
+            out.printil("private javax.servlet.jsp.tagext.JspTag _jspx_parent;");
+            out.printil("private int[] _jspx_push_body_count;");
+            out.println();
+            out.printil("public " + className
+                    + "( int discriminator, javax.servlet.jsp.JspContext jspContext, "
+                    + "javax.servlet.jsp.tagext.JspTag _jspx_parent, "
+                    + "int[] _jspx_push_body_count ) {");
+            out.pushIndent();
+            out.printil("super( discriminator, jspContext, _jspx_parent );");
+            out.printil("this._jspx_parent = _jspx_parent;");
+            out.printil("this._jspx_push_body_count = _jspx_push_body_count;");
+            out.popIndent();
+            out.printil("}");
+        }
+
+        public Fragment openFragment(Node parent, int methodNesting)
+        throws JasperException {
+            Fragment result = new Fragment(fragments.size(), parent);
+            fragments.add(result);
+            this.used = true;
+            parent.setInnerClassName(className);
+
+            ServletWriter out = result.getGenBuffer().getOut();
+            out.pushIndent();
+            out.pushIndent();
+            // XXX - Returns boolean because if a tag is invoked from
+            // within this fragment, the Generator sometimes might
+            // generate code like "return true". This is ignored for now,
+            // meaning only the fragment is skipped. The JSR-152
+            // expert group is currently discussing what to do in this case.
+            // See comment in closeFragment()
+            if (methodNesting > 0) {
+                out.printin("public boolean invoke");
+            } else {
+                out.printin("public void invoke");
+            }
+            out.println(result.getId() + "( " + "javax.servlet.jsp.JspWriter out ) ");
+            out.pushIndent();
+            // Note: Throwable required because methods like _jspx_meth_*
+            // throw Throwable.
+            out.printil("throws java.lang.Throwable");
+            out.popIndent();
+            out.printil("{");
+            out.pushIndent();
+            generateLocalVariables(out, parent);
+
+            return result;
+        }
+
+        public void closeFragment(Fragment fragment, int methodNesting) {
+            ServletWriter out = fragment.getGenBuffer().getOut();
+            // XXX - See comment in openFragment()
+            if (methodNesting > 0) {
+                out.printil("return false;");
+            } else {
+                out.printil("return;");
+            }
+            out.popIndent();
+            out.printil("}");
+        }
+
+        public void generatePostamble() {
+            ServletWriter out = this.classBuffer.getOut();
+            // Generate all fragment methods:
+            for (int i = 0; i < fragments.size(); i++) {
+                Fragment fragment = fragments.get(i);
+                fragment.getGenBuffer().adjustJavaLines(out.getJavaLine() - 1);
+                out.printMultiLn(fragment.getGenBuffer().toString());
+            }
+
+            // Generate postamble:
+            out.printil("public void invoke( java.io.Writer writer )");
+            out.pushIndent();
+            out.printil("throws javax.servlet.jsp.JspException");
+            out.popIndent();
+            out.printil("{");
+            out.pushIndent();
+            out.printil("javax.servlet.jsp.JspWriter out = null;");
+            out.printil("if( writer != null ) {");
+            out.pushIndent();
+            out.printil("out = this.jspContext.pushBody(writer);");
+            out.popIndent();
+            out.printil("} else {");
+            out.pushIndent();
+            out.printil("out = this.jspContext.getOut();");
+            out.popIndent();
+            out.printil("}");
+            out.printil("try {");
+            out.pushIndent();
+            out.printil("this.jspContext.getELContext().putContext(javax.servlet.jsp.JspContext.class,this.jspContext);");
+            out.printil("switch( this.discriminator ) {");
+            out.pushIndent();
+            for (int i = 0; i < fragments.size(); i++) {
+                out.printil("case " + i + ":");
+                out.pushIndent();
+                out.printil("invoke" + i + "( out );");
+                out.printil("break;");
+                out.popIndent();
+            }
+            out.popIndent();
+            out.printil("}"); // switch
+            out.popIndent();
+            out.printil("}"); // try
+            out.printil("catch( java.lang.Throwable e ) {");
+            out.pushIndent();
+            out.printil("if (e instanceof javax.servlet.jsp.SkipPageException)");
+            out.printil("    throw (javax.servlet.jsp.SkipPageException) e;");
+            out.printil("throw new javax.servlet.jsp.JspException( e );");
+            out.popIndent();
+            out.printil("}"); // catch
+            out.printil("finally {");
+            out.pushIndent();
+
+            out.printil("if( writer != null ) {");
+            out.pushIndent();
+            out.printil("this.jspContext.popBody();");
+            out.popIndent();
+            out.printil("}");
+
+            out.popIndent();
+            out.printil("}"); // finally
+            out.popIndent();
+            out.printil("}"); // invoke method
+            out.popIndent();
+            out.printil("}"); // helper class
+            out.popIndent();
+        }
+
+        @Override
+        public String toString() {
+            return classBuffer.toString();
+        }
+
+        public void adjustJavaLines(int offset) {
+            for (int i = 0; i < fragments.size(); i++) {
+                Fragment fragment = fragments.get(i);
+                fragment.getGenBuffer().adjustJavaLines(offset);
+            }
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ImplicitTagLibraryInfo.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ImplicitTagLibraryInfo.java
new file mode 100644
index 0000000..47e1b86
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ImplicitTagLibraryInfo.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.compiler;
+
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.servlet.jsp.tagext.FunctionInfo;
+import javax.servlet.jsp.tagext.TagFileInfo;
+import javax.servlet.jsp.tagext.TagInfo;
+import javax.servlet.jsp.tagext.TagLibraryInfo;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.apache.jasper.util.ExceptionUtils;
+import org.apache.jasper.xmlparser.ParserUtils;
+import org.apache.jasper.xmlparser.TreeNode;
+
+/**
+ * Class responsible for generating an implicit tag library containing tag
+ * handlers corresponding to the tag files in "/WEB-INF/tags/" or a 
+ * subdirectory of it.
+ *
+ * @author Jan Luehe
+ */
+class ImplicitTagLibraryInfo extends TagLibraryInfo {
+
+    private static final String WEB_INF_TAGS = "/WEB-INF/tags";
+    private static final String TAG_FILE_SUFFIX = ".tag";
+    private static final String TAGX_FILE_SUFFIX = ".tagx";
+    private static final String TAGS_SHORTNAME = "tags";
+    private static final String TLIB_VERSION = "1.0";
+    private static final String JSP_VERSION = "2.0";
+    private static final String IMPLICIT_TLD = "implicit.tld";
+
+    // Maps tag names to tag file paths
+    private Hashtable<String,String> tagFileMap;
+
+    private ParserController pc;
+    private PageInfo pi;
+    private Vector<TagFileInfo> vec;
+
+    /**
+     * Constructor.
+     */
+    public ImplicitTagLibraryInfo(JspCompilationContext ctxt,
+            ParserController pc,
+            PageInfo pi,
+            String prefix,
+            String tagdir,
+            ErrorDispatcher err) throws JasperException {
+        super(prefix, null);
+        this.pc = pc;
+        this.pi = pi;
+        this.tagFileMap = new Hashtable<String,String>();
+        this.vec = new Vector<TagFileInfo>();
+
+        // Implicit tag libraries have no functions:
+        this.functions = new FunctionInfo[0];
+
+        tlibversion = TLIB_VERSION;
+        jspversion = JSP_VERSION;
+
+        if (!tagdir.startsWith(WEB_INF_TAGS)) {
+            err.jspError("jsp.error.invalid.tagdir", tagdir);
+        }
+
+        // Determine the value of the <short-name> subelement of the
+        // "imaginary" <taglib> element
+        if (tagdir.equals(WEB_INF_TAGS)
+                || tagdir.equals( WEB_INF_TAGS + "/")) {
+            shortname = TAGS_SHORTNAME;
+        } else {
+            shortname = tagdir.substring(WEB_INF_TAGS.length());
+            shortname = shortname.replace('/', '-');
+        }
+
+        // Populate mapping of tag names to tag file paths
+        Set<String> dirList = ctxt.getResourcePaths(tagdir);
+        if (dirList != null) {
+            Iterator<String> it = dirList.iterator();
+            while (it.hasNext()) {
+                String path = it.next();
+                if (path.endsWith(TAG_FILE_SUFFIX)
+                        || path.endsWith(TAGX_FILE_SUFFIX)) {
+                    /*
+                     * Use the filename of the tag file, without the .tag or
+                     * .tagx extension, respectively, as the <name> subelement
+                     * of the "imaginary" <tag-file> element
+                     */
+                    String suffix = path.endsWith(TAG_FILE_SUFFIX) ?
+                            TAG_FILE_SUFFIX : TAGX_FILE_SUFFIX; 
+                    String tagName = path.substring(path.lastIndexOf("/") + 1);
+                    tagName = tagName.substring(0,
+                            tagName.lastIndexOf(suffix));
+                    tagFileMap.put(tagName, path);
+                } else if (path.endsWith(IMPLICIT_TLD)) {
+                    InputStream in = null;
+                    try {
+                        in = ctxt.getResourceAsStream(path);
+                        if (in != null) {
+                            
+                            // Add implicit TLD to dependency list
+                            if (pi != null) {
+                                pi.addDependant(path);
+                            }
+                            
+                            ParserUtils pu = new ParserUtils();
+                            TreeNode tld = pu.parseXMLDocument(uri, in);
+
+                            if (tld.findAttribute("version") != null) {
+                                this.jspversion = tld.findAttribute("version");
+                            }
+
+                            // Process each child element of our <taglib> element
+                            Iterator<TreeNode> list = tld.findChildren();
+
+                            while (list.hasNext()) {
+                                TreeNode element = list.next();
+                                String tname = element.getName();
+
+                                if ("tlibversion".equals(tname) // JSP 1.1
+                                        || "tlib-version".equals(tname)) { // JSP 1.2
+                                    this.tlibversion = element.getBody();
+                                } else if ("jspversion".equals(tname)
+                                        || "jsp-version".equals(tname)) {
+                                    this.jspversion = element.getBody();
+                                } else if ("shortname".equals(tname) || "short-name".equals(tname)) {
+                                    // Ignore
+                                } else {
+                                    // All other elements are invalid
+                                    err.jspError("jsp.error.invalid.implicit", path);
+                                }
+                            }
+                            try {
+                                double version = Double.parseDouble(this.jspversion);
+                                if (version < 2.0) {
+                                    err.jspError("jsp.error.invalid.implicit.version", path);
+                                }
+                            } catch (NumberFormatException e) {
+                                err.jspError("jsp.error.invalid.implicit.version", path);
+                            }
+                        }
+                    } finally {
+                        if (in != null) {
+                            try {
+                                in.close();
+                            } catch (Throwable t) {
+                                ExceptionUtils.handleThrowable(t);
+                            }
+                        }
+                    }
+                }
+            }
+        }        
+        
+    }
+
+    /**
+     * Checks to see if the given tag name maps to a tag file path,
+     * and if so, parses the corresponding tag file.
+     *
+     * @return The TagFileInfo corresponding to the given tag name, or null if
+     * the given tag name is not implemented as a tag file
+     */
+    @Override
+    public TagFileInfo getTagFile(String shortName) {
+
+        TagFileInfo tagFile = super.getTagFile(shortName);
+        if (tagFile == null) {
+            String path = tagFileMap.get(shortName);
+            if (path == null) {
+                return null;
+            }
+
+            TagInfo tagInfo = null;
+            try {
+                tagInfo = TagFileProcessor.parseTagFileDirectives(pc,
+                        shortName,
+                        path,
+                        pc.getJspCompilationContext().getTagFileJarResource(path),
+                        this);
+            } catch (JasperException je) {
+                throw new RuntimeException(je.toString(), je);
+            }
+
+            tagFile = new TagFileInfo(shortName, path, tagInfo);
+            vec.addElement(tagFile);
+
+            this.tagFiles = new TagFileInfo[vec.size()];
+            vec.copyInto(this.tagFiles);
+        }
+
+        return tagFile;
+    }
+
+    @Override
+    public TagLibraryInfo[] getTagLibraryInfos() {
+        Collection<TagLibraryInfo> coll = pi.getTaglibs();
+        return coll.toArray(new TagLibraryInfo[0]);
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JarResource.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JarResource.java
new file mode 100644
index 0000000..23e262b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JarResource.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.jasper.compiler;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.jar.JarFile;
+
+public interface JarResource {
+       
+    /**     
+     * @return The JarFile for this resource. A new instance of JarFile
+     *         should be returned on each call.
+     * @throws IOException
+     */
+    JarFile getJarFile() throws IOException;
+       
+    /**     
+     * @return The URL of this resource. May or may not point 
+     *         to the actual Jar file.    
+     */
+    String getUrl();
+    
+    /**     
+     * @param name
+     * @return The URL for the entry within this resource.
+     */
+    URL getEntry(String name);
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JarScannerFactory.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JarScannerFactory.java
new file mode 100644
index 0000000..b8e824c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JarScannerFactory.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.compiler;
+
+import javax.servlet.ServletContext;
+
+import org.apache.tomcat.JarScanner;
+import org.apache.tomcat.util.scan.StandardJarScanner;
+
+/**
+ * Provide a mechanism for Jasper to obtain a reference to the JarScanner
+ * implementation.
+ */
+public class JarScannerFactory {
+
+    private JarScannerFactory() {
+        // Don't want any instances so hide the default constructor.
+    }
+
+    /**
+     * Obtain the {@link JarScanner} associated with the specified {@link
+     * ServletContext}. It is obtained via a context parameter.
+     */
+    public static JarScanner getJarScanner(ServletContext ctxt) {
+        JarScanner jarScanner = 
+            (JarScanner) ctxt.getAttribute(JarScanner.class.getName());
+        if (jarScanner == null) {
+            ctxt.log(Localizer.getMessage("jsp.warning.noJarScanner"));
+            jarScanner = new StandardJarScanner();
+        }
+        return jarScanner;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JarURLResource.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JarURLResource.java
new file mode 100644
index 0000000..4ea16cf
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JarURLResource.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.jasper.compiler;
+
+import java.io.IOException;
+import java.net.JarURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.jar.JarFile;
+
+public class JarURLResource implements JarResource {
+    
+    private String jarUrl;
+    
+    public JarURLResource(URL jarURL) {
+        this(jarURL.toExternalForm());
+    }
+    
+    public JarURLResource(String jarUrl) {
+        this.jarUrl = jarUrl;
+    }
+    
+    @Override
+    public JarFile getJarFile() throws IOException {
+        URL jarFileUrl = new URL("jar:" + jarUrl + "!/");
+        JarURLConnection conn = (JarURLConnection) jarFileUrl.openConnection();
+        conn.setUseCaches(false);
+        conn.connect();
+        return conn.getJarFile();
+    }
+       
+    @Override
+    public String getUrl() {
+        return jarUrl;
+    }
+    
+    @Override
+    public URL getEntry(String name) {
+        try {
+            return new URL("jar:" + jarUrl + "!/" + name);
+        } catch (MalformedURLException e) {
+            throw new RuntimeException("", e);
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JasperTagInfo.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JasperTagInfo.java
new file mode 100644
index 0000000..d8b91cc
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JasperTagInfo.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.compiler;
+
+import javax.servlet.jsp.tagext.TagAttributeInfo;
+import javax.servlet.jsp.tagext.TagExtraInfo;
+import javax.servlet.jsp.tagext.TagInfo;
+import javax.servlet.jsp.tagext.TagLibraryInfo;
+import javax.servlet.jsp.tagext.TagVariableInfo;
+
+/**
+ * TagInfo extension used by tag handlers that are implemented via tag files.
+ * This class provides access to the name of the Map used to store the
+ * dynamic attribute names and values passed to the custom action invocation.
+ * This information is used by the code generator.
+ */
+class JasperTagInfo extends TagInfo {
+
+    private String dynamicAttrsMapName;
+
+    public JasperTagInfo(String tagName,
+            String tagClassName,
+            String bodyContent,
+            String infoString,
+            TagLibraryInfo taglib,
+            TagExtraInfo tagExtraInfo,
+            TagAttributeInfo[] attributeInfo,
+            String displayName,
+            String smallIcon,
+            String largeIcon,
+            TagVariableInfo[] tvi,
+            String mapName) {
+
+        super(tagName, tagClassName, bodyContent, infoString, taglib,
+                tagExtraInfo, attributeInfo, displayName, smallIcon, largeIcon,
+                tvi);
+        
+        this.dynamicAttrsMapName = mapName;
+    }
+
+    public String getDynamicAttributesMapName() {
+        return dynamicAttrsMapName;
+    }
+
+    @Override
+    public boolean hasDynamicAttributes() {
+        return dynamicAttrsMapName != null;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JavacErrorDetail.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JavacErrorDetail.java
new file mode 100644
index 0000000..6c6b96d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JavacErrorDetail.java
@@ -0,0 +1,231 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.jasper.Constants;
+import org.apache.jasper.JspCompilationContext;
+
+/**
+ * Class providing details about a javac compilation error.
+ *
+ * @author Jan Luehe
+ * @author Kin-man Chung
+ */
+public class JavacErrorDetail {
+
+    private String javaFileName;
+    private int javaLineNum;
+    private String jspFileName;
+    private int jspBeginLineNum;
+    private StringBuilder errMsg;
+    private String jspExtract = null;
+
+    /**
+     * Constructor.
+     *
+     * @param javaFileName The name of the Java file in which the 
+     * compilation error occurred
+     * @param javaLineNum The compilation error line number
+     * @param errMsg The compilation error message
+     */
+    public JavacErrorDetail(String javaFileName,
+                            int javaLineNum,
+                            StringBuilder errMsg) {
+
+        this.javaFileName = javaFileName;
+        this.javaLineNum = javaLineNum;
+        this.errMsg = errMsg;
+        this.jspBeginLineNum = -1;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param javaFileName The name of the Java file in which the 
+     * compilation error occurred
+     * @param javaLineNum The compilation error line number
+     * @param jspFileName The name of the JSP file from which the Java source
+     * file was generated
+     * @param jspBeginLineNum The start line number of the JSP element
+     * responsible for the compilation error
+     * @param errMsg The compilation error message
+     * @param ctxt The compilation context
+     */
+    public JavacErrorDetail(String javaFileName,
+            int javaLineNum,
+            String jspFileName,
+            int jspBeginLineNum,
+            StringBuilder errMsg,
+            JspCompilationContext ctxt) {
+        
+        this(javaFileName, javaLineNum, errMsg);
+        this.jspFileName = jspFileName;
+        this.jspBeginLineNum = jspBeginLineNum;
+        
+        if (jspBeginLineNum > 0 && ctxt != null) {
+            InputStream is = null;
+            FileInputStream  fis = null;
+            
+            try {
+                // Read both files in, so we can inspect them
+                is = ctxt.getResourceAsStream(jspFileName);
+                String[] jspLines = readFile(is);
+    
+                fis = new FileInputStream(ctxt.getServletJavaFileName());
+                String[] javaLines = readFile(fis);
+    
+                if (jspLines.length < jspBeginLineNum) {
+                    // Avoid ArrayIndexOutOfBoundsException
+                    // Probably bug 48498 but could be some other cause
+                    jspExtract = Localizer.getMessage("jsp.error.bug48498");
+                    return;
+                }
+                
+                // If the line contains the opening of a multi-line scriptlet
+                // block, then the JSP line number we got back is probably
+                // faulty.  Scan forward to match the java line...
+                if (jspLines[jspBeginLineNum-1].lastIndexOf("<%") >
+                    jspLines[jspBeginLineNum-1].lastIndexOf("%>")) {
+                    String javaLine = javaLines[javaLineNum-1].trim();
+    
+                    for (int i=jspBeginLineNum-1; i<jspLines.length; i++) {
+                        if (jspLines[i].indexOf(javaLine) != -1) {
+                            // Update jsp line number
+                            this.jspBeginLineNum = i+1;
+                            break;
+                        }
+                    }
+                }
+    
+                // copy out a fragment of JSP to display to the user
+                StringBuilder fragment = new StringBuilder(1024);
+                int startIndex = Math.max(0, this.jspBeginLineNum-1-3);
+                int endIndex = Math.min(
+                        jspLines.length-1, this.jspBeginLineNum-1+3);
+    
+                for (int i=startIndex;i<=endIndex; ++i) {
+                    fragment.append(i+1);
+                    fragment.append(": ");
+                    fragment.append(jspLines[i]);
+                    fragment.append(Constants.NEWLINE);
+                }
+                jspExtract = fragment.toString();
+    
+            } catch (IOException ioe) {
+                // Can't read files - ignore
+            } finally {
+                if (is != null) {
+                    try {
+                        is.close();
+                    } catch (IOException ioe) {
+                        // Ignore
+                    }
+                }
+                if (fis != null) {
+                    try {
+                        fis.close();
+                    } catch (IOException ioe) {
+                        // Ignore
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Gets the name of the Java source file in which the compilation error
+     * occurred.
+     *
+     * @return Java source file name
+     */
+    public String getJavaFileName() {
+        return this.javaFileName;
+    }
+
+    /**
+     * Gets the compilation error line number.
+     * 
+     * @return Compilation error line number
+     */
+    public int getJavaLineNumber() {
+        return this.javaLineNum;
+    }
+
+    /**
+     * Gets the name of the JSP file from which the Java source file was
+     * generated.
+     *
+     * @return JSP file from which the Java source file was generated.
+     */
+    public String getJspFileName() {
+        return this.jspFileName;
+    }
+
+    /**
+     * Gets the start line number (in the JSP file) of the JSP element
+     * responsible for the compilation error.
+     *
+     * @return Start line number of the JSP element responsible for the
+     * compilation error
+     */
+    public int getJspBeginLineNumber() {
+        return this.jspBeginLineNum;
+    }
+
+    /**
+     * Gets the compilation error message.
+     *
+     * @return Compilation error message
+     */
+    public String getErrorMessage() {
+        return this.errMsg.toString();
+    }
+    
+    /**
+     * Gets the extract of the JSP that corresponds to this message.
+     *
+     * @return Extract of JSP where error occurred
+     */
+    public String getJspExtract() {
+        return this.jspExtract;
+    }
+    
+    /**
+     * Reads a text file from an input stream into a String[]. Used to read in
+     * the JSP and generated Java file when generating error messages.
+     */
+    private String[] readFile(InputStream s) throws IOException {
+        BufferedReader reader = new BufferedReader(new InputStreamReader(s));
+        List<String> lines = new ArrayList<String>();
+        String line;
+
+        while ( (line = reader.readLine()) != null ) {
+            lines.add(line);
+        }
+
+        return lines.toArray( new String[lines.size()] );
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JspConfig.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JspConfig.java
new file mode 100644
index 0000000..5add4d1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JspConfig.java
@@ -0,0 +1,598 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import java.util.Iterator;
+import java.util.Vector;
+
+import javax.servlet.ServletContext;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.xmlparser.ParserUtils;
+import org.apache.jasper.xmlparser.TreeNode;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * Handles the jsp-config element in WEB_INF/web.xml.  This is used
+ * for specifying the JSP configuration information on a JSP page
+ *
+ * @author Kin-man Chung
+ * @author Remy Maucherat
+ */
+
+public class JspConfig {
+
+    // Logger
+    private final Log log = LogFactory.getLog(JspConfig.class);
+
+    private Vector<JspPropertyGroup> jspProperties = null;
+    private ServletContext ctxt;
+    private volatile boolean initialized = false;
+
+    private static final String defaultIsXml = null;    // unspecified
+    private String defaultIsELIgnored = null;           // unspecified
+    private static final String defaultIsScriptingInvalid = null;
+    private String defaultDeferedSyntaxAllowedAsLiteral = null;
+    private static final String defaultTrimDirectiveWhitespaces = null;
+    private static final String defaultDefaultContentType = null;
+    private static final String defaultBuffer = null;
+    private static final String defaultErrorOnUndeclaredNamespace = "false";
+    private JspProperty defaultJspProperty;
+
+    public JspConfig(ServletContext ctxt) {
+        this.ctxt = ctxt;
+    }
+
+    private double getVersion(TreeNode webApp) {
+        String v = webApp.findAttribute("version");
+        if (v != null) {
+            try {
+                return Double.parseDouble(v);
+            } catch (NumberFormatException e) {
+            }
+        }
+        return 2.3;
+    }
+
+    private void processWebDotXml() throws JasperException {
+
+        WebXml webXml = null;
+
+        try {
+            webXml = new WebXml(ctxt);
+            
+            TreeNode webApp = null;
+            if (webXml.getInputSource() != null) {
+                ParserUtils pu = new ParserUtils();
+                webApp = pu.parseXMLDocument(webXml.getSystemId(),
+                        webXml.getInputSource());
+            }
+
+            if (webApp == null
+                    || getVersion(webApp) < 2.4) {
+                defaultIsELIgnored = "true";
+                defaultDeferedSyntaxAllowedAsLiteral = "true";
+                return;
+            }
+            if (getVersion(webApp) < 2.5) {
+                defaultDeferedSyntaxAllowedAsLiteral = "true";
+            }
+            TreeNode jspConfig = webApp.findChild("jsp-config");
+            if (jspConfig == null) {
+                return;
+            }
+
+            jspProperties = new Vector<JspPropertyGroup>();
+            Iterator<TreeNode> jspPropertyList =
+                jspConfig.findChildren("jsp-property-group");
+            while (jspPropertyList.hasNext()) {
+
+                TreeNode element = jspPropertyList.next();
+                Iterator<TreeNode> list = element.findChildren();
+
+                Vector<String> urlPatterns = new Vector<String>();
+                String pageEncoding = null;
+                String scriptingInvalid = null;
+                String elIgnored = null;
+                String isXml = null;
+                Vector<String> includePrelude = new Vector<String>();
+                Vector<String> includeCoda = new Vector<String>();
+                String deferredSyntaxAllowedAsLiteral = null;
+                String trimDirectiveWhitespaces = null;
+                String defaultContentType = null;
+                String buffer = null;
+                String errorOnUndeclaredNamespace = null;
+
+                while (list.hasNext()) {
+
+                    element = list.next();
+                    String tname = element.getName();
+
+                    if ("url-pattern".equals(tname))
+                        urlPatterns.addElement( element.getBody() );
+                    else if ("page-encoding".equals(tname))
+                        pageEncoding = element.getBody();
+                    else if ("is-xml".equals(tname))
+                        isXml = element.getBody();
+                    else if ("el-ignored".equals(tname))
+                        elIgnored = element.getBody();
+                    else if ("scripting-invalid".equals(tname))
+                        scriptingInvalid = element.getBody();
+                    else if ("include-prelude".equals(tname))
+                        includePrelude.addElement(element.getBody());
+                    else if ("include-coda".equals(tname))
+                        includeCoda.addElement(element.getBody());
+                    else if ("deferred-syntax-allowed-as-literal".equals(tname))
+                        deferredSyntaxAllowedAsLiteral = element.getBody();
+                    else if ("trim-directive-whitespaces".equals(tname))
+                        trimDirectiveWhitespaces = element.getBody();
+                    else if ("default-content-type".equals(tname))
+                        defaultContentType = element.getBody();
+                    else if ("buffer".equals(tname))
+                        buffer = element.getBody();
+                    else if ("error-on-undeclared-namespace".equals(tname))
+                        errorOnUndeclaredNamespace = element.getBody();
+                }
+
+                if (urlPatterns.size() == 0) {
+                    continue;
+                }
+
+                // Add one JspPropertyGroup for each URL Pattern.  This makes
+                // the matching logic easier.
+                for( int p = 0; p < urlPatterns.size(); p++ ) {
+                    String urlPattern = urlPatterns.elementAt( p );
+                    String path = null;
+                    String extension = null;
+
+                    if (urlPattern.indexOf('*') < 0) {
+                        // Exact match
+                        path = urlPattern;
+                    } else {
+                        int i = urlPattern.lastIndexOf('/');
+                        String file;
+                        if (i >= 0) {
+                            path = urlPattern.substring(0,i+1);
+                            file = urlPattern.substring(i+1);
+                        } else {
+                            file = urlPattern;
+                        }
+
+                        // pattern must be "*", or of the form "*.jsp"
+                        if (file.equals("*")) {
+                            extension = "*";
+                        } else if (file.startsWith("*.")) {
+                            extension = file.substring(file.indexOf('.')+1);
+                        }
+
+                        // The url patterns are reconstructed as the following:
+                        // path != null, extension == null:  / or /foo/bar.ext
+                        // path == null, extension != null:  *.ext
+                        // path != null, extension == "*":   /foo/*
+                        boolean isStar = "*".equals(extension);
+                        if ((path == null && (extension == null || isStar))
+                                || (path != null && !isStar)) {
+                            if (log.isWarnEnabled()) {
+                                log.warn(Localizer.getMessage(
+                                        "jsp.warning.bad.urlpattern.propertygroup",
+                                        urlPattern));
+                            }
+                            continue;
+                        }
+                    }
+
+                    JspProperty property = new JspProperty(isXml,
+                            elIgnored,
+                            scriptingInvalid,
+                            pageEncoding,
+                            includePrelude,
+                            includeCoda,
+                            deferredSyntaxAllowedAsLiteral,
+                            trimDirectiveWhitespaces,
+                            defaultContentType,
+                            buffer,
+                            errorOnUndeclaredNamespace);
+                    JspPropertyGroup propertyGroup =
+                        new JspPropertyGroup(path, extension, property);
+
+                    jspProperties.addElement(propertyGroup);
+                }
+            }
+        } catch (Exception ex) {
+            throw new JasperException(ex);
+        } finally {
+            if (webXml != null) {
+                webXml.close();
+            }
+        }
+    }
+
+    private void init() throws JasperException {
+
+        if (!initialized) {
+            synchronized (this) {
+                if (!initialized) {
+                    processWebDotXml();
+                    defaultJspProperty = new JspProperty(defaultIsXml,
+                            defaultIsELIgnored,
+                            defaultIsScriptingInvalid,
+                            null, null, null,
+                            defaultDeferedSyntaxAllowedAsLiteral, 
+                            defaultTrimDirectiveWhitespaces,
+                            defaultDefaultContentType,
+                            defaultBuffer,
+                            defaultErrorOnUndeclaredNamespace);
+                    initialized = true;
+                }
+            }
+        }
+    }
+
+    /**
+     * Select the property group that has more restrictive url-pattern.
+     * In case of tie, select the first.
+     */
+    private JspPropertyGroup selectProperty(JspPropertyGroup prev,
+            JspPropertyGroup curr) {
+        if (prev == null) {
+            return curr;
+        }
+        if (prev.getExtension() == null) {
+            // exact match
+            return prev;
+        }
+        if (curr.getExtension() == null) {
+            // exact match
+            return curr;
+        }
+        String prevPath = prev.getPath();
+        String currPath = curr.getPath();
+        if (prevPath == null && currPath == null) {
+            // Both specifies a *.ext, keep the first one
+            return prev;
+        }
+        if (prevPath == null && currPath != null) {
+            return curr;
+        }
+        if (prevPath != null && currPath == null) {
+            return prev;
+        }
+        if (prevPath.length() >= currPath.length()) {
+            return prev;
+        }
+        return curr;
+    }
+
+
+    /**
+     * Find a property that best matches the supplied resource.
+     * @param uri the resource supplied.
+     * @return a JspProperty indicating the best match, or some default.
+     */
+    public JspProperty findJspProperty(String uri) throws JasperException {
+
+        init();
+
+        // JSP Configuration settings do not apply to tag files
+        if (jspProperties == null || uri.endsWith(".tag")
+                || uri.endsWith(".tagx")) {
+            return defaultJspProperty;
+        }
+
+        String uriPath = null;
+        int index = uri.lastIndexOf('/');
+        if (index >=0 ) {
+            uriPath = uri.substring(0, index+1);
+        }
+        String uriExtension = null;
+        index = uri.lastIndexOf('.');
+        if (index >=0) {
+            uriExtension = uri.substring(index+1);
+        }
+
+        Vector<String> includePreludes = new Vector<String>();
+        Vector<String> includeCodas = new Vector<String>();
+
+        JspPropertyGroup isXmlMatch = null;
+        JspPropertyGroup elIgnoredMatch = null;
+        JspPropertyGroup scriptingInvalidMatch = null;
+        JspPropertyGroup pageEncodingMatch = null;
+        JspPropertyGroup deferedSyntaxAllowedAsLiteralMatch = null;
+        JspPropertyGroup trimDirectiveWhitespacesMatch = null;
+        JspPropertyGroup defaultContentTypeMatch = null;
+        JspPropertyGroup bufferMatch = null;
+        JspPropertyGroup errorOnUndeclaredNamespaceMatch = null;
+
+        Iterator<JspPropertyGroup> iter = jspProperties.iterator();
+        while (iter.hasNext()) {
+
+            JspPropertyGroup jpg = iter.next();
+            JspProperty jp = jpg.getJspProperty();
+
+            // (arrays will be the same length)
+            String extension = jpg.getExtension();
+            String path = jpg.getPath();
+
+            if (extension == null) {
+                // exact match pattern: /a/foo.jsp
+                if (!uri.equals(path)) {
+                    // not matched;
+                    continue;
+                }
+            } else {
+                // Matching patterns *.ext or /p/*
+                if (path != null && uriPath != null &&
+                        ! uriPath.startsWith(path)) {
+                    // not matched
+                    continue;
+                }
+                if (!extension.equals("*") &&
+                        !extension.equals(uriExtension)) {
+                    // not matched
+                    continue;
+                }
+            }
+            // We have a match
+            // Add include-preludes and include-codas
+            if (jp.getIncludePrelude() != null) {
+                includePreludes.addAll(jp.getIncludePrelude());
+            }
+            if (jp.getIncludeCoda() != null) {
+                includeCodas.addAll(jp.getIncludeCoda());
+            }
+
+            // If there is a previous match for the same property, remember
+            // the one that is more restrictive.
+            if (jp.isXml() != null) {
+                isXmlMatch = selectProperty(isXmlMatch, jpg);
+            }
+            if (jp.isELIgnored() != null) {
+                elIgnoredMatch = selectProperty(elIgnoredMatch, jpg);
+            }
+            if (jp.isScriptingInvalid() != null) {
+                scriptingInvalidMatch =
+                    selectProperty(scriptingInvalidMatch, jpg);
+            }
+            if (jp.getPageEncoding() != null) {
+                pageEncodingMatch = selectProperty(pageEncodingMatch, jpg);
+            }
+            if (jp.isDeferedSyntaxAllowedAsLiteral() != null) {
+                deferedSyntaxAllowedAsLiteralMatch =
+                    selectProperty(deferedSyntaxAllowedAsLiteralMatch, jpg);
+            }
+            if (jp.isTrimDirectiveWhitespaces() != null) {
+                trimDirectiveWhitespacesMatch =
+                    selectProperty(trimDirectiveWhitespacesMatch, jpg);
+            }
+            if (jp.getDefaultContentType() != null) {
+                defaultContentTypeMatch =
+                    selectProperty(defaultContentTypeMatch, jpg);
+            }
+            if (jp.getBuffer() != null) {
+                bufferMatch = selectProperty(bufferMatch, jpg);
+            }
+            if (jp.isErrorOnUndeclaredNamespace() != null) {
+                errorOnUndeclaredNamespaceMatch =
+                    selectProperty(errorOnUndeclaredNamespaceMatch, jpg);
+            }
+        }
+
+
+        String isXml = defaultIsXml;
+        String isELIgnored = defaultIsELIgnored;
+        String isScriptingInvalid = defaultIsScriptingInvalid;
+        String pageEncoding = null;
+        String isDeferedSyntaxAllowedAsLiteral =
+            defaultDeferedSyntaxAllowedAsLiteral;
+        String isTrimDirectiveWhitespaces = defaultTrimDirectiveWhitespaces;
+        String defaultContentType = defaultDefaultContentType;
+        String buffer = defaultBuffer;
+        String errorOnUndelcaredNamespace = defaultErrorOnUndeclaredNamespace;
+
+        if (isXmlMatch != null) {
+            isXml = isXmlMatch.getJspProperty().isXml();
+        }
+        if (elIgnoredMatch != null) {
+            isELIgnored = elIgnoredMatch.getJspProperty().isELIgnored();
+        }
+        if (scriptingInvalidMatch != null) {
+            isScriptingInvalid =
+                scriptingInvalidMatch.getJspProperty().isScriptingInvalid();
+        }
+        if (pageEncodingMatch != null) {
+            pageEncoding = pageEncodingMatch.getJspProperty().getPageEncoding();
+        }
+        if (deferedSyntaxAllowedAsLiteralMatch != null) {
+            isDeferedSyntaxAllowedAsLiteral =
+                deferedSyntaxAllowedAsLiteralMatch.getJspProperty().isDeferedSyntaxAllowedAsLiteral();
+        }
+        if (trimDirectiveWhitespacesMatch != null) {
+            isTrimDirectiveWhitespaces =
+                trimDirectiveWhitespacesMatch.getJspProperty().isTrimDirectiveWhitespaces();
+        }
+        if (defaultContentTypeMatch != null) {
+            defaultContentType =
+                defaultContentTypeMatch.getJspProperty().getDefaultContentType();
+        }
+        if (bufferMatch != null) {
+            buffer = bufferMatch.getJspProperty().getBuffer();
+        }
+        if (errorOnUndeclaredNamespaceMatch != null) {
+            errorOnUndelcaredNamespace =
+                errorOnUndeclaredNamespaceMatch.getJspProperty().isErrorOnUndeclaredNamespace();
+        }
+
+        return new JspProperty(isXml, isELIgnored, isScriptingInvalid,
+                pageEncoding, includePreludes, includeCodas, 
+                isDeferedSyntaxAllowedAsLiteral, isTrimDirectiveWhitespaces,
+                defaultContentType, buffer, errorOnUndelcaredNamespace);
+    }
+
+    /**
+     * To find out if an uri matches an url pattern in jsp config.  If so,
+     * then the uri is a JSP page.  This is used primarily for jspc.
+     */
+    public boolean isJspPage(String uri) throws JasperException {
+
+        init();
+        if (jspProperties == null) {
+            return false;
+        }
+
+        String uriPath = null;
+        int index = uri.lastIndexOf('/');
+        if (index >=0 ) {
+            uriPath = uri.substring(0, index+1);
+        }
+        String uriExtension = null;
+        index = uri.lastIndexOf('.');
+        if (index >=0) {
+            uriExtension = uri.substring(index+1);
+        }
+
+        Iterator<JspPropertyGroup> iter = jspProperties.iterator();
+        while (iter.hasNext()) {
+
+            JspPropertyGroup jpg = iter.next();
+
+            String extension = jpg.getExtension();
+            String path = jpg.getPath();
+
+            if (extension == null) {
+                if (uri.equals(path)) {
+                    // There is an exact match
+                    return true;
+                }
+            } else {
+                if ((path == null || path.equals(uriPath)) &&
+                        (extension.equals("*") || extension.equals(uriExtension))) {
+                    // Matches *, *.ext, /p/*, or /p/*.ext
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public static class JspPropertyGroup {
+        private String path;
+        private String extension;
+        private JspProperty jspProperty;
+
+        JspPropertyGroup(String path, String extension,
+                JspProperty jspProperty) {
+            this.path = path;
+            this.extension = extension;
+            this.jspProperty = jspProperty;
+        }
+
+        public String getPath() {
+            return path;
+        }
+
+        public String getExtension() {
+            return extension;
+        }
+
+        public JspProperty getJspProperty() {
+            return jspProperty;
+        }
+    }
+
+    public static class JspProperty {
+
+        private String isXml;
+        private String elIgnored;
+        private String scriptingInvalid;
+        private String pageEncoding;
+        private Vector<String> includePrelude;
+        private Vector<String> includeCoda;
+        private String deferedSyntaxAllowedAsLiteral;
+        private String trimDirectiveWhitespaces;
+        private String defaultContentType;
+        private String buffer;
+        private String errorOnUndeclaredNamespace;
+
+        public JspProperty(String isXml, String elIgnored,
+                String scriptingInvalid, String pageEncoding,
+                Vector<String> includePrelude, Vector<String> includeCoda,
+                String deferedSyntaxAllowedAsLiteral, 
+                String trimDirectiveWhitespaces,
+                String defaultContentType,
+                String buffer,
+                String errorOnUndeclaredNamespace) {
+
+            this.isXml = isXml;
+            this.elIgnored = elIgnored;
+            this.scriptingInvalid = scriptingInvalid;
+            this.pageEncoding = pageEncoding;
+            this.includePrelude = includePrelude;
+            this.includeCoda = includeCoda;
+            this.deferedSyntaxAllowedAsLiteral = deferedSyntaxAllowedAsLiteral;
+            this.trimDirectiveWhitespaces = trimDirectiveWhitespaces;
+            this.defaultContentType = defaultContentType;
+            this.buffer = buffer;
+            this.errorOnUndeclaredNamespace = errorOnUndeclaredNamespace;
+        }
+
+        public String isXml() {
+            return isXml;
+        }
+
+        public String isELIgnored() {
+            return elIgnored;
+        }
+
+        public String isScriptingInvalid() {
+            return scriptingInvalid;
+        }
+
+        public String getPageEncoding() {
+            return pageEncoding;
+        }
+
+        public Vector<String> getIncludePrelude() {
+            return includePrelude;
+        }
+
+        public Vector<String> getIncludeCoda() {
+            return includeCoda;
+        }
+        
+        public String isDeferedSyntaxAllowedAsLiteral() {
+            return deferedSyntaxAllowedAsLiteral;
+        }
+        
+        public String isTrimDirectiveWhitespaces() {
+            return trimDirectiveWhitespaces;
+        }
+        
+        public String getDefaultContentType() {
+            return defaultContentType;
+        }
+        
+        public String getBuffer() {
+            return buffer;
+        }
+        
+        public String isErrorOnUndeclaredNamespace() {
+            return errorOnUndeclaredNamespace;
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JspDocumentParser.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JspDocumentParser.java
new file mode 100644
index 0000000..ef6ba2e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JspDocumentParser.java
@@ -0,0 +1,1484 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.compiler;
+
+import java.io.CharArrayWriter;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Iterator;
+import java.util.List;
+import java.util.jar.JarFile;
+
+import javax.servlet.jsp.tagext.TagFileInfo;
+import javax.servlet.jsp.tagext.TagInfo;
+import javax.servlet.jsp.tagext.TagLibraryInfo;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.ext.LexicalHandler;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Class implementing a parser for a JSP document, that is, a JSP page in XML
+ * syntax.
+ *
+ * @author Jan Luehe
+ * @author Kin-man Chung
+ */
+
+class JspDocumentParser
+    extends DefaultHandler
+    implements LexicalHandler, TagConstants {
+
+    private static final String LEXICAL_HANDLER_PROPERTY =
+        "http://xml.org/sax/properties/lexical-handler";
+    private static final String JSP_URI = "http://java.sun.com/JSP/Page";
+
+    private ParserController parserController;
+    private JspCompilationContext ctxt;
+    private PageInfo pageInfo;
+    private String path;
+    private StringBuilder charBuffer;
+
+    // Node representing the XML element currently being parsed
+    private Node current;
+
+    /*
+     * Outermost (in the nesting hierarchy) node whose body is declared to be
+     * scriptless. If a node's body is declared to be scriptless, all its
+     * nested nodes must be scriptless, too.
+     */ 
+    private Node scriptlessBodyNode;
+
+    private Locator locator;
+
+    //Mark representing the start of the current element.  Note
+    //that locator.getLineNumber() and locator.getColumnNumber()
+    //return the line and column numbers for the character
+    //immediately _following_ the current element.  The underlying
+    //XMl parser eats white space that is not part of character
+    //data, so for Nodes that are not created from character data,
+    //this is the best we can do.  But when we parse character data,
+    //we get an accurate starting location by starting with startMark
+    //as set by the previous element, and updating it as we advance
+    //through the characters.
+    private Mark startMark;
+
+    // Flag indicating whether we are inside DTD declarations
+    private boolean inDTD;
+
+    private boolean isValidating;
+
+    private ErrorDispatcher err;
+    private boolean isTagFile;
+    private boolean directivesOnly;
+    private boolean isTop;
+
+    // Nesting level of Tag dependent bodies
+    private int tagDependentNesting = 0;
+    // Flag set to delay incrementing tagDependentNesting until jsp:body
+    // is first encountered
+    private boolean tagDependentPending = false;
+
+    /*
+     * Constructor
+     */
+    public JspDocumentParser(
+        ParserController pc,
+        String path,
+        boolean isTagFile,
+        boolean directivesOnly) {
+        this.parserController = pc;
+        this.ctxt = pc.getJspCompilationContext();
+        this.pageInfo = pc.getCompiler().getPageInfo();
+        this.err = pc.getCompiler().getErrorDispatcher();
+        this.path = path;
+        this.isTagFile = isTagFile;
+        this.directivesOnly = directivesOnly;
+        this.isTop = true;
+    }
+
+    /*
+     * Parses a JSP document by responding to SAX events.
+     *
+     * @throws JasperException
+     */
+    public static Node.Nodes parse(
+        ParserController pc,
+        String path,
+        JarFile jarFile,
+        Node parent,
+        boolean isTagFile,
+        boolean directivesOnly,
+        String pageEnc,
+        String jspConfigPageEnc,
+        boolean isEncodingSpecifiedInProlog,
+        boolean isBomPresent)
+        throws JasperException {
+
+        JspDocumentParser jspDocParser =
+            new JspDocumentParser(pc, path, isTagFile, directivesOnly);
+        Node.Nodes pageNodes = null;
+
+        try {
+
+            // Create dummy root and initialize it with given page encodings
+            Node.Root dummyRoot = new Node.Root(null, parent, true);
+            dummyRoot.setPageEncoding(pageEnc);
+            dummyRoot.setJspConfigPageEncoding(jspConfigPageEnc);
+            dummyRoot.setIsEncodingSpecifiedInProlog(
+                isEncodingSpecifiedInProlog);
+            dummyRoot.setIsBomPresent(isBomPresent);
+            jspDocParser.current = dummyRoot;
+            if (parent == null) {
+                jspDocParser.addInclude(
+                    dummyRoot,
+                    jspDocParser.pageInfo.getIncludePrelude());
+            } else {
+                jspDocParser.isTop = false;
+            }
+
+            // Parse the input
+            SAXParser saxParser = getSAXParser(false, jspDocParser);
+            InputStream inStream = null;
+            try {
+                inStream = JspUtil.getInputStream(path, jarFile,
+                                                  jspDocParser.ctxt,
+                                                  jspDocParser.err);
+                saxParser.parse(new InputSource(inStream), jspDocParser);
+            } catch (EnableDTDValidationException e) {
+                saxParser = getSAXParser(true, jspDocParser);
+                jspDocParser.isValidating = true;
+                if (inStream != null) {
+                    try {
+                        inStream.close();
+                    } catch (Exception any) {
+                    }
+                }
+                inStream = JspUtil.getInputStream(path, jarFile,
+                                                  jspDocParser.ctxt,
+                                                  jspDocParser.err);
+                saxParser.parse(new InputSource(inStream), jspDocParser);
+            } finally {
+                if (inStream != null) {
+                    try {
+                        inStream.close();
+                    } catch (Exception any) {
+                    }
+                }
+            }
+
+            if (parent == null) {
+                jspDocParser.addInclude(
+                    dummyRoot,
+                    jspDocParser.pageInfo.getIncludeCoda());
+            }
+
+            // Create Node.Nodes from dummy root
+            pageNodes = new Node.Nodes(dummyRoot);
+
+        } catch (IOException ioe) {
+            jspDocParser.err.jspError("jsp.error.data.file.read", path, ioe);
+        } catch (SAXParseException e) {
+            jspDocParser.err.jspError
+                (new Mark(jspDocParser.ctxt, path, e.getLineNumber(),
+                          e.getColumnNumber()),
+                 e.getMessage());
+        } catch (Exception e) {
+            jspDocParser.err.jspError(e);
+        }
+
+        return pageNodes;
+    }
+
+    /*
+     * Processes the given list of included files.
+     *
+     * This is used to implement the include-prelude and include-coda
+     * subelements of the jsp-config element in web.xml
+     */
+    private void addInclude(Node parent, List<String> files) throws SAXException {
+        if (files != null) {
+            Iterator<String> iter = files.iterator();
+            while (iter.hasNext()) {
+                String file = iter.next();
+                AttributesImpl attrs = new AttributesImpl();
+                attrs.addAttribute("", "file", "file", "CDATA", file);
+
+                // Create a dummy Include directive node
+                    Node includeDir =
+                        new Node.IncludeDirective(attrs, null, // XXX
+    parent);
+                processIncludeDirective(file, includeDir);
+            }
+        }
+    }
+
+    /*
+     * Receives notification of the start of an element.
+     *
+     * This method assigns the given tag attributes to one of 3 buckets:
+     * 
+     * - "xmlns" attributes that represent (standard or custom) tag libraries.
+     * - "xmlns" attributes that do not represent tag libraries.
+     * - all remaining attributes.
+     *
+     * For each "xmlns" attribute that represents a custom tag library, the
+     * corresponding TagLibraryInfo object is added to the set of custom
+     * tag libraries.
+     */
+    @Override
+    public void startElement(
+        String uri,
+        String localName,
+        String qName,
+        Attributes attrs)
+        throws SAXException {
+
+        AttributesImpl taglibAttrs = null;
+        AttributesImpl nonTaglibAttrs = null;
+        AttributesImpl nonTaglibXmlnsAttrs = null;
+
+        processChars();
+
+        checkPrefixes(uri, qName, attrs);
+
+        if (directivesOnly &&
+            !(JSP_URI.equals(uri) && localName.startsWith(DIRECTIVE_ACTION))) {
+            return;
+        }
+
+        String currentPrefix = getPrefix(current.getQName());
+        
+        // jsp:text must not have any subelements
+        if (JSP_URI.equals(uri) && TEXT_ACTION.equals(current.getLocalName())
+                && "jsp".equals(currentPrefix)) {
+            throw new SAXParseException(
+                Localizer.getMessage("jsp.error.text.has_subelement"),
+                locator);
+        }
+
+        startMark = new Mark(ctxt, path, locator.getLineNumber(),
+                             locator.getColumnNumber());
+
+        if (attrs != null) {
+            /*
+             * Notice that due to a bug in the underlying SAX parser, the
+             * attributes must be enumerated in descending order. 
+             */
+            boolean isTaglib = false;
+            for (int i = attrs.getLength() - 1; i >= 0; i--) {
+                isTaglib = false;
+                String attrQName = attrs.getQName(i);
+                if (!attrQName.startsWith("xmlns")) {
+                    if (nonTaglibAttrs == null) {
+                        nonTaglibAttrs = new AttributesImpl();
+                    }
+                    nonTaglibAttrs.addAttribute(
+                        attrs.getURI(i),
+                        attrs.getLocalName(i),
+                        attrs.getQName(i),
+                        attrs.getType(i),
+                        attrs.getValue(i));
+                } else {
+                    if (attrQName.startsWith("xmlns:jsp")) {
+                        isTaglib = true;
+                    } else {
+                        String attrUri = attrs.getValue(i);
+                        // TaglibInfo for this uri already established in
+                        // startPrefixMapping
+                        isTaglib = pageInfo.hasTaglib(attrUri);
+                    }
+                    if (isTaglib) {
+                        if (taglibAttrs == null) {
+                            taglibAttrs = new AttributesImpl();
+                        }
+                        taglibAttrs.addAttribute(
+                            attrs.getURI(i),
+                            attrs.getLocalName(i),
+                            attrs.getQName(i),
+                            attrs.getType(i),
+                            attrs.getValue(i));
+                    } else {
+                        if (nonTaglibXmlnsAttrs == null) {
+                            nonTaglibXmlnsAttrs = new AttributesImpl();
+                        }
+                        nonTaglibXmlnsAttrs.addAttribute(
+                            attrs.getURI(i),
+                            attrs.getLocalName(i),
+                            attrs.getQName(i),
+                            attrs.getType(i),
+                            attrs.getValue(i));
+                    }
+                }
+            }
+        }
+
+        Node node = null;
+
+        if (tagDependentPending && JSP_URI.equals(uri) &&
+                     localName.equals(BODY_ACTION)) {
+            tagDependentPending = false;
+            tagDependentNesting++;
+            current =
+                parseStandardAction(
+                    qName,
+                    localName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    startMark);
+            return;
+        }
+
+        if (tagDependentPending && JSP_URI.equals(uri) &&
+                     localName.equals(ATTRIBUTE_ACTION)) {
+            current =
+                parseStandardAction(
+                    qName,
+                    localName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    startMark);
+            return;
+        }
+
+        if (tagDependentPending) {
+            tagDependentPending = false;
+            tagDependentNesting++;
+        }
+
+        if (tagDependentNesting > 0) {
+            node =
+                new Node.UninterpretedTag(
+                    qName,
+                    localName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    startMark,
+                    current);
+        } else if (JSP_URI.equals(uri)) {
+            node =
+                parseStandardAction(
+                    qName,
+                    localName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    startMark);
+        } else {
+            node =
+                parseCustomAction(
+                    qName,
+                    localName,
+                    uri,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    startMark,
+                    current);
+            if (node == null) {
+                node =
+                    new Node.UninterpretedTag(
+                        qName,
+                        localName,
+                        nonTaglibAttrs,
+                        nonTaglibXmlnsAttrs,
+                        taglibAttrs,
+                        startMark,
+                        current);
+            } else {
+                // custom action
+                String bodyType = getBodyType((Node.CustomTag) node);
+
+                if (scriptlessBodyNode == null
+                        && bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_SCRIPTLESS)) {
+                    scriptlessBodyNode = node;
+                }
+                else if (TagInfo.BODY_CONTENT_TAG_DEPENDENT.equalsIgnoreCase(bodyType)) {
+                    tagDependentPending = true;
+                }
+            }
+        }
+
+        current = node;
+    }
+
+    /*
+     * Receives notification of character data inside an element.
+     *
+     * The SAX does not call this method with all of the template text, but may
+     * invoke this method with chunks of it.  This is a problem when we try
+     * to determine if the text contains only whitespaces, or when we are
+     * looking for an EL expression string.  Therefore it is necessary to
+     * buffer and concatenate the chunks and process the concatenated text 
+     * later (at beginTag and endTag)
+     *
+     * @param buf The characters
+     * @param offset The start position in the character array
+     * @param len The number of characters to use from the character array
+     *
+     * @throws SAXException
+     */
+    @Override
+    public void characters(char[] buf, int offset, int len) {
+
+        if (charBuffer == null) {
+            charBuffer = new StringBuilder();
+        }
+        charBuffer.append(buf, offset, len);
+    }
+
+    private void processChars() throws SAXException {
+
+        if (charBuffer == null || directivesOnly) {
+            return;
+        }
+
+        /*
+         * JSP.6.1.1: All textual nodes that have only white space are to be
+         * dropped from the document, except for nodes in a jsp:text element,
+         * and any leading and trailing white-space-only textual nodes in a
+         * jsp:attribute whose 'trim' attribute is set to FALSE, which are to
+         * be kept verbatim.
+         * JSP.6.2.3 defines white space characters.
+         */
+        boolean isAllSpace = true;
+        if (!(current instanceof Node.JspText)
+            && !(current instanceof Node.NamedAttribute)) {
+            for (int i = 0; i < charBuffer.length(); i++) {
+                if (!(charBuffer.charAt(i) == ' '
+                    || charBuffer.charAt(i) == '\n'
+                    || charBuffer.charAt(i) == '\r'
+                    || charBuffer.charAt(i) == '\t')) {
+                    isAllSpace = false;
+                    break;
+                }
+            }
+        }
+
+        if (!isAllSpace && tagDependentPending) {
+            tagDependentPending = false;
+            tagDependentNesting++;
+        }
+
+        if (tagDependentNesting > 0) {
+            if (charBuffer.length() > 0) {
+                new Node.TemplateText(charBuffer.toString(), startMark, current);
+            }
+            startMark = new Mark(ctxt, path, locator.getLineNumber(),
+                                 locator.getColumnNumber());
+            charBuffer = null;
+            return;
+        }
+
+        if ((current instanceof Node.JspText)
+            || (current instanceof Node.NamedAttribute)
+            || !isAllSpace) {
+
+            int line = startMark.getLineNumber();
+            int column = startMark.getColumnNumber();
+
+            CharArrayWriter ttext = new CharArrayWriter();
+            int lastCh = 0, elType = 0;
+            for (int i = 0; i < charBuffer.length(); i++) {
+
+                int ch = charBuffer.charAt(i);
+                if (ch == '\n') {
+                    column = 1;
+                    line++;
+                } else {
+                    column++;
+                }
+                if ((lastCh == '$' || lastCh == '#') && ch == '{') {
+                    elType = lastCh;
+                    if (ttext.size() > 0) {
+                        new Node.TemplateText(
+                            ttext.toString(),
+                            startMark,
+                            current);
+                        ttext = new CharArrayWriter();
+                        //We subtract two from the column number to
+                        //account for the '[$,#]{' that we've already parsed
+                        startMark = new Mark(ctxt, path, line, column - 2);
+                    }
+                    // following "${" || "#{" to first unquoted "}"
+                    i++;
+                    boolean singleQ = false;
+                    boolean doubleQ = false;
+                    lastCh = 0;
+                    for (;; i++) {
+                        if (i >= charBuffer.length()) {
+                            throw new SAXParseException(
+                                Localizer.getMessage(
+                                    "jsp.error.unterminated",
+                                    (char) elType + "{"),
+                                locator);
+
+                        }
+                        ch = charBuffer.charAt(i);
+                        if (ch == '\n') {
+                            column = 1;
+                            line++;
+                        } else {
+                            column++;
+                        }
+                        if (lastCh == '\\' && (singleQ || doubleQ)) {
+                            ttext.write(ch);
+                            lastCh = 0;
+                            continue;
+                        }
+                        if (ch == '}') {
+                            new Node.ELExpression((char) elType,
+                                ttext.toString(),
+                                startMark,
+                                current);
+                            ttext = new CharArrayWriter();
+                            startMark = new Mark(ctxt, path, line, column);
+                            break;
+                        }
+                        if (ch == '"')
+                            doubleQ = !doubleQ;
+                        else if (ch == '\'')
+                            singleQ = !singleQ;
+
+                        ttext.write(ch);
+                        lastCh = ch;
+                    }
+                } else if (lastCh == '\\' && (ch == '$' || ch == '#')) {
+                    if (pageInfo.isELIgnored()) {
+                        ttext.write('\\');
+                    }
+                    ttext.write(ch);
+                    ch = 0;  // Not start of EL anymore
+                } else {
+                    if (lastCh == '$' || lastCh == '#' || lastCh == '\\') {
+                        ttext.write(lastCh);
+                    }
+                    if (ch != '$' && ch != '#' && ch != '\\') {
+                        ttext.write(ch);
+                    }
+                }
+                lastCh = ch;
+            }
+            if (lastCh == '$' || lastCh == '#' || lastCh == '\\') {
+                ttext.write(lastCh);
+            }
+            if (ttext.size() > 0) {
+                new Node.TemplateText(ttext.toString(), startMark, current);
+            }
+        }
+        startMark = new Mark(ctxt, path, locator.getLineNumber(),
+                             locator.getColumnNumber());
+
+        charBuffer = null;
+    }
+
+    /*
+     * Receives notification of the end of an element.
+     */
+    @Override
+    public void endElement(String uri, String localName, String qName)
+        throws SAXException {
+
+        processChars();
+
+        if (directivesOnly &&
+            !(JSP_URI.equals(uri) && localName.startsWith(DIRECTIVE_ACTION))) {
+            return;
+        }
+
+        if (current instanceof Node.NamedAttribute) {
+            boolean isTrim = ((Node.NamedAttribute)current).isTrim();
+            Node.Nodes subElems = ((Node.NamedAttribute)current).getBody();
+            for (int i = 0; subElems != null && i < subElems.size(); i++) {
+                Node subElem = subElems.getNode(i);
+                if (!(subElem instanceof Node.TemplateText)) {
+                    continue;
+                }
+                // Ignore any whitespace (including spaces, carriage returns,
+                // line feeds, and tabs, that appear at the beginning and at
+                // the end of the body of the <jsp:attribute> action, if the
+                // action's 'trim' attribute is set to TRUE (default).
+                // In addition, any textual nodes in the <jsp:attribute> that
+                // have only white space are dropped from the document, with
+                // the exception of leading and trailing white-space-only
+                // textual nodes in a <jsp:attribute> whose 'trim' attribute
+                // is set to FALSE, which must be kept verbatim.
+                if (i == 0) {
+                    if (isTrim) {
+                        ((Node.TemplateText)subElem).ltrim();
+                    }
+                } else if (i == subElems.size() - 1) {
+                    if (isTrim) {
+                        ((Node.TemplateText)subElem).rtrim();
+                    }
+                } else {
+                    if (((Node.TemplateText)subElem).isAllSpace()) {
+                        subElems.remove(subElem);
+                    }
+                }
+            }
+        } else if (current instanceof Node.ScriptingElement) {
+            checkScriptingBody((Node.ScriptingElement)current);
+        }
+
+        if ( isTagDependent(current)) {
+            tagDependentNesting--;
+        }
+
+        if (scriptlessBodyNode != null
+                && current.equals(scriptlessBodyNode)) {
+            scriptlessBodyNode = null;
+        }
+
+        if (current instanceof Node.CustomTag) {
+            String bodyType = getBodyType((Node.CustomTag) current);
+            if (TagInfo.BODY_CONTENT_EMPTY.equalsIgnoreCase(bodyType)) {
+                // Children - if any - must be JSP attributes
+                Node.Nodes children = current.getBody();
+                if (children != null && children.size() > 0) {
+                    for (int i = 0; i < children.size(); i++) {
+                        Node child = children.getNode(i);
+                        if (!(child instanceof Node.NamedAttribute)) {
+                            throw new SAXParseException(Localizer.getMessage(
+                                    "jasper.error.emptybodycontent.nonempty",
+                                    current.qName), locator); 
+                        }
+                    }
+                }
+            }
+        }
+        if (current.getParent() != null) {
+            current = current.getParent();
+        }
+    }
+
+    /*
+     * Receives the document locator.
+     *
+     * @param locator the document locator
+     */
+    @Override
+    public void setDocumentLocator(Locator locator) {
+        this.locator = locator;
+    }
+
+    /*
+     * See org.xml.sax.ext.LexicalHandler.
+     */
+    @Override
+    public void comment(char[] buf, int offset, int len) throws SAXException {
+
+        processChars();  // Flush char buffer and remove white spaces
+
+        // ignore comments in the DTD
+        if (!inDTD) {
+            startMark =
+                new Mark(
+                    ctxt,
+                    path,
+                    locator.getLineNumber(),
+                    locator.getColumnNumber());
+            new Node.Comment(new String(buf, offset, len), startMark, current);
+        }
+    }
+
+    /*
+     * See org.xml.sax.ext.LexicalHandler.
+     */
+    @Override
+    public void startCDATA() throws SAXException {
+
+        processChars();  // Flush char buffer and remove white spaces
+        startMark = new Mark(ctxt, path, locator.getLineNumber(),
+                             locator.getColumnNumber());
+    }
+
+    /*
+     * See org.xml.sax.ext.LexicalHandler.
+     */
+    @Override
+    public void endCDATA() throws SAXException {
+        processChars();  // Flush char buffer and remove white spaces
+    }
+
+    /*
+     * See org.xml.sax.ext.LexicalHandler.
+     */
+    @Override
+    public void startEntity(String name) throws SAXException {
+        // do nothing
+    }
+
+    /*
+     * See org.xml.sax.ext.LexicalHandler.
+     */
+    @Override
+    public void endEntity(String name) throws SAXException {
+        // do nothing
+    }
+
+    /*
+     * See org.xml.sax.ext.LexicalHandler.
+     */
+    @Override
+    public void startDTD(String name, String publicId, String systemId)
+        throws SAXException {
+        if (!isValidating) {
+            fatalError(new EnableDTDValidationException(
+                    "jsp.error.enable_dtd_validation", null));
+        }
+
+        inDTD = true;
+    }
+
+    /*
+     * See org.xml.sax.ext.LexicalHandler.
+     */
+    @Override
+    public void endDTD() throws SAXException {
+        inDTD = false;
+    }
+
+    /*
+     * Receives notification of a non-recoverable error.
+     */
+    @Override
+    public void fatalError(SAXParseException e) throws SAXException {
+        throw e;
+    }
+
+    /*
+     * Receives notification of a recoverable error.
+     */
+    @Override
+    public void error(SAXParseException e) throws SAXException {
+        throw e;
+    }
+
+    /*
+     * Receives notification of the start of a Namespace mapping. 
+     */
+    @Override
+    public void startPrefixMapping(String prefix, String uri)
+        throws SAXException {
+        TagLibraryInfo taglibInfo;
+
+        if (directivesOnly && !(JSP_URI.equals(uri))) {
+            return;
+        }
+        
+        try {
+            taglibInfo = getTaglibInfo(prefix, uri);
+        } catch (JasperException je) {
+            throw new SAXParseException(
+                Localizer.getMessage("jsp.error.could.not.add.taglibraries"),
+                locator,
+                je);
+        }
+
+        if (taglibInfo != null) {
+            if (pageInfo.getTaglib(uri) == null) {
+                pageInfo.addTaglib(uri, taglibInfo);
+            }
+            pageInfo.pushPrefixMapping(prefix, uri);
+        } else {
+            pageInfo.pushPrefixMapping(prefix, null);
+        }
+    }
+
+    /*
+     * Receives notification of the end of a Namespace mapping. 
+     */
+    @Override
+    public void endPrefixMapping(String prefix) throws SAXException {
+
+        if (directivesOnly) {
+            String uri = pageInfo.getURI(prefix);
+            if (!JSP_URI.equals(uri)) {
+                return;
+            }
+        }
+
+        pageInfo.popPrefixMapping(prefix);
+    }
+
+    //*********************************************************************
+    // Private utility methods
+
+    private Node parseStandardAction(
+        String qName,
+        String localName,
+        Attributes nonTaglibAttrs,
+        Attributes nonTaglibXmlnsAttrs,
+        Attributes taglibAttrs,
+        Mark start)
+        throws SAXException {
+
+        Node node = null;
+
+        if (localName.equals(ROOT_ACTION)) {
+            if (!(current instanceof Node.Root)) {
+                throw new SAXParseException(
+                    Localizer.getMessage("jsp.error.nested_jsproot"),
+                    locator);
+            }
+            node =
+                new Node.JspRoot(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+            if (isTop) {
+                pageInfo.setHasJspRoot(true);
+            }
+        } else if (localName.equals(PAGE_DIRECTIVE_ACTION)) {
+            if (isTagFile) {
+                throw new SAXParseException(
+                    Localizer.getMessage(
+                        "jsp.error.action.istagfile",
+                        localName),
+                    locator);
+            }
+            node =
+                new Node.PageDirective(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+            String imports = nonTaglibAttrs.getValue("import");
+            // There can only be one 'import' attribute per page directive
+            if (imports != null) {
+                ((Node.PageDirective)node).addImport(imports);
+            }
+        } else if (localName.equals(INCLUDE_DIRECTIVE_ACTION)) {
+            node =
+                new Node.IncludeDirective(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+            processIncludeDirective(nonTaglibAttrs.getValue("file"), node);
+        } else if (localName.equals(DECLARATION_ACTION)) {
+            if (scriptlessBodyNode != null) {
+                // We're nested inside a node whose body is
+                // declared to be scriptless
+                throw new SAXParseException(
+                    Localizer.getMessage(
+                        "jsp.error.no.scriptlets",
+                        localName),
+                    locator);
+            }
+            node =
+                new Node.Declaration(
+                    qName,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(SCRIPTLET_ACTION)) {
+            if (scriptlessBodyNode != null) {
+                // We're nested inside a node whose body is
+                // declared to be scriptless
+                throw new SAXParseException(
+                    Localizer.getMessage(
+                        "jsp.error.no.scriptlets",
+                        localName),
+                    locator);
+            }
+            node =
+                new Node.Scriptlet(
+                    qName,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(EXPRESSION_ACTION)) {
+            if (scriptlessBodyNode != null) {
+                // We're nested inside a node whose body is
+                // declared to be scriptless
+                throw new SAXParseException(
+                    Localizer.getMessage(
+                        "jsp.error.no.scriptlets",
+                        localName),
+                    locator);
+            }
+            node =
+                new Node.Expression(
+                    qName,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(USE_BEAN_ACTION)) {
+            node =
+                new Node.UseBean(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(SET_PROPERTY_ACTION)) {
+            node =
+                new Node.SetProperty(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(GET_PROPERTY_ACTION)) {
+            node =
+                new Node.GetProperty(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(INCLUDE_ACTION)) {
+            node =
+                new Node.IncludeAction(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(FORWARD_ACTION)) {
+            node =
+                new Node.ForwardAction(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(PARAM_ACTION)) {
+            node =
+                new Node.ParamAction(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(PARAMS_ACTION)) {
+            node =
+                new Node.ParamsAction(
+                    qName,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(PLUGIN_ACTION)) {
+            node =
+                new Node.PlugIn(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(TEXT_ACTION)) {
+            node =
+                new Node.JspText(
+                    qName,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(BODY_ACTION)) {
+            node =
+                new Node.JspBody(
+                    qName,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(ATTRIBUTE_ACTION)) {
+            node =
+                new Node.NamedAttribute(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(OUTPUT_ACTION)) {
+            node =
+                new Node.JspOutput(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(TAG_DIRECTIVE_ACTION)) {
+            if (!isTagFile) {
+                throw new SAXParseException(
+                    Localizer.getMessage(
+                        "jsp.error.action.isnottagfile",
+                        localName),
+                    locator);
+            }
+            node =
+                new Node.TagDirective(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+            String imports = nonTaglibAttrs.getValue("import");
+            // There can only be one 'import' attribute per tag directive
+            if (imports != null) {
+                ((Node.TagDirective)node).addImport(imports);
+            }
+        } else if (localName.equals(ATTRIBUTE_DIRECTIVE_ACTION)) {
+            if (!isTagFile) {
+                throw new SAXParseException(
+                    Localizer.getMessage(
+                        "jsp.error.action.isnottagfile",
+                        localName),
+                    locator);
+            }
+            node =
+                new Node.AttributeDirective(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(VARIABLE_DIRECTIVE_ACTION)) {
+            if (!isTagFile) {
+                throw new SAXParseException(
+                    Localizer.getMessage(
+                        "jsp.error.action.isnottagfile",
+                        localName),
+                    locator);
+            }
+            node =
+                new Node.VariableDirective(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(INVOKE_ACTION)) {
+            if (!isTagFile) {
+                throw new SAXParseException(
+                    Localizer.getMessage(
+                        "jsp.error.action.isnottagfile",
+                        localName),
+                    locator);
+            }
+            node =
+                new Node.InvokeAction(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(DOBODY_ACTION)) {
+            if (!isTagFile) {
+                throw new SAXParseException(
+                    Localizer.getMessage(
+                        "jsp.error.action.isnottagfile",
+                        localName),
+                    locator);
+            }
+            node =
+                new Node.DoBodyAction(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(ELEMENT_ACTION)) {
+            node =
+                new Node.JspElement(
+                    qName,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else if (localName.equals(FALLBACK_ACTION)) {
+            node =
+                new Node.FallBackAction(
+                    qName,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    current);
+        } else {
+            throw new SAXParseException(
+                Localizer.getMessage(
+                    "jsp.error.xml.badStandardAction",
+                    localName),
+                locator);
+        }
+
+        return node;
+    }
+
+    /*
+     * Checks if the XML element with the given tag name is a custom action,
+     * and returns the corresponding Node object.
+     */
+    private Node parseCustomAction(
+        String qName,
+        String localName,
+        String uri,
+        Attributes nonTaglibAttrs,
+        Attributes nonTaglibXmlnsAttrs,
+        Attributes taglibAttrs,
+        Mark start,
+        Node parent)
+        throws SAXException {
+
+        // Check if this is a user-defined (custom) tag
+        TagLibraryInfo tagLibInfo = pageInfo.getTaglib(uri);
+        if (tagLibInfo == null) {
+            return null;
+        }
+
+        TagInfo tagInfo = tagLibInfo.getTag(localName);
+        TagFileInfo tagFileInfo = tagLibInfo.getTagFile(localName);
+        if (tagInfo == null && tagFileInfo == null) {
+            throw new SAXException(
+                Localizer.getMessage("jsp.error.xml.bad_tag", localName, uri));
+        }
+        Class<?> tagHandlerClass = null;
+        if (tagInfo != null) {
+            String handlerClassName = tagInfo.getTagClassName();
+            try {
+                tagHandlerClass =
+                    ctxt.getClassLoader().loadClass(handlerClassName);
+            } catch (Exception e) {
+                throw new SAXException(
+                    Localizer.getMessage("jsp.error.loadclass.taghandler",
+                                         handlerClassName,
+                                         qName),
+                    e);
+            }
+        }
+
+        String prefix = getPrefix(qName);
+
+        Node.CustomTag ret = null;
+        if (tagInfo != null) {
+            ret =
+                new Node.CustomTag(
+                    qName,
+                    prefix,
+                    localName,
+                    uri,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    parent,
+                    tagInfo,
+                    tagHandlerClass);
+        } else {
+            ret =
+                new Node.CustomTag(
+                    qName,
+                    prefix,
+                    localName,
+                    uri,
+                    nonTaglibAttrs,
+                    nonTaglibXmlnsAttrs,
+                    taglibAttrs,
+                    start,
+                    parent,
+                    tagFileInfo);
+        }
+
+        return ret;
+    }
+
+    /*
+     * Creates the tag library associated with the given uri namespace, and
+     * returns it.
+     *
+     * @param prefix The prefix of the xmlns attribute
+     * @param uri The uri namespace (value of the xmlns attribute)
+     *
+     * @return The tag library associated with the given uri namespace
+     */
+    private TagLibraryInfo getTaglibInfo(String prefix, String uri)
+        throws JasperException {
+
+        TagLibraryInfo result = null;
+
+        if (uri.startsWith(URN_JSPTAGDIR)) {
+            // uri (of the form "urn:jsptagdir:path") references tag file dir
+            String tagdir = uri.substring(URN_JSPTAGDIR.length());
+            result =
+                new ImplicitTagLibraryInfo(
+                    ctxt,
+                    parserController,
+                    pageInfo,
+                    prefix,
+                    tagdir,
+                    err);
+        } else {
+            // uri references TLD file
+            boolean isPlainUri = false;
+            if (uri.startsWith(URN_JSPTLD)) {
+                // uri is of the form "urn:jsptld:path"
+                uri = uri.substring(URN_JSPTLD.length());
+            } else {
+                isPlainUri = true;
+            }
+
+            TldLocation location = ctxt.getTldLocation(uri);
+            if (location != null || !isPlainUri) {
+                if (ctxt.getOptions().isCaching()) {
+                    result = ctxt.getOptions().getCache().get(uri);
+                }
+                if (result == null) {
+                    /*
+                     * If the uri value is a plain uri, a translation error must
+                     * not be generated if the uri is not found in the taglib map.
+                     * Instead, any actions in the namespace defined by the uri
+                     * value must be treated as uninterpreted.
+                     */
+                    result =
+                        new TagLibraryInfoImpl(
+                            ctxt,
+                            parserController,
+                            pageInfo,
+                            prefix,
+                            uri,
+                            location,
+                            err,
+                            null);
+                    if (ctxt.getOptions().isCaching()) {
+                        ctxt.getOptions().getCache().put(uri, result);
+                    }
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /*
+     * Ensures that the given body only contains nodes that are instances of
+     * TemplateText.
+     *
+     * This check is performed only for the body of a scripting (that is:
+     * declaration, scriptlet, or expression) element, after the end tag of a
+     * scripting element has been reached.
+     */
+    private void checkScriptingBody(Node.ScriptingElement scriptingElem)
+        throws SAXException {
+        Node.Nodes body = scriptingElem.getBody();
+        if (body != null) {
+            int size = body.size();
+            for (int i = 0; i < size; i++) {
+                Node n = body.getNode(i);
+                if (!(n instanceof Node.TemplateText)) {
+                    String elemType = SCRIPTLET_ACTION;
+                    if (scriptingElem instanceof Node.Declaration)
+                        elemType = DECLARATION_ACTION;
+                    if (scriptingElem instanceof Node.Expression)
+                        elemType = EXPRESSION_ACTION;
+                    String msg =
+                        Localizer.getMessage(
+                            "jsp.error.parse.xml.scripting.invalid.body",
+                            elemType);
+                    throw new SAXException(msg);
+                }
+            }
+        }
+    }
+
+    /*
+     * Parses the given file included via an include directive.
+     *
+     * @param fname The path to the included resource, as specified by the
+     * 'file' attribute of the include directive
+     * @param parent The Node representing the include directive
+     */
+    private void processIncludeDirective(String fname, Node parent)
+        throws SAXException {
+
+        if (fname == null) {
+            return;
+        }
+
+        try {
+            parserController.parse(fname, parent, null);
+        } catch (FileNotFoundException fnfe) {
+            throw new SAXParseException(
+                Localizer.getMessage("jsp.error.file.not.found", fname),
+                locator,
+                fnfe);
+        } catch (Exception e) {
+            throw new SAXException(e);
+        }
+    }
+
+    /*
+     * Checks an element's given URI, qname, and attributes to see if any
+     * of them hijack the 'jsp' prefix, that is, bind it to a namespace other
+     * than http://java.sun.com/JSP/Page.
+     *
+     * @param uri The element's URI
+     * @param qName The element's qname
+     * @param attrs The element's attributes
+     */
+    private void checkPrefixes(String uri, String qName, Attributes attrs) {
+
+        checkPrefix(uri, qName);
+
+        int len = attrs.getLength();
+        for (int i = 0; i < len; i++) {
+            checkPrefix(attrs.getURI(i), attrs.getQName(i));
+        }
+    }
+
+    /*
+     * Checks the given URI and qname to see if they hijack the 'jsp' prefix,
+     * which would be the case if qName contained the 'jsp' prefix and
+     * uri was different from http://java.sun.com/JSP/Page.
+     *
+     * @param uri The URI to check
+     * @param qName The qname to check
+     */
+    private void checkPrefix(String uri, String qName) {
+
+        String prefix = getPrefix(qName);
+        if (prefix.length() > 0) {
+            pageInfo.addPrefix(prefix);
+            if ("jsp".equals(prefix) && !JSP_URI.equals(uri)) {
+                pageInfo.setIsJspPrefixHijacked(true);
+            }
+        }
+    }
+
+    private String getPrefix(String qName) {
+        int index = qName.indexOf(':');
+        if (index != -1) {
+            return qName.substring(0, index);
+        }
+        return "";
+    }
+
+    /*
+     * Gets SAXParser.
+     *
+     * @param validating Indicates whether the requested SAXParser should
+     * be validating
+     * @param jspDocParser The JSP document parser
+     *
+     * @return The SAXParser
+     */
+    private static SAXParser getSAXParser(
+        boolean validating,
+        JspDocumentParser jspDocParser)
+        throws Exception {
+
+        SAXParserFactory factory = SAXParserFactory.newInstance();
+        factory.setNamespaceAware(true);
+
+        // Preserve xmlns attributes
+        factory.setFeature(
+            "http://xml.org/sax/features/namespace-prefixes",
+            true);
+        factory.setValidating(validating);
+        //factory.setFeature(
+        //    "http://xml.org/sax/features/validation",
+        //    validating);
+        
+        // Configure the parser
+        SAXParser saxParser = factory.newSAXParser();
+        XMLReader xmlReader = saxParser.getXMLReader();
+        xmlReader.setProperty(LEXICAL_HANDLER_PROPERTY, jspDocParser);
+        xmlReader.setErrorHandler(jspDocParser);
+
+        return saxParser;
+    }
+
+    /*
+     * Exception indicating that a DOCTYPE declaration is present, but
+     * validation is turned off.
+     */
+    private static class EnableDTDValidationException
+            extends SAXParseException {
+
+        private static final long serialVersionUID = 1L;
+
+        EnableDTDValidationException(String message, Locator loc) {
+            super(message, loc);
+        }
+
+        @Override
+        public synchronized Throwable fillInStackTrace() {
+            // This class does not provide a stack trace
+            return this;
+        }
+    }
+
+    private static String getBodyType(Node.CustomTag custom) {
+
+        if (custom.getTagInfo() != null) {
+            return custom.getTagInfo().getBodyContent();
+        }
+
+        return custom.getTagFileInfo().getTagInfo().getBodyContent();
+    }
+
+    private boolean isTagDependent(Node n) {
+
+        if (n instanceof Node.CustomTag) {
+            String bodyType = getBodyType((Node.CustomTag) n);
+            return
+                TagInfo.BODY_CONTENT_TAG_DEPENDENT.equalsIgnoreCase(bodyType);
+        }
+        return false;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JspReader.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JspReader.java
new file mode 100644
index 0000000..09ae740
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JspReader.java
@@ -0,0 +1,626 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import java.io.CharArrayWriter;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.List;
+import java.util.Vector;
+import java.util.jar.JarFile;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.apache.jasper.util.ExceptionUtils;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * JspReader is an input buffer for the JSP parser. It should allow
+ * unlimited lookahead and pushback. It also has a bunch of parsing
+ * utility methods for understanding htmlesque thingies.
+ *
+ * @author Anil K. Vijendran
+ * @author Anselm Baird-Smith
+ * @author Harish Prabandham
+ * @author Rajiv Mordani
+ * @author Mandar Raje
+ * @author Danno Ferrin
+ * @author Kin-man Chung
+ * @author Shawn Bayern
+ * @author Mark Roth
+ */
+
+class JspReader {
+
+    /**
+     * Logger.
+     */
+    private final Log log = LogFactory.getLog(JspReader.class);
+
+    /**
+     * The current spot in the file.
+     */
+    private Mark current;
+
+    /**
+     * What is this?
+     */
+    private String master;
+
+    /**
+     * The list of source files.
+     */
+    private List<String> sourceFiles;
+
+    /**
+     * The current file ID (-1 indicates an error or no file).
+     */
+    private int currFileId;
+
+    /**
+     * Seems redundant.
+     */
+    private int size;
+
+    /**
+     * The compilation context.
+     */
+    private JspCompilationContext context;
+
+    /**
+     * The Jasper error dispatcher.
+     */
+    private ErrorDispatcher err;
+
+    /**
+     * Set to true when using the JspReader on a single file where we read up
+     * to the end and reset to the beginning many times.
+     * (as in ParserController.figureOutJspDocument()).
+     */
+    private boolean singleFile;
+
+    /**
+     * Constructor.
+     *
+     * @param ctxt The compilation context
+     * @param fname The file name
+     * @param encoding The file encoding
+     * @param jarFile ?
+     * @param err The error dispatcher
+     * @throws JasperException If a Jasper-internal error occurs
+     * @throws FileNotFoundException If the JSP file is not found (or is unreadable)
+     * @throws IOException If an IO-level error occurs, e.g. reading the file
+     */
+    public JspReader(JspCompilationContext ctxt,
+                     String fname,
+                     String encoding,
+                     JarFile jarFile,
+                     ErrorDispatcher err)
+            throws JasperException, FileNotFoundException, IOException {
+
+        this(ctxt, fname, encoding,
+             JspUtil.getReader(fname, encoding, jarFile, ctxt, err),
+             err);
+    }
+
+    /**
+     * Constructor: same as above constructor but with initialized reader
+     * to the file given.
+     */
+    public JspReader(JspCompilationContext ctxt,
+                     String fname,
+                     String encoding,
+                     InputStreamReader reader,
+                     ErrorDispatcher err)
+            throws JasperException {
+
+        this.context = ctxt;
+        this.err = err;
+        sourceFiles = new Vector<String>();
+        currFileId = 0;
+        size = 0;
+        singleFile = false;
+        pushFile(fname, encoding, reader);
+    }
+
+    /**
+     * @return JSP compilation context with which this JspReader is 
+     * associated
+     */
+    JspCompilationContext getJspCompilationContext() {
+        return context;
+    }
+    
+    /**
+     * Returns the file at the given position in the list.
+     *
+     * @param fileid The file position in the list
+     * @return The file at that position, if found, null otherwise
+     */
+    String getFile(final int fileid) {
+        return sourceFiles.get(fileid);
+    }
+       
+    /**
+     * Checks if the current file has more input.
+     *
+     * @return True if more reading is possible
+     * @throws JasperException if an error occurs
+     */ 
+    boolean hasMoreInput() throws JasperException {
+        if (current.cursor >= current.stream.length) {
+            if (singleFile) return false; 
+            while (popFile()) {
+                if (current.cursor < current.stream.length) return true;
+            }
+            return false;
+        }
+        return true;
+    }
+    
+    int nextChar() throws JasperException {
+        if (!hasMoreInput())
+            return -1;
+        
+        int ch = current.stream[current.cursor];
+
+        current.cursor++;
+        
+        if (ch == '\n') {
+            current.line++;
+            current.col = 0;
+        } else {
+            current.col++;
+        }
+        return ch;
+    }
+
+    /**
+     * Back up the current cursor by one char, assumes current.cursor > 0,
+     * and that the char to be pushed back is not '\n'.
+     */
+    void pushChar() {
+        current.cursor--;
+        current.col--;
+    }
+
+    String getText(Mark start, Mark stop) throws JasperException {
+        Mark oldstart = mark();
+        reset(start);
+        CharArrayWriter caw = new CharArrayWriter();
+        while (!stop.equals(mark()))
+            caw.write(nextChar());
+        caw.close();
+        reset(oldstart);
+        return caw.toString();
+    }
+
+    int peekChar() throws JasperException {
+        if (!hasMoreInput())
+            return -1;
+        return current.stream[current.cursor];
+    }
+
+    Mark mark() {
+        return new Mark(current);
+    }
+
+    void reset(Mark mark) {
+        current = new Mark(mark);
+    }
+
+    /**
+     * search the stream for a match to a string
+     * @param string The string to match
+     * @return <strong>true</strong> is one is found, the current position
+     *         in stream is positioned after the search string, <strong>
+     *               false</strong> otherwise, position in stream unchanged.
+     */
+    boolean matches(String string) throws JasperException {
+        Mark mark = mark();
+        int ch = 0;
+        int i = 0;
+        do {
+            ch = nextChar();
+            if (((char) ch) != string.charAt(i++)) {
+                reset(mark);
+                return false;
+            }
+        } while (i < string.length());
+        return true;
+    }
+
+    boolean matchesETag(String tagName) throws JasperException {
+        Mark mark = mark();
+
+        if (!matches("</" + tagName))
+            return false;
+        skipSpaces();
+        if (nextChar() == '>')
+            return true;
+
+        reset(mark);
+        return false;
+    }
+
+    boolean matchesETagWithoutLessThan(String tagName)
+        throws JasperException
+    {
+       Mark mark = mark();
+
+       if (!matches("/" + tagName))
+           return false;
+       skipSpaces();
+       if (nextChar() == '>')
+           return true;
+
+       reset(mark);
+       return false;
+    }
+
+
+    /**
+     * Looks ahead to see if there are optional spaces followed by
+     * the given String.  If so, true is returned and those spaces and
+     * characters are skipped.  If not, false is returned and the
+     * position is restored to where we were before.
+     */
+    boolean matchesOptionalSpacesFollowedBy( String s )
+        throws JasperException
+    {
+        Mark mark = mark();
+
+        skipSpaces();
+        boolean result = matches( s );
+        if( !result ) {
+            reset( mark );
+        }
+
+        return result;
+    }
+
+    int skipSpaces() throws JasperException {
+        int i = 0;
+        while (hasMoreInput() && isSpace()) {
+            i++;
+            nextChar();
+        }
+        return i;
+    }
+
+    /**
+     * Skip until the given string is matched in the stream.
+     * When returned, the context is positioned past the end of the match.
+     *
+     * @param s The String to match.
+     * @return A non-null <code>Mark</code> instance (positioned immediately
+     *         before the search string) if found, <strong>null</strong>
+     *         otherwise.
+     */
+    Mark skipUntil(String limit) throws JasperException {
+        Mark ret = null;
+        int limlen = limit.length();
+        int ch;
+
+    skip:
+        for (ret = mark(), ch = nextChar() ; ch != -1 ;
+                 ret = mark(), ch = nextChar()) {
+            if (ch == limit.charAt(0)) {
+                Mark restart = mark();
+                for (int i = 1 ; i < limlen ; i++) {
+                    if (peekChar() == limit.charAt(i))
+                        nextChar();
+                    else {
+                        reset(restart);
+                        continue skip;
+                    }
+                }
+                return ret;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Skip until the given string is matched in the stream, but ignoring
+     * chars initially escaped by a '\'.
+     * When returned, the context is positioned past the end of the match.
+     *
+     * @param s The String to match.
+     * @return A non-null <code>Mark</code> instance (positioned immediately
+     *         before the search string) if found, <strong>null</strong>
+     *         otherwise.
+     */
+    Mark skipUntilIgnoreEsc(String limit) throws JasperException {
+        Mark ret = null;
+        int limlen = limit.length();
+        int ch;
+        int prev = 'x';        // Doesn't matter
+        
+    skip:
+        for (ret = mark(), ch = nextChar() ; ch != -1 ;
+                 ret = mark(), prev = ch, ch = nextChar()) {            
+            if (ch == '\\' && prev == '\\') {
+                ch = 0;                // Double \ is not an escape char anymore
+            }
+            else if (ch == limit.charAt(0) && prev != '\\') {
+                for (int i = 1 ; i < limlen ; i++) {
+                    if (peekChar() == limit.charAt(i))
+                        nextChar();
+                    else
+                        continue skip;
+                }
+                return ret;
+            }
+        }
+        return null;
+    }
+    
+    /**
+     * Skip until the given end tag is matched in the stream.
+     * When returned, the context is positioned past the end of the tag.
+     *
+     * @param tag The name of the tag whose ETag (</tag>) to match.
+     * @return A non-null <code>Mark</code> instance (positioned immediately
+     *               before the ETag) if found, <strong>null</strong> otherwise.
+     */
+    Mark skipUntilETag(String tag) throws JasperException {
+        Mark ret = skipUntil("</" + tag);
+        if (ret != null) {
+            skipSpaces();
+            if (nextChar() != '>')
+                ret = null;
+        }
+        return ret;
+    }
+
+    final boolean isSpace() throws JasperException {
+        // Note: If this logic changes, also update Node.TemplateText.rtrim()
+        return peekChar() <= ' ';
+    }
+
+    /**
+     * Parse a space delimited token.
+     * If quoted the token will consume all characters up to a matching quote,
+     * otherwise, it consumes up to the first delimiter character.
+     *
+     * @param quoted If <strong>true</strong> accept quoted strings.
+     */
+    String parseToken(boolean quoted) throws JasperException {
+        StringBuilder StringBuilder = new StringBuilder();
+        skipSpaces();
+        StringBuilder.setLength(0);
+        
+        if (!hasMoreInput()) {
+            return "";
+        }
+
+        int ch = peekChar();
+        
+        if (quoted) {
+            if (ch == '"' || ch == '\'') {
+
+                char endQuote = ch == '"' ? '"' : '\'';
+                // Consume the open quote: 
+                ch = nextChar();
+                for (ch = nextChar(); ch != -1 && ch != endQuote;
+                         ch = nextChar()) {
+                    if (ch == '\\') 
+                        ch = nextChar();
+                    StringBuilder.append((char) ch);
+                }
+                // Check end of quote, skip closing quote:
+                if (ch == -1) {
+                    err.jspError(mark(), "jsp.error.quotes.unterminated");
+                }
+            } else {
+                err.jspError(mark(), "jsp.error.attr.quoted");
+            }
+        } else {
+            if (!isDelimiter()) {
+                // Read value until delimiter is found:
+                do {
+                    ch = nextChar();
+                    // Take care of the quoting here.
+                    if (ch == '\\') {
+                        if (peekChar() == '"' || peekChar() == '\'' ||
+                               peekChar() == '>' || peekChar() == '%')
+                            ch = nextChar();
+                    }
+                    StringBuilder.append((char) ch);
+                } while (!isDelimiter());
+            }
+        }
+
+        return StringBuilder.toString();
+    }
+
+    void setSingleFile(boolean val) {
+        singleFile = val;
+    }
+
+
+    /**
+     * Parse utils - Is current character a token delimiter ?
+     * Delimiters are currently defined to be =, &gt;, &lt;, ", and ' or any
+     * any space character as defined by <code>isSpace</code>.
+     *
+     * @return A boolean.
+     */
+    private boolean isDelimiter() throws JasperException {
+        if (! isSpace()) {
+            int ch = peekChar();
+            // Look for a single-char work delimiter:
+            if (ch == '=' || ch == '>' || ch == '"' || ch == '\''
+                    || ch == '/') {
+                return true;
+            }
+            // Look for an end-of-comment or end-of-tag:                
+            if (ch == '-') {
+                Mark mark = mark();
+                if (((ch = nextChar()) == '>')
+                        || ((ch == '-') && (nextChar() == '>'))) {
+                    reset(mark);
+                    return true;
+                } else {
+                    reset(mark);
+                    return false;
+                }
+            }
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * Register a new source file.
+     * This method is used to implement file inclusion. Each included file
+     * gets a unique identifier (which is the index in the array of source
+     * files).
+     *
+     * @return The index of the now registered file.
+     */
+    private int registerSourceFile(final String file) {
+        if (sourceFiles.contains(file)) {
+            return -1;
+        }
+
+        sourceFiles.add(file);
+        this.size++;
+
+        return sourceFiles.size() - 1;
+    }
+    
+
+    /**
+     * Unregister the source file.
+     * This method is used to implement file inclusion. Each included file
+     * gets a unique identifier (which is the index in the array of source
+     * files).
+     *
+     * @return The index of the now registered file.
+     */
+    private int unregisterSourceFile(final String file) {
+        if (!sourceFiles.contains(file)) {
+            return -1;
+        }
+
+        sourceFiles.remove(file);
+        this.size--;
+        return sourceFiles.size() - 1;
+    }
+
+    /**
+     * Push a file (and its associated Stream) on the file stack.  THe
+     * current position in the current file is remembered.
+     */
+    private void pushFile(String file, String encoding, 
+                           InputStreamReader reader) throws JasperException {
+
+        // Register the file
+        String longName = file;
+
+        int fileid = registerSourceFile(longName);
+
+        if (fileid == -1) {
+            // Bugzilla 37407: http://issues.apache.org/bugzilla/show_bug.cgi?id=37407
+            if(reader != null) {
+                try {
+                    reader.close();
+                } catch (Exception any) {
+                    if(log.isDebugEnabled()) {
+                        log.debug("Exception closing reader: ", any);
+                    }
+                }
+            }
+
+            err.jspError("jsp.error.file.already.registered", file);
+        }
+
+        currFileId = fileid;
+
+        try {
+            CharArrayWriter caw = new CharArrayWriter();
+            char buf[] = new char[1024];
+            for (int i = 0 ; (i = reader.read(buf)) != -1 ;)
+                caw.write(buf, 0, i);
+            caw.close();
+            if (current == null) {
+                current = new Mark(this, caw.toCharArray(), fileid, 
+                                   getFile(fileid), master, encoding);
+            } else {
+                current.pushStream(caw.toCharArray(), fileid, getFile(fileid),
+                                   longName, encoding);
+            }
+        } catch (Throwable ex) {
+            ExceptionUtils.handleThrowable(ex);
+            log.error("Exception parsing file ", ex);
+            // Pop state being constructed:
+            popFile();
+            err.jspError("jsp.error.file.cannot.read", file);
+        } finally {
+            if (reader != null) {
+                try {
+                    reader.close();
+                } catch (Exception any) {
+                    if(log.isDebugEnabled()) {
+                        log.debug("Exception closing reader: ", any);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Pop a file from the file stack.  The field "current" is retored
+     * to the value to point to the previous files, if any, and is set
+     * to null otherwise.
+     * @return true is there is a previous file on the stack.
+     *         false otherwise.
+     */
+    private boolean popFile() throws JasperException {
+
+        // Is stack created ? (will happen if the Jsp file we're looking at is
+        // missing.
+        if (current == null || currFileId < 0) {
+            return false;
+        }
+
+        // Restore parser state:
+        String fName = getFile(currFileId);
+        currFileId = unregisterSourceFile(fName);
+        if (currFileId < -1) {
+            err.jspError("jsp.error.file.not.registered", fName);
+        }
+
+        Mark previous = current.popStream();
+        if (previous != null) {
+            master = current.baseDir;
+            current = previous;
+            return true;
+        }
+        // Note that although the current file is undefined here, "current"
+        // is not set to null just for convenience, for it maybe used to
+        // set the current (undefined) position.
+        return false;
+    }
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JspRuntimeContext.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JspRuntimeContext.java
new file mode 100644
index 0000000..3ebe6b6
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JspRuntimeContext.java
@@ -0,0 +1,618 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FilePermission;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.CodeSource;
+import java.security.PermissionCollection;
+import java.security.Policy;
+import java.security.cert.Certificate;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.ServletContext;
+import javax.servlet.jsp.JspFactory;
+
+import org.apache.jasper.Constants;
+import org.apache.jasper.JspCompilationContext;
+import org.apache.jasper.Options;
+import org.apache.jasper.runtime.JspFactoryImpl;
+import org.apache.jasper.security.SecurityClassLoad;
+import org.apache.jasper.servlet.JspServletWrapper;
+import org.apache.jasper.util.ExceptionUtils;
+import org.apache.jasper.util.FastRemovalDequeue;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+
+/**
+ * Class for tracking JSP compile time file dependencies when the
+ * &060;%@include file="..."%&062; directive is used.
+ *
+ * A background thread periodically checks the files a JSP page
+ * is dependent upon.  If a dependent file changes the JSP page
+ * which included it is recompiled.
+ *
+ * Only used if a web application context is a directory.
+ *
+ * @author Glenn L. Nielsen
+ * @version $Revision: 1.1 $
+ */
+public final class JspRuntimeContext {
+
+    // Logger
+    private final Log log = LogFactory.getLog(JspRuntimeContext.class);
+
+    /*
+     * Counts how many times the webapp's JSPs have been reloaded.
+     */
+    private AtomicInteger jspReloadCount = new AtomicInteger(0);
+
+    /*
+     * Counts how many times JSPs have been unloaded in this webapp.
+     */
+    private AtomicInteger jspUnloadCount = new AtomicInteger(0);
+
+    /**
+     * Preload classes required at runtime by a JSP servlet so that
+     * we don't get a defineClassInPackage security exception.
+     */
+    static {
+        JspFactoryImpl factory = new JspFactoryImpl();
+        SecurityClassLoad.securityClassLoad(factory.getClass().getClassLoader());
+        if( System.getSecurityManager() != null ) {
+            String basePackage = "org.apache.jasper.";
+            try {
+                factory.getClass().getClassLoader().loadClass( basePackage +
+                                                               "runtime.JspFactoryImpl$PrivilegedGetPageContext");
+                factory.getClass().getClassLoader().loadClass( basePackage +
+                                                               "runtime.JspFactoryImpl$PrivilegedReleasePageContext");
+                factory.getClass().getClassLoader().loadClass( basePackage +
+                                                               "runtime.JspRuntimeLibrary");
+                factory.getClass().getClassLoader().loadClass( basePackage +
+                                                               "runtime.JspRuntimeLibrary$PrivilegedIntrospectHelper");
+                factory.getClass().getClassLoader().loadClass( basePackage +
+                                                               "runtime.ServletResponseWrapperInclude");
+                factory.getClass().getClassLoader().loadClass( basePackage +
+                                                               "servlet.JspServletWrapper");
+            } catch (ClassNotFoundException ex) {
+                throw new IllegalStateException(ex);
+            }
+        }
+
+        JspFactory.setDefaultFactory(factory);
+    }
+
+    // ----------------------------------------------------------- Constructors
+
+    /**
+     * Create a JspRuntimeContext for a web application context.
+     *
+     * Loads in any previously generated dependencies from file.
+     *
+     * @param context ServletContext for web application
+     */
+    public JspRuntimeContext(ServletContext context, Options options) {
+
+        this.context = context;
+        this.options = options;
+
+        // Get the parent class loader
+        ClassLoader loader = Thread.currentThread().getContextClassLoader();
+        if (loader == null) {
+            loader = this.getClass().getClassLoader();
+        }
+
+        if (log.isDebugEnabled()) {
+            if (loader != null) {
+                log.debug(Localizer.getMessage("jsp.message.parent_class_loader_is",
+                                               loader.toString()));
+            } else {
+                log.debug(Localizer.getMessage("jsp.message.parent_class_loader_is",
+                                               "<none>"));
+            }
+        }
+
+        parentClassLoader =  loader;
+        classpath = initClassPath();
+
+        if (context instanceof org.apache.jasper.servlet.JspCServletContext) {
+            codeSource = null;
+            permissionCollection = null;
+            return;
+        }
+
+        if (Constants.IS_SECURITY_ENABLED) {
+            SecurityHolder holder = initSecurity();
+            codeSource = holder.cs;
+            permissionCollection = holder.pc;
+        } else {
+            codeSource = null;
+            permissionCollection = null;
+        }
+
+        // If this web application context is running from a
+        // directory, start the background compilation thread
+        String appBase = context.getRealPath("/");         
+        if (!options.getDevelopment()
+                && appBase != null
+                && options.getCheckInterval() > 0) {
+            lastCompileCheck = System.currentTimeMillis();
+        }                                            
+
+        if (options.getMaxLoadedJsps() > 0) {
+            jspQueue = new FastRemovalDequeue<JspServletWrapper>(options.getMaxLoadedJsps());
+            if (log.isDebugEnabled()) {
+                log.debug(Localizer.getMessage("jsp.message.jsp_queue_created",
+                                               "" + options.getMaxLoadedJsps(), context.getContextPath()));
+            }
+        }
+
+        /* Init parameter is in seconds, locally we use milliseconds */
+        jspIdleTimeout = options.getJspIdleTimeout() * 1000;
+    }
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * This web applications ServletContext
+     */
+    private final ServletContext context;
+    private final Options options;
+    private final ClassLoader parentClassLoader;
+    private final PermissionCollection permissionCollection;
+    private final CodeSource codeSource;                    
+    private final String classpath;
+    private volatile long lastCompileCheck = -1L;
+    private volatile long lastJspQueueUpdate = System.currentTimeMillis();
+    /* JSP idle timeout in milliseconds */
+    private long jspIdleTimeout;
+
+    /**
+     * Maps JSP pages to their JspServletWrapper's
+     */
+    private Map<String, JspServletWrapper> jsps = new ConcurrentHashMap<String, JspServletWrapper>();
+
+    /**
+     * Keeps JSP pages ordered by last access. 
+     */
+    private FastRemovalDequeue<JspServletWrapper> jspQueue = null;
+
+    // ------------------------------------------------------ Public Methods
+
+    /**
+     * Add a new JspServletWrapper.
+     *
+     * @param jspUri JSP URI
+     * @param jsw Servlet wrapper for JSP
+     */
+    public void addWrapper(String jspUri, JspServletWrapper jsw) {
+        jsps.put(jspUri, jsw);
+    }
+
+    /**
+     * Get an already existing JspServletWrapper.
+     *
+     * @param jspUri JSP URI
+     * @return JspServletWrapper for JSP
+     */
+    public JspServletWrapper getWrapper(String jspUri) {
+        return jsps.get(jspUri);
+    }
+
+    /**
+     * Remove a  JspServletWrapper.
+     *
+     * @param jspUri JSP URI of JspServletWrapper to remove
+     */
+    public void removeWrapper(String jspUri) {
+        jsps.remove(jspUri);
+    }
+
+    /**
+     * Push a newly compiled JspServletWrapper into the queue at first
+     * execution of jsp. Destroy any JSP that has been replaced in the queue.
+     *
+     * @param jsw Servlet wrapper for jsp.
+     * @return an unloadHandle that can be pushed to front of queue at later execution times.
+     * */
+    public FastRemovalDequeue<JspServletWrapper>.Entry push(JspServletWrapper jsw) {
+        if (log.isTraceEnabled()) {
+            log.trace(Localizer.getMessage("jsp.message.jsp_added",
+                                           jsw.getJspUri(), context.getContextPath()));
+        }
+        FastRemovalDequeue<JspServletWrapper>.Entry entry = jspQueue.push(jsw);
+        JspServletWrapper replaced = entry.getReplaced();
+        if (replaced != null) {
+            if (log.isDebugEnabled()) {
+                log.debug(Localizer.getMessage("jsp.message.jsp_removed_excess",
+                                               replaced.getJspUri(), context.getContextPath()));
+            }
+            unloadJspServletWrapper(replaced);
+        }
+        return entry;
+    }
+    
+    /**
+     * Push unloadHandle for JspServletWrapper to front of the queue.
+     *
+     * @param unloadHandle the unloadHandle for the jsp.
+     * */
+    public void makeYoungest(FastRemovalDequeue<JspServletWrapper>.Entry unloadHandle) {
+        if (log.isTraceEnabled()) {
+            JspServletWrapper jsw = unloadHandle.getContent();
+            log.trace(Localizer.getMessage("jsp.message.jsp_queue_update",
+                                           jsw.getJspUri(), context.getContextPath()));
+        }
+        jspQueue.moveFirst(unloadHandle);
+    }
+    
+    /**
+     * Returns the number of JSPs for which JspServletWrappers exist, i.e.,
+     * the number of JSPs that have been loaded into the webapp.
+     *
+     * @return The number of JSPs that have been loaded into the webapp
+     */
+    public int getJspCount() {
+        return jsps.size();
+    }
+
+    /**
+     * Get the SecurityManager Policy CodeSource for this web
+     * application context.
+     *
+     * @return CodeSource for JSP
+     */
+    public CodeSource getCodeSource() {
+        return codeSource;
+    }
+
+    /**
+     * Get the parent ClassLoader.
+     *
+     * @return ClassLoader parent
+     */
+    public ClassLoader getParentClassLoader() {
+        return parentClassLoader;
+    }
+
+    /**
+     * Get the SecurityManager PermissionCollection for this
+     * web application context.
+     *
+     * @return PermissionCollection permissions
+     */
+    public PermissionCollection getPermissionCollection() {
+        return permissionCollection;
+    }
+
+    /**
+     * Process a "destroy" event for this web application context.
+     */                                                        
+    public void destroy() {
+        Iterator<JspServletWrapper> servlets = jsps.values().iterator();
+        while (servlets.hasNext()) {
+            servlets.next().destroy();
+        }
+    }
+
+    /**
+     * Increments the JSP reload counter.
+     */
+    public void incrementJspReloadCount() {
+        jspReloadCount.incrementAndGet();
+    }
+
+    /**
+     * Resets the JSP reload counter.
+     *
+     * @param count Value to which to reset the JSP reload counter
+     */
+    public void setJspReloadCount(int count) {
+        jspReloadCount.set(count);
+    }
+
+    /**
+     * Gets the current value of the JSP reload counter.
+     *
+     * @return The current value of the JSP reload counter
+     */
+    public int getJspReloadCount() {
+        return jspReloadCount.intValue();
+    }
+
+    /**
+     * Gets the number of JSPs that are in the JSP limiter queue
+     *
+     * @return The number of JSPs (in the webapp with which this JspServlet is
+     * associated) that are in the JSP limiter queue
+     */
+    public int getJspQueueLength() {
+        if (jspQueue != null) {
+            return jspQueue.getSize();
+        }
+        return -1;
+    }
+
+    /**
+     * Increments the JSP unload counter.
+     */
+    public void incrementJspUnloadCount() {
+        jspUnloadCount.incrementAndGet();
+    }
+
+    /**
+     * Gets the number of JSPs that have been unloaded.
+     *
+     * @return The number of JSPs (in the webapp with which this JspServlet is
+     * associated) that have been unloaded
+     */
+    public int getJspUnloadCount() {
+        return jspUnloadCount.intValue();
+    }
+
+
+    /**
+     * Method used by background thread to check the JSP dependencies
+     * registered with this class for JSP's.
+     */
+    public void checkCompile() {
+
+        if (lastCompileCheck < 0) {
+            // Checking was disabled
+            return;
+        }
+        long now = System.currentTimeMillis();
+        if (now > (lastCompileCheck + (options.getCheckInterval() * 1000L))) {
+            lastCompileCheck = now;
+        } else {
+            return;
+        }
+        
+        Object [] wrappers = jsps.values().toArray();
+        for (int i = 0; i < wrappers.length; i++ ) {
+            JspServletWrapper jsw = (JspServletWrapper)wrappers[i];
+            JspCompilationContext ctxt = jsw.getJspEngineContext();
+            // JspServletWrapper also synchronizes on this when
+            // it detects it has to do a reload
+            synchronized(jsw) {
+                try {
+                    ctxt.compile();
+                } catch (FileNotFoundException ex) {
+                    ctxt.incrementRemoved();
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    jsw.getServletContext().log("Background compile failed",
+                                                t);
+                }
+            }
+        }
+
+    }
+
+    /**
+     * The classpath that is passed off to the Java compiler.
+     */
+    public String getClassPath() {
+        return classpath;
+    }
+
+    /**
+     * Last time the update background task has run
+     */
+    public long getLastJspQueueUpdate() {
+        return lastJspQueueUpdate;
+    }
+
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Method used to initialize classpath for compiles.
+     */
+    private String initClassPath() {
+
+        StringBuilder cpath = new StringBuilder();
+        String sep = System.getProperty("path.separator");
+
+        if (parentClassLoader instanceof URLClassLoader) {
+            URL [] urls = ((URLClassLoader)parentClassLoader).getURLs();
+    
+            for(int i = 0; i < urls.length; i++) {
+                // Tomcat 4 can use URL's other than file URL's,
+                // a protocol other than file: will generate a
+                // bad file system path, so only add file:
+                // protocol URL's to the classpath.
+                
+                if( urls[i].getProtocol().equals("file") ) {
+                    cpath.append(urls[i].getFile()+sep);
+                }
+            }
+        }
+
+        cpath.append(options.getScratchDir() + sep);
+
+        String cp = (String) context.getAttribute(Constants.SERVLET_CLASSPATH);
+        if (cp == null || cp.equals("")) {
+            cp = options.getClassPath();
+        }
+
+        String path = cpath.toString() + cp;
+
+        if(log.isDebugEnabled()) {
+            log.debug("Compilation classpath initialized: " + path);
+        }
+        return path;
+    }
+
+    // Helper class to allow initSecurity() to return two items
+    private static class SecurityHolder{
+        private final CodeSource cs;
+        private final PermissionCollection pc;
+        private SecurityHolder(CodeSource cs, PermissionCollection pc){
+            this.cs = cs;
+            this.pc = pc;
+        }
+    }
+    /**
+     * Method used to initialize SecurityManager data.
+     */
+    private SecurityHolder initSecurity() {
+
+        // Setup the PermissionCollection for this web app context
+        // based on the permissions configured for the root of the
+        // web app context directory, then add a file read permission
+        // for that directory.
+        Policy policy = Policy.getPolicy();
+        CodeSource source = null;
+        PermissionCollection permissions = null;
+        if( policy != null ) {
+            try {          
+                // Get the permissions for the web app context
+                String docBase = context.getRealPath("/");
+                if( docBase == null ) {
+                    docBase = options.getScratchDir().toString();
+                }
+                String codeBase = docBase;
+                if (!codeBase.endsWith(File.separator)){
+                    codeBase = codeBase + File.separator;
+                }
+                File contextDir = new File(codeBase);
+                URL url = contextDir.getCanonicalFile().toURI().toURL();
+                source = new CodeSource(url,(Certificate[])null);
+                permissions = policy.getPermissions(source);
+
+                // Create a file read permission for web app context directory
+                if (!docBase.endsWith(File.separator)){
+                    permissions.add
+                        (new FilePermission(docBase,"read"));
+                    docBase = docBase + File.separator;
+                } else {
+                    permissions.add
+                        (new FilePermission
+                            (docBase.substring(0,docBase.length() - 1),"read"));
+                }
+                docBase = docBase + "-";
+                permissions.add(new FilePermission(docBase,"read"));
+
+                // Spec says apps should have read/write for their temp
+                // directory. This is fine, as no security sensitive files, at
+                // least any that the app doesn't have full control of anyway,
+                // will be written here.
+                String workDir = options.getScratchDir().toString();
+                if (!workDir.endsWith(File.separator)){
+                    permissions.add
+                        (new FilePermission(workDir,"read,write"));
+                    workDir = workDir + File.separator;
+                }
+                workDir = workDir + "-";
+                permissions.add(new FilePermission(
+                        workDir,"read,write,delete"));
+
+                // Allow the JSP to access org.apache.jasper.runtime.HttpJspBase
+                permissions.add( new RuntimePermission(
+                    "accessClassInPackage.org.apache.jasper.runtime") );
+
+                if (parentClassLoader instanceof URLClassLoader) {
+                    URL [] urls = ((URLClassLoader)parentClassLoader).getURLs();
+                    String jarUrl = null;
+                    String jndiUrl = null;
+                    for (int i=0; i<urls.length; i++) {
+                        if (jndiUrl == null
+                                && urls[i].toString().startsWith("jndi:") ) {
+                            jndiUrl = urls[i].toString() + "-";
+                        }
+                        if (jarUrl == null
+                                && urls[i].toString().startsWith("jar:jndi:")
+                                ) {
+                            jarUrl = urls[i].toString();
+                            jarUrl = jarUrl.substring(0,jarUrl.length() - 2);
+                            jarUrl = jarUrl.substring(0,
+                                     jarUrl.lastIndexOf('/')) + "/-";
+                        }
+                    }
+                    if (jarUrl != null) {
+                        permissions.add(
+                                new FilePermission(jarUrl,"read"));
+                        permissions.add(
+                                new FilePermission(jarUrl.substring(4),"read"));
+                    }
+                    if (jndiUrl != null)
+                        permissions.add(
+                                new FilePermission(jndiUrl,"read") );
+                }
+            } catch(Exception e) {
+                context.log("Security Init for context failed",e);
+            }
+        }
+        return new SecurityHolder(source, permissions);
+    }
+
+    private void unloadJspServletWrapper(JspServletWrapper jsw) {
+        removeWrapper(jsw.getJspUri());
+        synchronized(jsw) {
+            jsw.destroy();
+        }
+        jspUnloadCount.incrementAndGet();
+    }
+
+
+    /**
+     * Method used by background thread to check if any JSP's should be unloaded.
+     */
+    public void checkUnload() {
+
+        if (log.isTraceEnabled()) {
+            int queueLength = -1;
+            if (jspQueue != null) {
+                queueLength = jspQueue.getSize();
+            }
+            log.trace(Localizer.getMessage("jsp.message.jsp_unload_check",
+                                           context.getContextPath(), "" + jsps.size(), "" + queueLength));
+        }
+        long now = System.currentTimeMillis();
+        if (jspIdleTimeout > 0) {
+            long unloadBefore = now - jspIdleTimeout;
+            Object [] wrappers = jsps.values().toArray();
+            for (int i = 0; i < wrappers.length; i++ ) {
+                JspServletWrapper jsw = (JspServletWrapper)wrappers[i];
+                synchronized(jsw) {
+                    if (jsw.getLastUsageTime() < unloadBefore) {
+                        if (log.isDebugEnabled()) {
+                            log.debug(Localizer.getMessage("jsp.message.jsp_removed_idle",
+                                                           jsw.getJspUri(), context.getContextPath(),
+                                                           "" + (now-jsw.getLastUsageTime())));
+                        }
+                        if (jspQueue != null) {
+                            jspQueue.remove(jsw.getUnloadHandle());
+                        }
+                        unloadJspServletWrapper(jsw);
+                    }
+                }
+            }
+        }
+        lastJspQueueUpdate = now;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JspUtil.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JspUtil.java
new file mode 100644
index 0000000..7d693cc
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/JspUtil.java
@@ -0,0 +1,937 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.util.Vector;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.xml.sax.Attributes;
+
+/**
+ * This class has all the utility method(s). Ideally should move all the bean
+ * containers here.
+ * 
+ * @author Mandar Raje.
+ * @author Rajiv Mordani.
+ * @author Danno Ferrin
+ * @author Pierre Delisle
+ * @author Shawn Bayern
+ * @author Mark Roth
+ */
+public class JspUtil {
+
+    private static final String WEB_INF_TAGS = "/WEB-INF/tags/";
+    private static final String META_INF_TAGS = "/META-INF/tags/";
+
+    // Delimiters for request-time expressions (JSP and XML syntax)
+    private static final String OPEN_EXPR = "<%=";
+    private static final String CLOSE_EXPR = "%>";
+
+    private static final String javaKeywords[] = { "abstract", "assert",
+            "boolean", "break", "byte", "case", "catch", "char", "class",
+            "const", "continue", "default", "do", "double", "else", "enum",
+            "extends", "final", "finally", "float", "for", "goto", "if",
+            "implements", "import", "instanceof", "int", "interface", "long",
+            "native", "new", "package", "private", "protected", "public",
+            "return", "short", "static", "strictfp", "super", "switch",
+            "synchronized", "this", "throw", "throws", "transient", "try",
+            "void", "volatile", "while" };
+
+    public static final int CHUNKSIZE = 1024;
+
+    /**
+     * Takes a potential expression and converts it into XML form
+     */
+    public static String getExprInXml(String expression) {
+        String returnString;
+        int length = expression.length();
+
+        if (expression.startsWith(OPEN_EXPR) &&
+                expression.endsWith(CLOSE_EXPR)) {
+            returnString = expression.substring(1, length - 1);
+        } else {
+            returnString = expression;
+        }
+
+        return escapeXml(returnString);
+    }
+
+    /**
+     * Checks to see if the given scope is valid.
+     * 
+     * @param scope
+     *            The scope to be checked
+     * @param n
+     *            The Node containing the 'scope' attribute whose value is to be
+     *            checked
+     * @param err
+     *            error dispatcher
+     * 
+     * @throws JasperException
+     *             if scope is not null and different from &quot;page&quot;,
+     *             &quot;request&quot;, &quot;session&quot;, and
+     *             &quot;application&quot;
+     */
+    public static void checkScope(String scope, Node n, ErrorDispatcher err)
+            throws JasperException {
+        if (scope != null && !scope.equals("page") && !scope.equals("request")
+                && !scope.equals("session") && !scope.equals("application")) {
+            err.jspError(n, "jsp.error.invalid.scope", scope);
+        }
+    }
+
+    /**
+     * Checks if all mandatory attributes are present and if all attributes
+     * present have valid names. Checks attributes specified as XML-style
+     * attributes as well as attributes specified using the jsp:attribute
+     * standard action.
+     */
+    public static void checkAttributes(String typeOfTag, Node n,
+            ValidAttribute[] validAttributes, ErrorDispatcher err)
+            throws JasperException {
+        Attributes attrs = n.getAttributes();
+        Mark start = n.getStart();
+        boolean valid = true;
+
+        // AttributesImpl.removeAttribute is broken, so we do this...
+        int tempLength = (attrs == null) ? 0 : attrs.getLength();
+        Vector<String> temp = new Vector<String>(tempLength, 1);
+        for (int i = 0; i < tempLength; i++) {
+            String qName = attrs.getQName(i);
+            if ((!qName.equals("xmlns")) && (!qName.startsWith("xmlns:")))
+                temp.addElement(qName);
+        }
+
+        // Add names of attributes specified using jsp:attribute
+        Node.Nodes tagBody = n.getBody();
+        if (tagBody != null) {
+            int numSubElements = tagBody.size();
+            for (int i = 0; i < numSubElements; i++) {
+                Node node = tagBody.getNode(i);
+                if (node instanceof Node.NamedAttribute) {
+                    String attrName = node.getAttributeValue("name");
+                    temp.addElement(attrName);
+                    // Check if this value appear in the attribute of the node
+                    if (n.getAttributeValue(attrName) != null) {
+                        err.jspError(n,
+                                "jsp.error.duplicate.name.jspattribute",
+                                attrName);
+                    }
+                } else {
+                    // Nothing can come before jsp:attribute, and only
+                    // jsp:body can come after it.
+                    break;
+                }
+            }
+        }
+
+        /*
+         * First check to see if all the mandatory attributes are present. If so
+         * only then proceed to see if the other attributes are valid for the
+         * particular tag.
+         */
+        String missingAttribute = null;
+
+        for (int i = 0; i < validAttributes.length; i++) {
+            int attrPos;
+            if (validAttributes[i].mandatory) {
+                attrPos = temp.indexOf(validAttributes[i].name);
+                if (attrPos != -1) {
+                    temp.remove(attrPos);
+                    valid = true;
+                } else {
+                    valid = false;
+                    missingAttribute = validAttributes[i].name;
+                    break;
+                }
+            }
+        }
+
+        // If mandatory attribute is missing then the exception is thrown
+        if (!valid)
+            err.jspError(start, "jsp.error.mandatory.attribute", typeOfTag,
+                    missingAttribute);
+
+        // Check to see if there are any more attributes for the specified tag.
+        int attrLeftLength = temp.size();
+        if (attrLeftLength == 0)
+            return;
+
+        // Now check to see if the rest of the attributes are valid too.
+        String attribute = null;
+
+        for (int j = 0; j < attrLeftLength; j++) {
+            valid = false;
+            attribute = temp.elementAt(j);
+            for (int i = 0; i < validAttributes.length; i++) {
+                if (attribute.equals(validAttributes[i].name)) {
+                    valid = true;
+                    break;
+                }
+            }
+            if (!valid)
+                err.jspError(start, "jsp.error.invalid.attribute", typeOfTag,
+                        attribute);
+        }
+        // XXX *could* move EL-syntax validation here... (sb)
+    }
+
+    /**
+     * Escape the 5 entities defined by XML.
+     */
+    public static String escapeXml(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("&apos;");
+            } else if (c == '&') {
+                sb.append("&amp;");
+            } else if (c == '"') {
+                sb.append("&quot;");
+            } else {
+                sb.append(c);
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Replaces any occurrences of the character <tt>replace</tt> with the
+     * string <tt>with</tt>.
+     */
+    public static String replace(String name, char replace, String with) {
+        StringBuilder buf = new StringBuilder();
+        int begin = 0;
+        int end;
+        int last = name.length();
+
+        while (true) {
+            end = name.indexOf(replace, begin);
+            if (end < 0) {
+                end = last;
+            }
+            buf.append(name.substring(begin, end));
+            if (end == last) {
+                break;
+            }
+            buf.append(with);
+            begin = end + 1;
+        }
+
+        return buf.toString();
+    }
+
+    public static class ValidAttribute {
+        String name;
+
+        boolean mandatory;
+
+        public ValidAttribute(String name, boolean mandatory) {
+            this.name = name;
+            this.mandatory = mandatory;
+        }
+
+        public ValidAttribute(String name) {
+            this(name, false);
+        }
+    }
+
+    /**
+     * Convert a String value to 'boolean'. Besides the standard conversions
+     * done by Boolean.valueOf(s).booleanValue(), the value "yes" (ignore case)
+     * is also converted to 'true'. If 's' is null, then 'false' is returned.
+     * 
+     * @param s
+     *            the string to be converted
+     * @return the boolean value associated with the string s
+     */
+    public static boolean booleanValue(String s) {
+        boolean b = false;
+        if (s != null) {
+            if (s.equalsIgnoreCase("yes")) {
+                b = true;
+            } else {
+                b = Boolean.valueOf(s).booleanValue();
+            }
+        }
+        return b;
+    }
+
+    /**
+     * Returns the <tt>Class</tt> object associated with the class or
+     * interface with the given string name.
+     * 
+     * <p>
+     * The <tt>Class</tt> object is determined by passing the given string
+     * name to the <tt>Class.forName()</tt> method, unless the given string
+     * name represents a primitive type, in which case it is converted to a
+     * <tt>Class</tt> object by appending ".class" to it (e.g., "int.class").
+     */
+    public static Class<?> toClass(String type, ClassLoader loader)
+            throws ClassNotFoundException {
+
+        Class<?> c = null;
+        int i0 = type.indexOf('[');
+        int dims = 0;
+        if (i0 > 0) {
+            // This is an array. Count the dimensions
+            for (int i = 0; i < type.length(); i++) {
+                if (type.charAt(i) == '[')
+                    dims++;
+            }
+            type = type.substring(0, i0);
+        }
+
+        if ("boolean".equals(type))
+            c = boolean.class;
+        else if ("char".equals(type))
+            c = char.class;
+        else if ("byte".equals(type))
+            c = byte.class;
+        else if ("short".equals(type))
+            c = short.class;
+        else if ("int".equals(type))
+            c = int.class;
+        else if ("long".equals(type))
+            c = long.class;
+        else if ("float".equals(type))
+            c = float.class;
+        else if ("double".equals(type))
+            c = double.class;
+        else if ("void".equals(type))
+            c = void.class;
+        else if (type.indexOf('[') < 0)
+            c = loader.loadClass(type);
+
+        if (dims == 0)
+            return c;
+
+        if (dims == 1)
+            return java.lang.reflect.Array.newInstance(c, 1).getClass();
+
+        // Array of more than i dimension
+        return java.lang.reflect.Array.newInstance(c, new int[dims]).getClass();
+    }
+
+    /**
+     * Produces a String representing a call to the EL interpreter.
+     * 
+     * @param expression
+     *            a String containing zero or more "${}" expressions
+     * @param expectedType
+     *            the expected type of the interpreted result
+     * @param fnmapvar
+     *            Variable pointing to a function map.
+     * @param XmlEscape
+     *            True if the result should do XML escaping
+     * @return a String representing a call to the EL interpreter.
+     */
+    public static String interpreterCall(boolean isTagFile, String expression,
+            Class<?> expectedType, String fnmapvar, boolean XmlEscape) {
+        /*
+         * Determine which context object to use.
+         */
+        String jspCtxt = null;
+        if (isTagFile)
+            jspCtxt = "this.getJspContext()";
+        else
+            jspCtxt = "_jspx_page_context";
+
+        /*
+         * Determine whether to use the expected type's textual name or, if it's
+         * a primitive, the name of its correspondent boxed type.
+         */
+        String targetType = expectedType.getCanonicalName();
+        String primitiveConverterMethod = null;
+        if (expectedType.isPrimitive()) {
+            if (expectedType.equals(Boolean.TYPE)) {
+                targetType = Boolean.class.getName();
+                primitiveConverterMethod = "booleanValue";
+            } else if (expectedType.equals(Byte.TYPE)) {
+                targetType = Byte.class.getName();
+                primitiveConverterMethod = "byteValue";
+            } else if (expectedType.equals(Character.TYPE)) {
+                targetType = Character.class.getName();
+                primitiveConverterMethod = "charValue";
+            } else if (expectedType.equals(Short.TYPE)) {
+                targetType = Short.class.getName();
+                primitiveConverterMethod = "shortValue";
+            } else if (expectedType.equals(Integer.TYPE)) {
+                targetType = Integer.class.getName();
+                primitiveConverterMethod = "intValue";
+            } else if (expectedType.equals(Long.TYPE)) {
+                targetType = Long.class.getName();
+                primitiveConverterMethod = "longValue";
+            } else if (expectedType.equals(Float.TYPE)) {
+                targetType = Float.class.getName();
+                primitiveConverterMethod = "floatValue";
+            } else if (expectedType.equals(Double.TYPE)) {
+                targetType = Double.class.getName();
+                primitiveConverterMethod = "doubleValue";
+            }
+        }
+
+        if (primitiveConverterMethod != null) {
+            XmlEscape = false;
+        }
+
+        /*
+         * Build up the base call to the interpreter.
+         */
+        // XXX - We use a proprietary call to the interpreter for now
+        // as the current standard machinery is inefficient and requires
+        // lots of wrappers and adapters. This should all clear up once
+        // the EL interpreter moves out of JSTL and into its own project.
+        // In the future, this should be replaced by code that calls
+        // ExpressionEvaluator.parseExpression() and then cache the resulting
+        // expression objects. The interpreterCall would simply select
+        // one of the pre-cached expressions and evaluate it.
+        // Note that PageContextImpl implements VariableResolver and
+        // the generated Servlet/SimpleTag implements FunctionMapper, so
+        // that machinery is already in place (mroth).
+        targetType = toJavaSourceType(targetType);
+        StringBuilder call = new StringBuilder(
+                "("
+                        + targetType
+                        + ") "
+                        + "org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate"
+                        + "(" + Generator.quote(expression) + ", " + targetType
+                        + ".class, " + "(javax.servlet.jsp.PageContext)" + jspCtxt + ", "
+                        + fnmapvar + ", " + XmlEscape + ")");
+
+        /*
+         * Add the primitive converter method if we need to.
+         */
+        if (primitiveConverterMethod != null) {
+            call.insert(0, "(");
+            call.append(")." + primitiveConverterMethod + "()");
+        }
+
+        return call.toString();
+    }
+
+    public static String coerceToPrimitiveBoolean(String s,
+            boolean isNamedAttribute) {
+        if (isNamedAttribute) {
+            return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToBoolean("
+                    + s + ")";
+        } else {
+            if (s == null || s.length() == 0)
+                return "false";
+            else
+                return Boolean.valueOf(s).toString();
+        }
+    }
+
+    public static String coerceToBoolean(String s, boolean isNamedAttribute) {
+        if (isNamedAttribute) {
+            return "(java.lang.Boolean) org.apache.jasper.runtime.JspRuntimeLibrary.coerce("
+                    + s + ", java.lang.Boolean.class)";
+        } else {
+            if (s == null || s.length() == 0) {
+                return "new java.lang.Boolean(false)";
+            } else {
+                // Detect format error at translation time
+                return "new java.lang.Boolean(" + Boolean.valueOf(s).toString() + ")";
+            }
+        }
+    }
+
+    public static String coerceToPrimitiveByte(String s,
+            boolean isNamedAttribute) {
+        if (isNamedAttribute) {
+            return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToByte("
+                    + s + ")";
+        } else {
+            if (s == null || s.length() == 0)
+                return "(byte) 0";
+            else
+                return "((byte)" + Byte.valueOf(s).toString() + ")";
+        }
+    }
+
+    public static String coerceToByte(String s, boolean isNamedAttribute) {
+        if (isNamedAttribute) {
+            return "(java.lang.Byte) org.apache.jasper.runtime.JspRuntimeLibrary.coerce("
+                    + s + ", java.lang.Byte.class)";
+        } else {
+            if (s == null || s.length() == 0) {
+                return "new java.lang.Byte((byte) 0)";
+            } else {
+                // Detect format error at translation time
+                return "new java.lang.Byte((byte)" + Byte.valueOf(s).toString() + ")";
+            }
+        }
+    }
+
+    public static String coerceToChar(String s, boolean isNamedAttribute) {
+        if (isNamedAttribute) {
+            return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToChar("
+                    + s + ")";
+        } else {
+            if (s == null || s.length() == 0) {
+                return "(char) 0";
+            } else {
+                char ch = s.charAt(0);
+                // this trick avoids escaping issues
+                return "((char) " + (int) ch + ")";
+            }
+        }
+    }
+
+    public static String coerceToCharacter(String s, boolean isNamedAttribute) {
+        if (isNamedAttribute) {
+            return "(java.lang.Character) org.apache.jasper.runtime.JspRuntimeLibrary.coerce("
+                    + s + ", java.lang.Character.class)";
+        } else {
+            if (s == null || s.length() == 0) {
+                return "new java.lang.Character((char) 0)";
+            } else {
+                char ch = s.charAt(0);
+                // this trick avoids escaping issues
+                return "new java.lang.Character((char) " + (int) ch + ")";
+            }
+        }
+    }
+
+    public static String coerceToPrimitiveDouble(String s,
+            boolean isNamedAttribute) {
+        if (isNamedAttribute) {
+            return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToDouble("
+                    + s + ")";
+        } else {
+            if (s == null || s.length() == 0)
+                return "(double) 0";
+            else
+                return Double.valueOf(s).toString();
+        }
+    }
+
+    public static String coerceToDouble(String s, boolean isNamedAttribute) {
+        if (isNamedAttribute) {
+            return "(java.lang.Double) org.apache.jasper.runtime.JspRuntimeLibrary.coerce("
+                    + s + ", Double.class)";
+        } else {
+            if (s == null || s.length() == 0) {
+                return "new java.lang.Double(0)";
+            } else {
+                // Detect format error at translation time
+                return "new java.lang.Double(" + Double.valueOf(s).toString() + ")";
+            }
+        }
+    }
+
+    public static String coerceToPrimitiveFloat(String s,
+            boolean isNamedAttribute) {
+        if (isNamedAttribute) {
+            return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToFloat("
+                    + s + ")";
+        } else {
+            if (s == null || s.length() == 0)
+                return "(float) 0";
+            else
+                return Float.valueOf(s).toString() + "f";
+        }
+    }
+
+    public static String coerceToFloat(String s, boolean isNamedAttribute) {
+        if (isNamedAttribute) {
+            return "(java.lang.Float) org.apache.jasper.runtime.JspRuntimeLibrary.coerce("
+                    + s + ", java.lang.Float.class)";
+        } else {
+            if (s == null || s.length() == 0) {
+                return "new java.lang.Float(0)";
+            } else {
+                // Detect format error at translation time
+                return "new java.lang.Float(" + Float.valueOf(s).toString() + "f)";
+            }
+        }
+    }
+
+    public static String coerceToInt(String s, boolean isNamedAttribute) {
+        if (isNamedAttribute) {
+            return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToInt("
+                    + s + ")";
+        } else {
+            if (s == null || s.length() == 0)
+                return "0";
+            else
+                return Integer.valueOf(s).toString();
+        }
+    }
+
+    public static String coerceToInteger(String s, boolean isNamedAttribute) {
+        if (isNamedAttribute) {
+            return "(java.lang.Integer) org.apache.jasper.runtime.JspRuntimeLibrary.coerce("
+                    + s + ", java.lang.Integer.class)";
+        } else {
+            if (s == null || s.length() == 0) {
+                return "new java.lang.Integer(0)";
+            } else {
+                // Detect format error at translation time
+                return "new java.lang.Integer(" + Integer.valueOf(s).toString() + ")";
+            }
+        }
+    }
+
+    public static String coerceToPrimitiveShort(String s,
+            boolean isNamedAttribute) {
+        if (isNamedAttribute) {
+            return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToShort("
+                    + s + ")";
+        } else {
+            if (s == null || s.length() == 0)
+                return "(short) 0";
+            else
+                return "((short) " + Short.valueOf(s).toString() + ")";
+        }
+    }
+
+    public static String coerceToShort(String s, boolean isNamedAttribute) {
+        if (isNamedAttribute) {
+            return "(java.lang.Short) org.apache.jasper.runtime.JspRuntimeLibrary.coerce("
+                    + s + ", java.lang.Short.class)";
+        } else {
+            if (s == null || s.length() == 0) {
+                return "new java.lang.Short((short) 0)";
+            } else {
+                // Detect format error at translation time
+                return "new java.lang.Short(\"" + Short.valueOf(s).toString() + "\")";
+            }
+        }
+    }
+
+    public static String coerceToPrimitiveLong(String s,
+            boolean isNamedAttribute) {
+        if (isNamedAttribute) {
+            return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToLong("
+                    + s + ")";
+        } else {
+            if (s == null || s.length() == 0)
+                return "(long) 0";
+            else
+                return Long.valueOf(s).toString() + "l";
+        }
+    }
+
+    public static String coerceToLong(String s, boolean isNamedAttribute) {
+        if (isNamedAttribute) {
+            return "(java.lang.Long) org.apache.jasper.runtime.JspRuntimeLibrary.coerce("
+                    + s + ", java.lang.Long.class)";
+        } else {
+            if (s == null || s.length() == 0) {
+                return "new java.lang.Long(0)";
+            } else {
+                // Detect format error at translation time
+                return "new java.lang.Long(" + Long.valueOf(s).toString() + "l)";
+            }
+        }
+    }
+
+    public static InputStream getInputStream(String fname, JarFile jarFile,
+            JspCompilationContext ctxt, ErrorDispatcher err)
+            throws JasperException, IOException {
+
+        InputStream in = null;
+
+        if (jarFile != null) {
+            String jarEntryName = fname.substring(1, fname.length());
+            ZipEntry jarEntry = jarFile.getEntry(jarEntryName);
+            if (jarEntry == null) {
+                err.jspError("jsp.error.file.not.found", fname);
+            }
+            in = jarFile.getInputStream(jarEntry);
+        } else {
+            in = ctxt.getResourceAsStream(fname);
+        }
+
+        if (in == null) {
+            err.jspError("jsp.error.file.not.found", fname);
+        }
+
+        return in;
+    }
+
+    /**
+     * Gets the fully-qualified class name of the tag handler corresponding to
+     * the given tag file path.
+     * 
+     * @param path
+     *            Tag file path
+     * @param err
+     *            Error dispatcher
+     * 
+     * @return Fully-qualified class name of the tag handler corresponding to
+     *         the given tag file path
+     */
+    public static String getTagHandlerClassName(String path, String urn,
+            ErrorDispatcher err) throws JasperException {
+
+
+        String className = null;
+        int begin = 0;
+        int index;
+
+        index = path.lastIndexOf(".tag");
+        if (index == -1) {
+            err.jspError("jsp.error.tagfile.badSuffix", path);
+        }
+
+        // It's tempting to remove the ".tag" suffix here, but we can't.
+        // If we remove it, the fully-qualified class name of this tag
+        // could conflict with the package name of other tags.
+        // For instance, the tag file
+        // /WEB-INF/tags/foo.tag
+        // would have fully-qualified class name
+        // org.apache.jsp.tag.web.foo
+        // which would conflict with the package name of the tag file
+        // /WEB-INF/tags/foo/bar.tag
+
+        index = path.indexOf(WEB_INF_TAGS);
+        if (index != -1) {
+            className = "org.apache.jsp.tag.web.";
+            begin = index + WEB_INF_TAGS.length();
+        } else {
+            index = path.indexOf(META_INF_TAGS);
+            if (index != -1) {
+                className = getClassNameBase(urn);
+                begin = index + META_INF_TAGS.length();
+            } else {
+                err.jspError("jsp.error.tagfile.illegalPath", path);
+            }
+        }
+
+        className += makeJavaPackage(path.substring(begin));
+
+        return className;
+    }
+
+    private static String getClassNameBase(String urn) {
+        StringBuilder base = new StringBuilder("org.apache.jsp.tag.meta.");
+        if (urn != null) {
+            base.append(makeJavaPackage(urn));
+            base.append('.');
+        }
+        return base.toString();
+    }
+
+    /**
+     * Converts the given path to a Java package or fully-qualified class name
+     * 
+     * @param path
+     *            Path to convert
+     * 
+     * @return Java package corresponding to the given path
+     */
+    public static final String makeJavaPackage(String path) {
+        String classNameComponents[] = split(path, "/");
+        StringBuilder legalClassNames = new StringBuilder();
+        for (int i = 0; i < classNameComponents.length; i++) {
+            legalClassNames.append(makeJavaIdentifier(classNameComponents[i]));
+            if (i < classNameComponents.length - 1) {
+                legalClassNames.append('.');
+            }
+        }
+        return legalClassNames.toString();
+    }
+
+    /**
+     * Splits a string into it's components.
+     * 
+     * @param path
+     *            String to split
+     * @param pat
+     *            Pattern to split at
+     * @return the components of the path
+     */
+    private static final String[] split(String path, String pat) {
+        Vector<String> comps = new Vector<String>();
+        int pos = path.indexOf(pat);
+        int start = 0;
+        while (pos >= 0) {
+            if (pos > start) {
+                String comp = path.substring(start, pos);
+                comps.add(comp);
+            }
+            start = pos + pat.length();
+            pos = path.indexOf(pat, start);
+        }
+        if (start < path.length()) {
+            comps.add(path.substring(start));
+        }
+        String[] result = new String[comps.size()];
+        for (int i = 0; i < comps.size(); i++) {
+            result[i] = comps.elementAt(i);
+        }
+        return result;
+    }
+
+    /**
+     * Converts the given identifier to a legal Java identifier
+     * 
+     * @param identifier
+     *            Identifier to convert
+     * 
+     * @return Legal Java identifier corresponding to the given identifier
+     */
+    public static final String makeJavaIdentifier(String identifier) {
+        StringBuilder modifiedIdentifier = new StringBuilder(identifier.length());
+        if (!Character.isJavaIdentifierStart(identifier.charAt(0))) {
+            modifiedIdentifier.append('_');
+        }
+        for (int i = 0; i < identifier.length(); i++) {
+            char ch = identifier.charAt(i);
+            if (Character.isJavaIdentifierPart(ch) && ch != '_') {
+                modifiedIdentifier.append(ch);
+            } else if (ch == '.') {
+                modifiedIdentifier.append('_');
+            } else {
+                modifiedIdentifier.append(mangleChar(ch));
+            }
+        }
+        if (isJavaKeyword(modifiedIdentifier.toString())) {
+            modifiedIdentifier.append('_');
+        }
+        return modifiedIdentifier.toString();
+    }
+
+    /**
+     * Mangle the specified character to create a legal Java class name.
+     */
+    public static final String mangleChar(char ch) {
+        char[] result = new char[5];
+        result[0] = '_';
+        result[1] = Character.forDigit((ch >> 12) & 0xf, 16);
+        result[2] = Character.forDigit((ch >> 8) & 0xf, 16);
+        result[3] = Character.forDigit((ch >> 4) & 0xf, 16);
+        result[4] = Character.forDigit(ch & 0xf, 16);
+        return new String(result);
+    }
+
+    /**
+     * Test whether the argument is a Java keyword
+     */
+    public static boolean isJavaKeyword(String key) {
+        int i = 0;
+        int j = javaKeywords.length;
+        while (i < j) {
+            int k = (i + j) / 2;
+            int result = javaKeywords[k].compareTo(key);
+            if (result == 0) {
+                return true;
+            }
+            if (result < 0) {
+                i = k + 1;
+            } else {
+                j = k;
+            }
+        }
+        return false;
+    }
+
+    static InputStreamReader getReader(String fname, String encoding,
+            JarFile jarFile, JspCompilationContext ctxt, ErrorDispatcher err)
+            throws JasperException, IOException {
+
+        return getReader(fname, encoding, jarFile, ctxt, err, 0);
+    }
+
+    static InputStreamReader getReader(String fname, String encoding,
+            JarFile jarFile, JspCompilationContext ctxt, ErrorDispatcher err,
+            int skip) throws JasperException, IOException {
+
+        InputStreamReader reader = null;
+        InputStream in = getInputStream(fname, jarFile, ctxt, err);
+        for (int i = 0; i < skip; i++) {
+            in.read();
+        }
+        try {
+            reader = new InputStreamReader(in, encoding);
+        } catch (UnsupportedEncodingException ex) {
+            err.jspError("jsp.error.unsupported.encoding", encoding);
+        }
+
+        return reader;
+    }
+
+    /**
+     * Handles taking input from TLDs 'java.lang.Object' ->
+     * 'java.lang.Object.class' 'int' -> 'int.class' 'void' -> 'Void.TYPE'
+     * 'int[]' -> 'int[].class'
+     * 
+     * @param type
+     */
+    public static String toJavaSourceTypeFromTld(String type) {
+        if (type == null || "void".equals(type)) {
+            return "java.lang.Void.TYPE";
+        }
+        return type + ".class";
+    }
+
+    /**
+     * Class.getName() return arrays in the form "[[[<et>", where et, the
+     * element type can be one of ZBCDFIJS or L<classname>; It is converted
+     * into forms that can be understood by javac.
+     */
+    public static String toJavaSourceType(String type) {
+
+        if (type.charAt(0) != '[') {
+            return type;
+        }
+
+        int dims = 1;
+        String t = null;
+        for (int i = 1; i < type.length(); i++) {
+            if (type.charAt(i) == '[') {
+                dims++;
+            } else {
+                switch (type.charAt(i)) {
+                case 'Z': t = "boolean"; break;
+                case 'B': t = "byte"; break;
+                case 'C': t = "char"; break;
+                case 'D': t = "double"; break;
+                case 'F': t = "float"; break;
+                case 'I': t = "int"; break;
+                case 'J': t = "long"; break;
+                case 'S': t = "short"; break;
+                case 'L': t = type.substring(i+1, type.indexOf(';')); break;
+                }
+                break;
+            }
+        }
+        StringBuilder resultType = new StringBuilder(t);
+        for (; dims > 0; dims--) {
+            resultType.append("[]");
+        }
+        return resultType.toString();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Localizer.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Localizer.java
new file mode 100644
index 0000000..f1075dd
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Localizer.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import java.text.MessageFormat;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import org.apache.jasper.util.ExceptionUtils;
+
+/**
+ * Class responsible for converting error codes to corresponding localized
+ * error messages.
+ *
+ * @author Jan Luehe
+ */
+public class Localizer {
+
+    private static ResourceBundle bundle = null;
+    
+    static {
+        try {
+        bundle = ResourceBundle.getBundle(
+            "org.apache.jasper.resources.LocalStrings");
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            t.printStackTrace();
+        }
+    }
+
+    /*
+     * Returns the localized error message corresponding to the given error
+     * code.
+     *
+     * If the given error code is not defined in the resource bundle for
+     * localized error messages, it is used as the error message.
+     *
+     * @param errCode Error code to localize
+     * 
+     * @return Localized error message
+     */
+    public static String getMessage(String errCode) {
+        String errMsg = errCode;
+        try {
+            errMsg = bundle.getString(errCode);
+        } catch (MissingResourceException e) {
+        }
+        return errMsg;
+    }
+
+    /* 
+     * Returns the localized error message corresponding to the given error
+     * code.
+     *
+     * If the given error code is not defined in the resource bundle for
+     * localized error messages, it is used as the error message.
+     *
+     * @param errCode Error code to localize
+     * @param arg Argument for parametric replacement
+     *
+     * @return Localized error message
+     */
+    public static String getMessage(String errCode, String arg) {
+        return getMessage(errCode, new Object[] {arg});
+    }
+
+    /* 
+     * Returns the localized error message corresponding to the given error
+     * code.
+     *
+     * If the given error code is not defined in the resource bundle for
+     * localized error messages, it is used as the error message.
+     *
+     * @param errCode Error code to localize
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     *
+     * @return Localized error message
+     */
+    public static String getMessage(String errCode, String arg1, String arg2) {
+        return getMessage(errCode, new Object[] {arg1, arg2});
+    }
+    
+    /* 
+     * Returns the localized error message corresponding to the given error
+     * code.
+     *
+     * If the given error code is not defined in the resource bundle for
+     * localized error messages, it is used as the error message.
+     *
+     * @param errCode Error code to localize
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     * @param arg3 Third argument for parametric replacement
+     *
+     * @return Localized error message
+     */
+    public static String getMessage(String errCode, String arg1, String arg2,
+                                    String arg3) {
+        return getMessage(errCode, new Object[] {arg1, arg2, arg3});
+    }
+
+    /* 
+     * Returns the localized error message corresponding to the given error
+     * code.
+     *
+     * If the given error code is not defined in the resource bundle for
+     * localized error messages, it is used as the error message.
+     *
+     * @param errCode Error code to localize
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     * @param arg3 Third argument for parametric replacement
+     * @param arg4 Fourth argument for parametric replacement
+     *
+     * @return Localized error message
+     */
+    public static String getMessage(String errCode, String arg1, String arg2,
+                                    String arg3, String arg4) {
+        return getMessage(errCode, new Object[] {arg1, arg2, arg3, arg4});
+    }
+
+    /*
+     * Returns the localized error message corresponding to the given error
+     * code.
+     *
+     * If the given error code is not defined in the resource bundle for
+     * localized error messages, it is used as the error message.
+     *
+     * @param errCode Error code to localize
+     * @param args Arguments for parametric replacement
+     *
+     * @return Localized error message
+     */
+    public static String getMessage(String errCode, Object[] args) {
+        String errMsg = errCode;
+        try {
+            errMsg = bundle.getString(errCode);
+            if (args != null) {
+                MessageFormat formatter = new MessageFormat(errMsg);
+                errMsg = formatter.format(args);
+            }
+        } catch (MissingResourceException e) {
+        }
+        
+        return errMsg;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Mark.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Mark.java
new file mode 100644
index 0000000..5cf6653
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Mark.java
@@ -0,0 +1,263 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.compiler;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Stack;
+
+import org.apache.jasper.JspCompilationContext;
+
+/**
+ * Mark represents a point in the JSP input. 
+ *
+ * @author Anil K. Vijendran
+ */
+final class Mark {
+
+    // position within current stream
+    int cursor, line, col;
+
+    // directory of file for current stream
+    String baseDir;
+
+    // current stream
+    char[] stream = null;
+
+    // fileid of current stream
+    private int fileId;
+
+    // name of the current file
+    private String fileName;
+
+    /*
+     * stack of stream and stream state of streams that have included
+     * current stream
+     */
+    private Stack<IncludeState> includeStack = null;
+
+    // encoding of current file
+    private String encoding = null;
+
+    // reader that owns this mark (so we can look up fileid's)
+    private JspReader reader;
+
+    private JspCompilationContext ctxt;
+
+    /**
+     * Constructor
+     *
+     * @param reader JspReader this mark belongs to
+     * @param inStream current stream for this mark
+     * @param fileId id of requested jsp file
+     * @param name JSP file name
+     * @param inBaseDir base directory of requested jsp file
+     * @param inEncoding encoding of current file
+     */
+    Mark(JspReader reader, char[] inStream, int fileId, String name,
+         String inBaseDir, String inEncoding) {
+
+        this.reader = reader;
+        this.ctxt = reader.getJspCompilationContext();
+        this.stream = inStream;
+        this.cursor = 0;
+        this.line = 1;
+        this.col = 1;
+        this.fileId = fileId;
+        this.fileName = name;
+        this.baseDir = inBaseDir;
+        this.encoding = inEncoding;
+        this.includeStack = new Stack<IncludeState>();
+    }
+
+
+    /**
+     * Constructor
+     */
+    Mark(Mark other) {
+
+        this.reader = other.reader;
+        this.ctxt = other.reader.getJspCompilationContext();
+        this.stream = other.stream;
+        this.fileId = other.fileId;
+        this.fileName = other.fileName;
+        this.cursor = other.cursor;
+        this.line = other.line;
+        this.col = other.col;
+        this.baseDir = other.baseDir;
+        this.encoding = other.encoding;
+
+        // clone includeStack without cloning contents
+        includeStack = new Stack<IncludeState>();
+        for ( int i=0; i < other.includeStack.size(); i++ ) {
+            includeStack.addElement( other.includeStack.elementAt(i) );
+        }
+    }
+
+
+    /**
+     * Constructor
+     */    
+    Mark(JspCompilationContext ctxt, String filename, int line, int col) {
+
+        this.reader = null;
+        this.ctxt = ctxt;
+        this.stream = null;
+        this.cursor = 0;
+        this.line = line;
+        this.col = col;
+        this.fileId = -1;
+        this.fileName = filename;
+        this.baseDir = "le-basedir";
+        this.encoding = "le-endocing";
+        this.includeStack = null;
+    }
+
+
+    /**
+     * Sets this mark's state to a new stream.
+     * It will store the current stream in it's includeStack.
+     *
+     * @param inStream new stream for mark
+     * @param inFileId id of new file from which stream comes from
+     * @param inBaseDir directory of file
+     * @param inEncoding encoding of new file
+     */
+    public void pushStream(char[] inStream, int inFileId, String name,
+                           String inBaseDir, String inEncoding) 
+    {
+        // store current state in stack
+        includeStack.push(new IncludeState(cursor, line, col, fileId,
+                                           fileName, baseDir, 
+                                           encoding, stream) );
+
+        // set new variables
+        cursor = 0;
+        line = 1;
+        col = 1;
+        fileId = inFileId;
+        fileName = name;
+        baseDir = inBaseDir;
+        encoding = inEncoding;
+        stream = inStream;
+    }
+
+
+    /**
+     * Restores this mark's state to a previously stored stream.
+     * @return The previous Mark instance when the stream was pushed, or null
+     * if there is no previous stream
+     */
+    public Mark popStream() {
+        // make sure we have something to pop
+        if ( includeStack.size() <= 0 ) {
+            return null;
+        }
+
+        // get previous state in stack
+        IncludeState state = includeStack.pop( );
+
+        // set new variables
+        cursor = state.cursor;
+        line = state.line;
+        col = state.col;
+        fileId = state.fileId;
+        fileName = state.fileName;
+        baseDir = state.baseDir;
+        stream = state.stream;
+        return this;
+    }
+
+
+    // -------------------- Locator interface --------------------
+
+    public int getLineNumber() {
+        return line;
+    }
+
+    public int getColumnNumber() {
+        return col;
+    }
+
+    public String getSystemId() {
+        return getFile();
+    }
+
+    public String getPublicId() {
+        return null;
+    }
+
+    @Override
+    public String toString() {
+        return getFile()+"("+line+","+col+")";
+    }
+
+    public String getFile() {
+        return this.fileName;
+    }
+
+    /**
+     * Gets the URL of the resource with which this Mark is associated
+     *
+     * @return URL of the resource with which this Mark is associated
+     *
+     * @exception MalformedURLException if the resource pathname is incorrect
+     */
+    public URL getURL() throws MalformedURLException {
+        return ctxt.getResource(getFile());
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other instanceof Mark) {
+            Mark m = (Mark) other;
+            return this.reader == m.reader && this.fileId == m.fileId 
+                && this.cursor == m.cursor && this.line == m.line 
+                && this.col == m.col;
+        } 
+        return false;
+    }
+
+    /**
+     * Keep track of parser before parsing an included file.
+     * This class keeps track of the parser before we switch to parsing an
+     * included file. In other words, it's the parser's continuation to be
+     * reinstalled after the included file parsing is done.
+     */
+    class IncludeState {
+        int cursor, line, col;
+        int fileId;
+        String fileName;
+        String baseDir;
+        char[] stream = null;
+
+        IncludeState(int inCursor, int inLine, int inCol, int inFileId, 
+                     String name, String inBaseDir, String inEncoding,
+                     char[] inStream) {
+            cursor = inCursor;
+            line = inLine;
+            col = inCol;
+            fileId = inFileId;
+            fileName = name;
+            baseDir = inBaseDir;
+            encoding = inEncoding;
+            stream = inStream;
+        }
+    }
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Node.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Node.java
new file mode 100644
index 0000000..52859bb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Node.java
@@ -0,0 +1,2584 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+import javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.ExpressionFactory;
+import javax.servlet.jsp.tagext.BodyTag;
+import javax.servlet.jsp.tagext.DynamicAttributes;
+import javax.servlet.jsp.tagext.IterationTag;
+import javax.servlet.jsp.tagext.JspIdConsumer;
+import javax.servlet.jsp.tagext.SimpleTag;
+import javax.servlet.jsp.tagext.TagAttributeInfo;
+import javax.servlet.jsp.tagext.TagData;
+import javax.servlet.jsp.tagext.TagFileInfo;
+import javax.servlet.jsp.tagext.TagInfo;
+import javax.servlet.jsp.tagext.TagVariableInfo;
+import javax.servlet.jsp.tagext.TryCatchFinally;
+import javax.servlet.jsp.tagext.VariableInfo;
+
+import org.apache.jasper.Constants;
+import org.apache.jasper.JasperException;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+import org.xml.sax.Attributes;
+
+/**
+ * An internal data representation of a JSP page or a JSP document (XML). Also
+ * included here is a visitor class for traversing nodes.
+ * 
+ * @author Kin-man Chung
+ * @author Jan Luehe
+ * @author Shawn Bayern
+ * @author Mark Roth
+ */
+
+abstract class Node implements TagConstants {
+
+    private static final VariableInfo[] ZERO_VARIABLE_INFO = {};
+
+    protected Attributes attrs;
+
+    // xmlns attributes that represent tag libraries (only in XML syntax)
+    protected Attributes taglibAttrs;
+
+    /*
+     * xmlns attributes that do not represent tag libraries (only in XML syntax)
+     */
+    protected Attributes nonTaglibXmlnsAttrs;
+
+    protected Nodes body;
+
+    protected String text;
+
+    protected Mark startMark;
+
+    protected int beginJavaLine;
+
+    protected int endJavaLine;
+
+    protected Node parent;
+
+    protected Nodes namedAttributeNodes; // cached for performance
+
+    protected String qName;
+
+    protected String localName;
+
+    /*
+     * The name of the inner class to which the codes for this node and its body
+     * are generated. For instance, for <jsp:body> in foo.jsp, this is
+     * "foo_jspHelper". This is primarily used for communicating such info from
+     * Generator to Smap generator.
+     */
+    protected String innerClassName;
+
+    private boolean isDummy;
+
+    /**
+     * Zero-arg Constructor.
+     */
+    public Node() {
+        this.isDummy = true;
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param start
+     *            The location of the jsp page
+     * @param parent
+     *            The enclosing node
+     */
+    public Node(Mark start, Node parent) {
+        this.startMark = start;
+        this.isDummy = (start == null);
+        addToParent(parent);
+    }
+
+    /**
+     * Constructor for Nodes parsed from standard syntax.
+     * 
+     * @param qName
+     *            The action's qualified name
+     * @param localName
+     *            The action's local name
+     * @param attrs
+     *            The attributes for this node
+     * @param start
+     *            The location of the jsp page
+     * @param parent
+     *            The enclosing node
+     */
+    public Node(String qName, String localName, Attributes attrs, Mark start,
+            Node parent) {
+        this.qName = qName;
+        this.localName = localName;
+        this.attrs = attrs;
+        this.startMark = start;
+        this.isDummy = (start == null);
+        addToParent(parent);
+    }
+
+    /**
+     * Constructor for Nodes parsed from XML syntax.
+     * 
+     * @param qName
+     *            The action's qualified name
+     * @param localName
+     *            The action's local name
+     * @param attrs
+     *            The action's attributes whose name does not start with xmlns
+     * @param nonTaglibXmlnsAttrs
+     *            The action's xmlns attributes that do not represent tag
+     *            libraries
+     * @param taglibAttrs
+     *            The action's xmlns attributes that represent tag libraries
+     * @param start
+     *            The location of the jsp page
+     * @param parent
+     *            The enclosing node
+     */
+    public Node(String qName, String localName, Attributes attrs,
+            Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs, Mark start,
+            Node parent) {
+        this.qName = qName;
+        this.localName = localName;
+        this.attrs = attrs;
+        this.nonTaglibXmlnsAttrs = nonTaglibXmlnsAttrs;
+        this.taglibAttrs = taglibAttrs;
+        this.startMark = start;
+        this.isDummy = (start == null);
+        addToParent(parent);
+    }
+
+    /*
+     * Constructor.
+     * 
+     * @param qName The action's qualified name @param localName The action's
+     * local name @param text The text associated with this node @param start
+     * The location of the jsp page @param parent The enclosing node
+     */
+    public Node(String qName, String localName, String text, Mark start,
+            Node parent) {
+        this.qName = qName;
+        this.localName = localName;
+        this.text = text;
+        this.startMark = start;
+        this.isDummy = (start == null);
+        addToParent(parent);
+    }
+
+    public String getQName() {
+        return this.qName;
+    }
+
+    public String getLocalName() {
+        return this.localName;
+    }
+
+    /*
+     * Gets this Node's attributes.
+     * 
+     * In the case of a Node parsed from standard syntax, this method returns
+     * all the Node's attributes.
+     * 
+     * In the case of a Node parsed from XML syntax, this method returns only
+     * those attributes whose name does not start with xmlns.
+     */
+    public Attributes getAttributes() {
+        return this.attrs;
+    }
+
+    /*
+     * Gets this Node's xmlns attributes that represent tag libraries (only
+     * meaningful for Nodes parsed from XML syntax)
+     */
+    public Attributes getTaglibAttributes() {
+        return this.taglibAttrs;
+    }
+
+    /*
+     * Gets this Node's xmlns attributes that do not represent tag libraries
+     * (only meaningful for Nodes parsed from XML syntax)
+     */
+    public Attributes getNonTaglibXmlnsAttributes() {
+        return this.nonTaglibXmlnsAttrs;
+    }
+
+    public void setAttributes(Attributes attrs) {
+        this.attrs = attrs;
+    }
+
+    public String getAttributeValue(String name) {
+        return (attrs == null) ? null : attrs.getValue(name);
+    }
+
+    /**
+     * Get the attribute that is non request time expression, either from the
+     * attribute of the node, or from a jsp:attrbute
+     */
+    public String getTextAttribute(String name) {
+
+        String attr = getAttributeValue(name);
+        if (attr != null) {
+            return attr;
+        }
+
+        NamedAttribute namedAttribute = getNamedAttributeNode(name);
+        if (namedAttribute == null) {
+            return null;
+        }
+
+        return namedAttribute.getText();
+    }
+
+    /**
+     * Searches all subnodes of this node for jsp:attribute standard actions
+     * with the given name, and returns the NamedAttribute node of the matching
+     * named attribute, nor null if no such node is found.
+     * <p>
+     * This should always be called and only be called for nodes that accept
+     * dynamic runtime attribute expressions.
+     */
+    public NamedAttribute getNamedAttributeNode(String name) {
+        NamedAttribute result = null;
+
+        // Look for the attribute in NamedAttribute children
+        Nodes nodes = getNamedAttributeNodes();
+        int numChildNodes = nodes.size();
+        for (int i = 0; i < numChildNodes; i++) {
+            NamedAttribute na = (NamedAttribute) nodes.getNode(i);
+            boolean found = false;
+            int index = name.indexOf(':');
+            if (index != -1) {
+                // qualified name
+                found = na.getName().equals(name);
+            } else {
+                found = na.getLocalName().equals(name);
+            }
+            if (found) {
+                result = na;
+                break;
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Searches all subnodes of this node for jsp:attribute standard actions,
+     * and returns that set of nodes as a Node.Nodes object.
+     * 
+     * @return Possibly empty Node.Nodes object containing any jsp:attribute
+     *         subnodes of this Node
+     */
+    public Node.Nodes getNamedAttributeNodes() {
+
+        if (namedAttributeNodes != null) {
+            return namedAttributeNodes;
+        }
+
+        Node.Nodes result = new Node.Nodes();
+
+        // Look for the attribute in NamedAttribute children
+        Nodes nodes = getBody();
+        if (nodes != null) {
+            int numChildNodes = nodes.size();
+            for (int i = 0; i < numChildNodes; i++) {
+                Node n = nodes.getNode(i);
+                if (n instanceof NamedAttribute) {
+                    result.add(n);
+                } else if (!(n instanceof Comment)) {
+                    // Nothing can come before jsp:attribute, and only
+                    // jsp:body can come after it.
+                    break;
+                }
+            }
+        }
+
+        namedAttributeNodes = result;
+        return result;
+    }
+
+    public Nodes getBody() {
+        return body;
+    }
+
+    public void setBody(Nodes body) {
+        this.body = body;
+    }
+
+    public String getText() {
+        return text;
+    }
+
+    public Mark getStart() {
+        return startMark;
+    }
+
+    public Node getParent() {
+        return parent;
+    }
+
+    public int getBeginJavaLine() {
+        return beginJavaLine;
+    }
+
+    public void setBeginJavaLine(int begin) {
+        beginJavaLine = begin;
+    }
+
+    public int getEndJavaLine() {
+        return endJavaLine;
+    }
+
+    public void setEndJavaLine(int end) {
+        endJavaLine = end;
+    }
+
+    public boolean isDummy() {
+        return isDummy;
+    }
+
+    public Node.Root getRoot() {
+        Node n = this;
+        while (!(n instanceof Node.Root)) {
+            n = n.getParent();
+        }
+        return (Node.Root) n;
+    }
+
+    public String getInnerClassName() {
+        return innerClassName;
+    }
+
+    public void setInnerClassName(String icn) {
+        innerClassName = icn;
+    }
+
+    /**
+     * Selects and invokes a method in the visitor class based on the node type.
+     * This is abstract and should be overrode by the extending classes.
+     * 
+     * @param v
+     *            The visitor class
+     */
+    abstract void accept(Visitor v) throws JasperException;
+
+    // *********************************************************************
+    // Private utility methods
+
+    /*
+     * Adds this Node to the body of the given parent.
+     */
+    private void addToParent(Node parent) {
+        if (parent != null) {
+            this.parent = parent;
+            Nodes parentBody = parent.getBody();
+            if (parentBody == null) {
+                parentBody = new Nodes();
+                parent.setBody(parentBody);
+            }
+            parentBody.add(this);
+        }
+    }
+
+    /***************************************************************************
+     * Child classes
+     */
+
+    /**
+     * Represents the root of a Jsp page or Jsp document
+     */
+    public static class Root extends Node {
+
+        private Root parentRoot;
+
+        private boolean isXmlSyntax;
+
+        // Source encoding of the page containing this Root
+        private String pageEnc;
+
+        // Page encoding specified in JSP config element
+        private String jspConfigPageEnc;
+
+        /*
+         * Flag indicating if the default page encoding is being used (only
+         * applicable with standard syntax).
+         * 
+         * True if the page does not provide a page directive with a
+         * 'contentType' attribute (or the 'contentType' attribute doesn't have
+         * a CHARSET value), the page does not provide a page directive with a
+         * 'pageEncoding' attribute, and there is no JSP configuration element
+         * page-encoding whose URL pattern matches the page.
+         */
+        private boolean isDefaultPageEncoding;
+
+        /*
+         * Indicates whether an encoding has been explicitly specified in the
+         * page's XML prolog (only used for pages in XML syntax). This
+         * information is used to decide whether a translation error must be
+         * reported for encoding conflicts.
+         */
+        private boolean isEncodingSpecifiedInProlog;
+
+        /*
+         * Indicates whether an encoding has been explicitly specified in the
+         * page's dom.
+         */
+        private boolean isBomPresent;
+
+        /*
+         * Sequence number for temporary variables.
+         */
+        private int tempSequenceNumber = 0;
+
+        /*
+         * Constructor.
+         */
+        Root(Mark start, Node parent, boolean isXmlSyntax) {
+            super(start, parent);
+            this.isXmlSyntax = isXmlSyntax;
+            this.qName = JSP_ROOT_ACTION;
+            this.localName = ROOT_ACTION;
+
+            // Figure out and set the parent root
+            Node r = parent;
+            while ((r != null) && !(r instanceof Node.Root))
+                r = r.getParent();
+            parentRoot = (Node.Root) r;
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public boolean isXmlSyntax() {
+            return isXmlSyntax;
+        }
+
+        /*
+         * Sets the encoding specified in the JSP config element whose URL
+         * pattern matches the page containing this Root.
+         */
+        public void setJspConfigPageEncoding(String enc) {
+            jspConfigPageEnc = enc;
+        }
+
+        /*
+         * Gets the encoding specified in the JSP config element whose URL
+         * pattern matches the page containing this Root.
+         */
+        public String getJspConfigPageEncoding() {
+            return jspConfigPageEnc;
+        }
+
+        public void setPageEncoding(String enc) {
+            pageEnc = enc;
+        }
+
+        public String getPageEncoding() {
+            return pageEnc;
+        }
+
+        public void setIsDefaultPageEncoding(boolean isDefault) {
+            isDefaultPageEncoding = isDefault;
+        }
+
+        public boolean isDefaultPageEncoding() {
+            return isDefaultPageEncoding;
+        }
+
+        public void setIsEncodingSpecifiedInProlog(boolean isSpecified) {
+            isEncodingSpecifiedInProlog = isSpecified;
+        }
+
+        public boolean isEncodingSpecifiedInProlog() {
+            return isEncodingSpecifiedInProlog;
+        }
+
+        public void setIsBomPresent(boolean isBom) {
+            isBomPresent = isBom;
+        }
+
+        public boolean isBomPresent() {
+            return isBomPresent;
+        }
+
+        /**
+         * @return The enclosing root to this Root. Usually represents the page
+         *         that includes this one.
+         */
+        public Root getParentRoot() {
+            return parentRoot;
+        }
+        
+        /**
+         * Generates a new temporary variable name.
+         */
+        public String nextTemporaryVariableName() {
+            if (parentRoot == null) {
+                return Constants.TEMP_VARIABLE_NAME_PREFIX + (tempSequenceNumber++);
+            } else {
+                return parentRoot.nextTemporaryVariableName();
+            }
+            
+        }
+    }
+
+    /**
+     * Represents the root of a Jsp document (XML syntax)
+     */
+    public static class JspRoot extends Node {
+
+        public JspRoot(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, ROOT_ACTION, attrs, nonTaglibXmlnsAttrs, taglibAttrs,
+                    start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents a page directive
+     */
+    public static class PageDirective extends Node {
+
+        private Vector<String> imports;
+
+        public PageDirective(Attributes attrs, Mark start, Node parent) {
+            this(JSP_PAGE_DIRECTIVE_ACTION, attrs, null, null, start, parent);
+        }
+
+        public PageDirective(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, PAGE_DIRECTIVE_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+            imports = new Vector<String>();
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        /**
+         * Parses the comma-separated list of class or package names in the
+         * given attribute value and adds each component to this PageDirective's
+         * vector of imported classes and packages.
+         * 
+         * @param value
+         *            A comma-separated string of imports.
+         */
+        public void addImport(String value) {
+            int start = 0;
+            int index;
+            while ((index = value.indexOf(',', start)) != -1) {
+                imports.add(value.substring(start, index).trim());
+                start = index + 1;
+            }
+            if (start == 0) {
+                // No comma found
+                imports.add(value.trim());
+            } else {
+                imports.add(value.substring(start).trim());
+            }
+        }
+
+        public List<String> getImports() {
+            return imports;
+        }
+    }
+
+    /**
+     * Represents an include directive
+     */
+    public static class IncludeDirective extends Node {
+
+        public IncludeDirective(Attributes attrs, Mark start, Node parent) {
+            this(JSP_INCLUDE_DIRECTIVE_ACTION, attrs, null, null, start, parent);
+        }
+
+        public IncludeDirective(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, INCLUDE_DIRECTIVE_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents a custom taglib directive
+     */
+    public static class TaglibDirective extends Node {
+
+        public TaglibDirective(Attributes attrs, Mark start, Node parent) {
+            super(JSP_TAGLIB_DIRECTIVE_ACTION, TAGLIB_DIRECTIVE_ACTION, attrs,
+                    start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents a tag directive
+     */
+    public static class TagDirective extends Node {
+        private Vector<String> imports;
+
+        public TagDirective(Attributes attrs, Mark start, Node parent) {
+            this(JSP_TAG_DIRECTIVE_ACTION, attrs, null, null, start, parent);
+        }
+
+        public TagDirective(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, TAG_DIRECTIVE_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+            imports = new Vector<String>();
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        /**
+         * Parses the comma-separated list of class or package names in the
+         * given attribute value and adds each component to this PageDirective's
+         * vector of imported classes and packages.
+         * 
+         * @param value
+         *            A comma-separated string of imports.
+         */
+        public void addImport(String value) {
+            int start = 0;
+            int index;
+            while ((index = value.indexOf(',', start)) != -1) {
+                imports.add(value.substring(start, index).trim());
+                start = index + 1;
+            }
+            if (start == 0) {
+                // No comma found
+                imports.add(value.trim());
+            } else {
+                imports.add(value.substring(start).trim());
+            }
+        }
+
+        public List<String> getImports() {
+            return imports;
+        }
+    }
+
+    /**
+     * Represents an attribute directive
+     */
+    public static class AttributeDirective extends Node {
+
+        public AttributeDirective(Attributes attrs, Mark start, Node parent) {
+            this(JSP_ATTRIBUTE_DIRECTIVE_ACTION, attrs, null, null, start,
+                    parent);
+        }
+
+        public AttributeDirective(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, ATTRIBUTE_DIRECTIVE_ACTION, attrs,
+                    nonTaglibXmlnsAttrs, taglibAttrs, start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents a variable directive
+     */
+    public static class VariableDirective extends Node {
+
+        public VariableDirective(Attributes attrs, Mark start, Node parent) {
+            this(JSP_VARIABLE_DIRECTIVE_ACTION, attrs, null, null, start,
+                    parent);
+        }
+
+        public VariableDirective(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, VARIABLE_DIRECTIVE_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents a <jsp:invoke> tag file action
+     */
+    public static class InvokeAction extends Node {
+
+        public InvokeAction(Attributes attrs, Mark start, Node parent) {
+            this(JSP_INVOKE_ACTION, attrs, null, null, start, parent);
+        }
+
+        public InvokeAction(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, INVOKE_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents a <jsp:doBody> tag file action
+     */
+    public static class DoBodyAction extends Node {
+
+        public DoBodyAction(Attributes attrs, Mark start, Node parent) {
+            this(JSP_DOBODY_ACTION, attrs, null, null, start, parent);
+        }
+
+        public DoBodyAction(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, DOBODY_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents a Jsp comment Comments are kept for completeness.
+     */
+    public static class Comment extends Node {
+
+        public Comment(String text, Mark start, Node parent) {
+            super(null, null, text, start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents an expression, declaration, or scriptlet
+     */
+    public abstract static class ScriptingElement extends Node {
+
+        public ScriptingElement(String qName, String localName, String text,
+                Mark start, Node parent) {
+            super(qName, localName, text, start, parent);
+        }
+
+        public ScriptingElement(String qName, String localName,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, localName, null, nonTaglibXmlnsAttrs, taglibAttrs,
+                    start, parent);
+        }
+
+        /**
+         * When this node was created from a JSP page in JSP syntax, its text
+         * was stored as a String in the "text" field, whereas when this node
+         * was created from a JSP document, its text was stored as one or more
+         * TemplateText nodes in its body. This method handles either case.
+         * 
+         * @return The text string
+         */
+        @Override
+        public String getText() {
+            String ret = text;
+            if (ret == null) {
+                if (body != null) {
+                    StringBuilder buf = new StringBuilder();
+                    for (int i = 0; i < body.size(); i++) {
+                        buf.append(body.getNode(i).getText());
+                    }
+                    ret = buf.toString();
+                } else {
+                    // Nulls cause NPEs further down the line
+                    ret = "";
+                }
+            }
+            return ret;
+        }
+
+        /**
+         * For the same reason as above, the source line information in the
+         * contained TemplateText node should be used.
+         */
+        @Override
+        public Mark getStart() {
+            if (text == null && body != null && body.size() > 0) {
+                return body.getNode(0).getStart();
+            } else {
+                return super.getStart();
+            }
+        }
+    }
+
+    /**
+     * Represents a declaration
+     */
+    public static class Declaration extends ScriptingElement {
+
+        public Declaration(String text, Mark start, Node parent) {
+            super(JSP_DECLARATION_ACTION, DECLARATION_ACTION, text, start,
+                    parent);
+        }
+
+        public Declaration(String qName, Attributes nonTaglibXmlnsAttrs,
+                Attributes taglibAttrs, Mark start, Node parent) {
+            super(qName, DECLARATION_ACTION, nonTaglibXmlnsAttrs, taglibAttrs,
+                    start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents an expression. Expressions in attributes are embedded in the
+     * attribute string and not here.
+     */
+    public static class Expression extends ScriptingElement {
+
+        public Expression(String text, Mark start, Node parent) {
+            super(JSP_EXPRESSION_ACTION, EXPRESSION_ACTION, text, start, parent);
+        }
+
+        public Expression(String qName, Attributes nonTaglibXmlnsAttrs,
+                Attributes taglibAttrs, Mark start, Node parent) {
+            super(qName, EXPRESSION_ACTION, nonTaglibXmlnsAttrs, taglibAttrs,
+                    start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents a scriptlet
+     */
+    public static class Scriptlet extends ScriptingElement {
+
+        public Scriptlet(String text, Mark start, Node parent) {
+            super(JSP_SCRIPTLET_ACTION, SCRIPTLET_ACTION, text, start, parent);
+        }
+
+        public Scriptlet(String qName, Attributes nonTaglibXmlnsAttrs,
+                Attributes taglibAttrs, Mark start, Node parent) {
+            super(qName, SCRIPTLET_ACTION, nonTaglibXmlnsAttrs, taglibAttrs,
+                    start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents an EL expression. Expressions in attributes are embedded in
+     * the attribute string and not here.
+     */
+    public static class ELExpression extends Node {
+
+        private ELNode.Nodes el;
+
+        private final char type;
+
+        public ELExpression(char type, String text, Mark start, Node parent) {
+            super(null, null, text, start, parent);
+            this.type = type;
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public void setEL(ELNode.Nodes el) {
+            this.el = el;
+        }
+
+        public ELNode.Nodes getEL() {
+            return el;
+        }
+
+        public char getType() {
+            return this.type;
+        }
+    }
+
+    /**
+     * Represents a param action
+     */
+    public static class ParamAction extends Node {
+
+        JspAttribute value;
+
+        public ParamAction(Attributes attrs, Mark start, Node parent) {
+            this(JSP_PARAM_ACTION, attrs, null, null, start, parent);
+        }
+
+        public ParamAction(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, PARAM_ACTION, attrs, nonTaglibXmlnsAttrs, taglibAttrs,
+                    start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public void setValue(JspAttribute value) {
+            this.value = value;
+        }
+
+        public JspAttribute getValue() {
+            return value;
+        }
+    }
+
+    /**
+     * Represents a params action
+     */
+    public static class ParamsAction extends Node {
+
+        public ParamsAction(Mark start, Node parent) {
+            this(JSP_PARAMS_ACTION, null, null, start, parent);
+        }
+
+        public ParamsAction(String qName, Attributes nonTaglibXmlnsAttrs,
+                Attributes taglibAttrs, Mark start, Node parent) {
+            super(qName, PARAMS_ACTION, null, nonTaglibXmlnsAttrs, taglibAttrs,
+                    start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents a fallback action
+     */
+    public static class FallBackAction extends Node {
+
+        public FallBackAction(Mark start, Node parent) {
+            this(JSP_FALLBACK_ACTION, null, null, start, parent);
+        }
+
+        public FallBackAction(String qName, Attributes nonTaglibXmlnsAttrs,
+                Attributes taglibAttrs, Mark start, Node parent) {
+            super(qName, FALLBACK_ACTION, null, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents an include action
+     */
+    public static class IncludeAction extends Node {
+
+        private JspAttribute page;
+
+        public IncludeAction(Attributes attrs, Mark start, Node parent) {
+            this(JSP_INCLUDE_ACTION, attrs, null, null, start, parent);
+        }
+
+        public IncludeAction(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, INCLUDE_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public void setPage(JspAttribute page) {
+            this.page = page;
+        }
+
+        public JspAttribute getPage() {
+            return page;
+        }
+    }
+
+    /**
+     * Represents a forward action
+     */
+    public static class ForwardAction extends Node {
+
+        private JspAttribute page;
+
+        public ForwardAction(Attributes attrs, Mark start, Node parent) {
+            this(JSP_FORWARD_ACTION, attrs, null, null, start, parent);
+        }
+
+        public ForwardAction(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, FORWARD_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public void setPage(JspAttribute page) {
+            this.page = page;
+        }
+
+        public JspAttribute getPage() {
+            return page;
+        }
+    }
+
+    /**
+     * Represents a getProperty action
+     */
+    public static class GetProperty extends Node {
+
+        public GetProperty(Attributes attrs, Mark start, Node parent) {
+            this(JSP_GET_PROPERTY_ACTION, attrs, null, null, start, parent);
+        }
+
+        public GetProperty(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, GET_PROPERTY_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents a setProperty action
+     */
+    public static class SetProperty extends Node {
+
+        private JspAttribute value;
+
+        public SetProperty(Attributes attrs, Mark start, Node parent) {
+            this(JSP_SET_PROPERTY_ACTION, attrs, null, null, start, parent);
+        }
+
+        public SetProperty(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, SET_PROPERTY_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public void setValue(JspAttribute value) {
+            this.value = value;
+        }
+
+        public JspAttribute getValue() {
+            return value;
+        }
+    }
+
+    /**
+     * Represents a useBean action
+     */
+    public static class UseBean extends Node {
+
+        JspAttribute beanName;
+
+        public UseBean(Attributes attrs, Mark start, Node parent) {
+            this(JSP_USE_BEAN_ACTION, attrs, null, null, start, parent);
+        }
+
+        public UseBean(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, USE_BEAN_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public void setBeanName(JspAttribute beanName) {
+            this.beanName = beanName;
+        }
+
+        public JspAttribute getBeanName() {
+            return beanName;
+        }
+    }
+
+    /**
+     * Represents a plugin action
+     */
+    public static class PlugIn extends Node {
+
+        private JspAttribute width;
+
+        private JspAttribute height;
+
+        public PlugIn(Attributes attrs, Mark start, Node parent) {
+            this(JSP_PLUGIN_ACTION, attrs, null, null, start, parent);
+        }
+
+        public PlugIn(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, PLUGIN_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public void setHeight(JspAttribute height) {
+            this.height = height;
+        }
+
+        public void setWidth(JspAttribute width) {
+            this.width = width;
+        }
+
+        public JspAttribute getHeight() {
+            return height;
+        }
+
+        public JspAttribute getWidth() {
+            return width;
+        }
+    }
+
+    /**
+     * Represents an uninterpreted tag, from a Jsp document
+     */
+    public static class UninterpretedTag extends Node {
+
+        private JspAttribute[] jspAttrs;
+
+        public UninterpretedTag(String qName, String localName,
+                Attributes attrs, Attributes nonTaglibXmlnsAttrs,
+                Attributes taglibAttrs, Mark start, Node parent) {
+            super(qName, localName, attrs, nonTaglibXmlnsAttrs, taglibAttrs,
+                    start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public void setJspAttributes(JspAttribute[] jspAttrs) {
+            this.jspAttrs = jspAttrs;
+        }
+
+        public JspAttribute[] getJspAttributes() {
+            return jspAttrs;
+        }
+    }
+
+    /**
+     * Represents a <jsp:element>.
+     */
+    public static class JspElement extends Node {
+
+        private JspAttribute[] jspAttrs;
+
+        private JspAttribute nameAttr;
+
+        public JspElement(Attributes attrs, Mark start, Node parent) {
+            this(JSP_ELEMENT_ACTION, attrs, null, null, start, parent);
+        }
+
+        public JspElement(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, ELEMENT_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public void setJspAttributes(JspAttribute[] jspAttrs) {
+            this.jspAttrs = jspAttrs;
+        }
+
+        public JspAttribute[] getJspAttributes() {
+            return jspAttrs;
+        }
+
+        /*
+         * Sets the XML-style 'name' attribute
+         */
+        public void setNameAttribute(JspAttribute nameAttr) {
+            this.nameAttr = nameAttr;
+        }
+
+        /*
+         * Gets the XML-style 'name' attribute
+         */
+        public JspAttribute getNameAttribute() {
+            return this.nameAttr;
+        }
+    }
+
+    /**
+     * Represents a <jsp:output>.
+     */
+    public static class JspOutput extends Node {
+
+        public JspOutput(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+            super(qName, OUTPUT_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Collected information about child elements. Used by nodes like CustomTag,
+     * JspBody, and NamedAttribute. The information is set in the Collector.
+     */
+    public static class ChildInfo {
+        private boolean scriptless; // true if the tag and its body
+
+        // contain no scripting elements.
+        private boolean hasUseBean;
+
+        private boolean hasIncludeAction;
+
+        private boolean hasParamAction;
+
+        private boolean hasSetProperty;
+
+        private boolean hasScriptingVars;
+
+        public void setScriptless(boolean s) {
+            scriptless = s;
+        }
+
+        public boolean isScriptless() {
+            return scriptless;
+        }
+
+        public void setHasUseBean(boolean u) {
+            hasUseBean = u;
+        }
+
+        public boolean hasUseBean() {
+            return hasUseBean;
+        }
+
+        public void setHasIncludeAction(boolean i) {
+            hasIncludeAction = i;
+        }
+
+        public boolean hasIncludeAction() {
+            return hasIncludeAction;
+        }
+
+        public void setHasParamAction(boolean i) {
+            hasParamAction = i;
+        }
+
+        public boolean hasParamAction() {
+            return hasParamAction;
+        }
+
+        public void setHasSetProperty(boolean s) {
+            hasSetProperty = s;
+        }
+
+        public boolean hasSetProperty() {
+            return hasSetProperty;
+        }
+
+        public void setHasScriptingVars(boolean s) {
+            hasScriptingVars = s;
+        }
+
+        public boolean hasScriptingVars() {
+            return hasScriptingVars;
+        }
+    }
+
+    /**
+     * Represents a custom tag
+     */
+    public static class CustomTag extends Node {
+
+        private String uri;
+
+        private String prefix;
+
+        private JspAttribute[] jspAttrs;
+
+        private TagData tagData;
+
+        private String tagHandlerPoolName;
+
+        private TagInfo tagInfo;
+
+        private TagFileInfo tagFileInfo;
+
+        private Class<?> tagHandlerClass;
+
+        private VariableInfo[] varInfos;
+
+        private int customNestingLevel;
+
+        private ChildInfo childInfo;
+
+        private boolean implementsIterationTag;
+
+        private boolean implementsBodyTag;
+
+        private boolean implementsTryCatchFinally;
+
+        private boolean implementsJspIdConsumer;
+
+        private boolean implementsSimpleTag;
+
+        private boolean implementsDynamicAttributes;
+
+        private List<Object> atBeginScriptingVars;
+
+        private List<Object> atEndScriptingVars;
+
+        private List<Object> nestedScriptingVars;
+
+        private Node.CustomTag customTagParent;
+
+        private Integer numCount;
+
+        private boolean useTagPlugin;
+
+        private TagPluginContext tagPluginContext;
+
+        /**
+         * The following two fields are used for holding the Java scriptlets
+         * that the tag plugins may generate. Meaningful only if useTagPlugin is
+         * true; Could move them into TagPluginContextImpl, but we'll need to
+         * cast tagPluginContext to TagPluginContextImpl all the time...
+         */
+        private Nodes atSTag;
+
+        private Nodes atETag;
+
+        /*
+         * Constructor for custom action implemented by tag handler.
+         */
+        public CustomTag(String qName, String prefix, String localName,
+                String uri, Attributes attrs, Mark start, Node parent,
+                TagInfo tagInfo, Class<?> tagHandlerClass) {
+            this(qName, prefix, localName, uri, attrs, null, null, start,
+                    parent, tagInfo, tagHandlerClass);
+        }
+
+        /*
+         * Constructor for custom action implemented by tag handler.
+         */
+        public CustomTag(String qName, String prefix, String localName,
+                String uri, Attributes attrs, Attributes nonTaglibXmlnsAttrs,
+                Attributes taglibAttrs, Mark start, Node parent,
+                TagInfo tagInfo, Class<?> tagHandlerClass) {
+            super(qName, localName, attrs, nonTaglibXmlnsAttrs, taglibAttrs,
+                    start, parent);
+
+            this.uri = uri;
+            this.prefix = prefix;
+            this.tagInfo = tagInfo;
+            this.tagHandlerClass = tagHandlerClass;
+            this.customNestingLevel = makeCustomNestingLevel();
+            this.childInfo = new ChildInfo();
+
+            this.implementsIterationTag = IterationTag.class
+                    .isAssignableFrom(tagHandlerClass);
+            this.implementsBodyTag = BodyTag.class
+                    .isAssignableFrom(tagHandlerClass);
+            this.implementsTryCatchFinally = TryCatchFinally.class
+                    .isAssignableFrom(tagHandlerClass);
+            this.implementsSimpleTag = SimpleTag.class
+                    .isAssignableFrom(tagHandlerClass);
+            this.implementsDynamicAttributes = DynamicAttributes.class
+                    .isAssignableFrom(tagHandlerClass);
+            this.implementsJspIdConsumer = JspIdConsumer.class
+                    .isAssignableFrom(tagHandlerClass);
+        }
+
+        /*
+         * Constructor for custom action implemented by tag file.
+         */
+        public CustomTag(String qName, String prefix, String localName,
+                String uri, Attributes attrs, Mark start, Node parent,
+                TagFileInfo tagFileInfo) {
+            this(qName, prefix, localName, uri, attrs, null, null, start,
+                    parent, tagFileInfo);
+        }
+
+        /*
+         * Constructor for custom action implemented by tag file.
+         */
+        public CustomTag(String qName, String prefix, String localName,
+                String uri, Attributes attrs, Attributes nonTaglibXmlnsAttrs,
+                Attributes taglibAttrs, Mark start, Node parent,
+                TagFileInfo tagFileInfo) {
+
+            super(qName, localName, attrs, nonTaglibXmlnsAttrs, taglibAttrs,
+                    start, parent);
+
+            this.uri = uri;
+            this.prefix = prefix;
+            this.tagFileInfo = tagFileInfo;
+            this.tagInfo = tagFileInfo.getTagInfo();
+            this.customNestingLevel = makeCustomNestingLevel();
+            this.childInfo = new ChildInfo();
+
+            this.implementsIterationTag = false;
+            this.implementsBodyTag = false;
+            this.implementsTryCatchFinally = false;
+            this.implementsSimpleTag = true;
+            this.implementsJspIdConsumer = false;
+            this.implementsDynamicAttributes = tagInfo.hasDynamicAttributes();
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        /**
+         * @return The URI namespace that this custom action belongs to
+         */
+        public String getURI() {
+            return this.uri;
+        }
+
+        /**
+         * @return The tag prefix
+         */
+        public String getPrefix() {
+            return prefix;
+        }
+
+        public void setJspAttributes(JspAttribute[] jspAttrs) {
+            this.jspAttrs = jspAttrs;
+        }
+
+        public JspAttribute[] getJspAttributes() {
+            return jspAttrs;
+        }
+
+        public ChildInfo getChildInfo() {
+            return childInfo;
+        }
+
+        public void setTagData(TagData tagData) {
+            this.tagData = tagData;
+            this.varInfos = tagInfo.getVariableInfo(tagData);
+            if (this.varInfos == null) {
+                this.varInfos = ZERO_VARIABLE_INFO;
+            }
+        }
+
+        public TagData getTagData() {
+            return tagData;
+        }
+
+        public void setTagHandlerPoolName(String s) {
+            tagHandlerPoolName = s;
+        }
+
+        public String getTagHandlerPoolName() {
+            return tagHandlerPoolName;
+        }
+
+        public TagInfo getTagInfo() {
+            return tagInfo;
+        }
+
+        public TagFileInfo getTagFileInfo() {
+            return tagFileInfo;
+        }
+
+        /*
+         * @return true if this custom action is supported by a tag file, false
+         * otherwise
+         */
+        public boolean isTagFile() {
+            return tagFileInfo != null;
+        }
+
+        public Class<?> getTagHandlerClass() {
+            return tagHandlerClass;
+        }
+
+        public void setTagHandlerClass(Class<?> hc) {
+            tagHandlerClass = hc;
+        }
+
+        public boolean implementsIterationTag() {
+            return implementsIterationTag;
+        }
+
+        public boolean implementsBodyTag() {
+            return implementsBodyTag;
+        }
+
+        public boolean implementsTryCatchFinally() {
+            return implementsTryCatchFinally;
+        }
+
+        public boolean implementsJspIdConsumer() {
+            return implementsJspIdConsumer;
+        }
+
+        public boolean implementsSimpleTag() {
+            return implementsSimpleTag;
+        }
+
+        public boolean implementsDynamicAttributes() {
+            return implementsDynamicAttributes;
+        }
+
+        public TagVariableInfo[] getTagVariableInfos() {
+            return tagInfo.getTagVariableInfos();
+        }
+
+        public VariableInfo[] getVariableInfos() {
+            return varInfos;
+        }
+
+        public void setCustomTagParent(Node.CustomTag n) {
+            this.customTagParent = n;
+        }
+
+        public Node.CustomTag getCustomTagParent() {
+            return this.customTagParent;
+        }
+
+        public void setNumCount(Integer count) {
+            this.numCount = count;
+        }
+
+        public Integer getNumCount() {
+            return this.numCount;
+        }
+
+        public void setScriptingVars(List<Object> vec, int scope) {
+            switch (scope) {
+            case VariableInfo.AT_BEGIN:
+                this.atBeginScriptingVars = vec;
+                break;
+            case VariableInfo.AT_END:
+                this.atEndScriptingVars = vec;
+                break;
+            case VariableInfo.NESTED:
+                this.nestedScriptingVars = vec;
+                break;
+            }
+        }
+
+        /*
+         * Gets the scripting variables for the given scope that need to be
+         * declared.
+         */
+        public List<Object> getScriptingVars(int scope) {
+            List<Object> vec = null;
+
+            switch (scope) {
+            case VariableInfo.AT_BEGIN:
+                vec = this.atBeginScriptingVars;
+                break;
+            case VariableInfo.AT_END:
+                vec = this.atEndScriptingVars;
+                break;
+            case VariableInfo.NESTED:
+                vec = this.nestedScriptingVars;
+                break;
+            }
+
+            return vec;
+        }
+
+        /*
+         * Gets this custom tag's custom nesting level, which is given as the
+         * number of times this custom tag is nested inside itself.
+         */
+        public int getCustomNestingLevel() {
+            return customNestingLevel;
+        }
+
+        /**
+         * Checks to see if the attribute of the given name is of type
+         * JspFragment.
+         */
+        public boolean checkIfAttributeIsJspFragment(String name) {
+            boolean result = false;
+
+            TagAttributeInfo[] attributes = tagInfo.getAttributes();
+            for (int i = 0; i < attributes.length; i++) {
+                if (attributes[i].getName().equals(name)
+                        && attributes[i].isFragment()) {
+                    result = true;
+                    break;
+                }
+            }
+
+            return result;
+        }
+
+        public void setUseTagPlugin(boolean use) {
+            useTagPlugin = use;
+        }
+
+        public boolean useTagPlugin() {
+            return useTagPlugin;
+        }
+
+        public void setTagPluginContext(TagPluginContext tagPluginContext) {
+            this.tagPluginContext = tagPluginContext;
+        }
+
+        public TagPluginContext getTagPluginContext() {
+            return tagPluginContext;
+        }
+
+        public void setAtSTag(Nodes sTag) {
+            atSTag = sTag;
+        }
+
+        public Nodes getAtSTag() {
+            return atSTag;
+        }
+
+        public void setAtETag(Nodes eTag) {
+            atETag = eTag;
+        }
+
+        public Nodes getAtETag() {
+            return atETag;
+        }
+
+        /*
+         * Computes this custom tag's custom nesting level, which corresponds to
+         * the number of times this custom tag is nested inside itself.
+         * 
+         * Example:
+         * 
+         * <g:h> <a:b> -- nesting level 0 <c:d> <e:f> <a:b> -- nesting level 1
+         * <a:b> -- nesting level 2 </a:b> </a:b> <a:b> -- nesting level 1
+         * </a:b> </e:f> </c:d> </a:b> </g:h>
+         * 
+         * @return Custom tag's nesting level
+         */
+        private int makeCustomNestingLevel() {
+            int n = 0;
+            Node p = parent;
+            while (p != null) {
+                if ((p instanceof Node.CustomTag)
+                        && qName.equals(((Node.CustomTag) p).qName)) {
+                    n++;
+                }
+                p = p.parent;
+            }
+            return n;
+        }
+
+        /**
+         * Returns true if this custom action has an empty body, and false
+         * otherwise.
+         * 
+         * A custom action is considered to have an empty body if the following
+         * holds true: - getBody() returns null, or - all immediate children are
+         * jsp:attribute actions, or - the action's jsp:body is empty.
+         */
+        public boolean hasEmptyBody() {
+            boolean hasEmptyBody = true;
+            Nodes nodes = getBody();
+            if (nodes != null) {
+                int numChildNodes = nodes.size();
+                for (int i = 0; i < numChildNodes; i++) {
+                    Node n = nodes.getNode(i);
+                    if (!(n instanceof NamedAttribute)) {
+                        if (n instanceof JspBody) {
+                            hasEmptyBody = (n.getBody() == null);
+                        } else {
+                            hasEmptyBody = false;
+                        }
+                        break;
+                    }
+                }
+            }
+
+            return hasEmptyBody;
+        }
+    }
+
+    /**
+     * Used as a placeholder for the evaluation code of a custom action
+     * attribute (used by the tag plugin machinery only).
+     */
+    public static class AttributeGenerator extends Node {
+        String name; // name of the attribute
+
+        CustomTag tag; // The tag this attribute belongs to
+
+        public AttributeGenerator(Mark start, String name, CustomTag tag) {
+            super(start, null);
+            this.name = name;
+            this.tag = tag;
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public CustomTag getTag() {
+            return tag;
+        }
+    }
+
+    /**
+     * Represents the body of a &lt;jsp:text&gt; element
+     */
+    public static class JspText extends Node {
+
+        public JspText(String qName, Attributes nonTaglibXmlnsAttrs,
+                Attributes taglibAttrs, Mark start, Node parent) {
+            super(qName, TEXT_ACTION, null, nonTaglibXmlnsAttrs, taglibAttrs,
+                    start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+    }
+
+    /**
+     * Represents a Named Attribute (&lt;jsp:attribute&gt;)
+     */
+    public static class NamedAttribute extends Node {
+
+        // A unique temporary variable name suitable for code generation
+        private String temporaryVariableName;
+
+        // True if this node is to be trimmed, or false otherwise
+        private boolean trim = true;
+
+        // True if this attribute should be omitted from the output if
+        // used with a <jsp:element>, otherwise false
+        private JspAttribute omit;
+
+        private ChildInfo childInfo;
+
+        private String name;
+
+        private String localName;
+
+        private String prefix;
+
+        public NamedAttribute(Attributes attrs, Mark start, Node parent) {
+            this(JSP_ATTRIBUTE_ACTION, attrs, null, null, start, parent);
+        }
+
+        public NamedAttribute(String qName, Attributes attrs,
+                Attributes nonTaglibXmlnsAttrs, Attributes taglibAttrs,
+                Mark start, Node parent) {
+
+            super(qName, ATTRIBUTE_ACTION, attrs, nonTaglibXmlnsAttrs,
+                    taglibAttrs, start, parent);
+            if ("false".equals(this.getAttributeValue("trim"))) {
+                // (if null or true, leave default of true)
+                trim = false;
+            }
+            childInfo = new ChildInfo();
+            name = this.getAttributeValue("name");
+            if (name != null) {
+                // Mandatory attribute "name" will be checked in Validator
+                localName = name;
+                int index = name.indexOf(':');
+                if (index != -1) {
+                    prefix = name.substring(0, index);
+                    localName = name.substring(index + 1);
+                }
+            }
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public String getName() {
+            return this.name;
+        }
+
+        @Override
+        public String getLocalName() {
+            return this.localName;
+        }
+
+        public String getPrefix() {
+            return this.prefix;
+        }
+
+        public ChildInfo getChildInfo() {
+            return this.childInfo;
+        }
+
+        public boolean isTrim() {
+            return trim;
+        }
+
+        public void setOmit(JspAttribute omit) {
+            this.omit = omit;
+        }
+        
+        public JspAttribute getOmit() {
+            return omit;
+        }
+
+        /**
+         * @return A unique temporary variable name to store the result in.
+         *         (this probably could go elsewhere, but it's convenient here)
+         */
+        public String getTemporaryVariableName() {
+            if (temporaryVariableName == null) {
+                temporaryVariableName = getRoot().nextTemporaryVariableName();
+            }
+            return temporaryVariableName;
+        }
+
+        /*
+         * Get the attribute value from this named attribute (<jsp:attribute>).
+         * Since this method is only for attributes that are not rtexpr, we can
+         * assume the body of the jsp:attribute is a template text.
+         */
+        @Override
+        public String getText() {
+
+            class AttributeVisitor extends Visitor {
+                String attrValue = null;
+
+                @Override
+                public void visit(TemplateText txt) {
+                    attrValue = txt.getText();
+                }
+
+                public String getAttrValue() {
+                    return attrValue;
+                }
+            }
+
+            // According to JSP 2.0, if the body of the <jsp:attribute>
+            // action is empty, it is equivalent of specifying "" as the value
+            // of the attribute.
+            String text = "";
+            if (getBody() != null) {
+                AttributeVisitor attributeVisitor = new AttributeVisitor();
+                try {
+                    getBody().visit(attributeVisitor);
+                } catch (JasperException e) {
+                }
+                text = attributeVisitor.getAttrValue();
+            }
+
+            return text;
+        }
+    }
+
+    /**
+     * Represents a JspBody node (&lt;jsp:body&gt;)
+     */
+    public static class JspBody extends Node {
+
+        private ChildInfo childInfo;
+
+        public JspBody(Mark start, Node parent) {
+            this(JSP_BODY_ACTION, null, null, start, parent);
+        }
+
+        public JspBody(String qName, Attributes nonTaglibXmlnsAttrs,
+                Attributes taglibAttrs, Mark start, Node parent) {
+            super(qName, BODY_ACTION, null, nonTaglibXmlnsAttrs, taglibAttrs,
+                    start, parent);
+            this.childInfo = new ChildInfo();
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        public ChildInfo getChildInfo() {
+            return childInfo;
+        }
+    }
+
+    /**
+     * Represents a template text string
+     */
+    public static class TemplateText extends Node {
+
+        private ArrayList<Integer> extraSmap = null;
+
+        public TemplateText(String text, Mark start, Node parent) {
+            super(null, null, text, start, parent);
+        }
+
+        @Override
+        public void accept(Visitor v) throws JasperException {
+            v.visit(this);
+        }
+
+        /**
+         * Trim all whitespace from the left of the template text
+         */
+        public void ltrim() {
+            int index = 0;
+            while ((index < text.length()) && (text.charAt(index) <= ' ')) {
+                index++;
+            }
+            text = text.substring(index);
+        }
+
+        public void setText(String text) {
+            this.text = text;
+        }
+
+        /**
+         * Trim all whitespace from the right of the template text
+         */
+        public void rtrim() {
+            int index = text.length();
+            while ((index > 0) && (text.charAt(index - 1) <= ' ')) {
+                index--;
+            }
+            text = text.substring(0, index);
+        }
+
+        /**
+         * Returns true if this template text contains whitespace only.
+         */
+        public boolean isAllSpace() {
+            boolean isAllSpace = true;
+            for (int i = 0; i < text.length(); i++) {
+                if (!Character.isWhitespace(text.charAt(i))) {
+                    isAllSpace = false;
+                    break;
+                }
+            }
+            return isAllSpace;
+        }
+
+        /**
+         * Add a source to Java line mapping
+         * 
+         * @param srcLine
+         *            The position of the source line, relative to the line at
+         *            the start of this node. The corresponding java line is
+         *            assumed to be consecutive, i.e. one more than the last.
+         */
+        public void addSmap(int srcLine) {
+            if (extraSmap == null) {
+                extraSmap = new ArrayList<Integer>();
+            }
+            extraSmap.add(new Integer(srcLine));
+        }
+
+        public ArrayList<Integer> getExtraSmap() {
+            return extraSmap;
+        }
+    }
+
+    /***************************************************************************
+     * Auxiliary classes used in Node
+     */
+
+    /**
+     * Represents attributes that can be request time expressions.
+     * 
+     * Can either be a plain attribute, an attribute that represents a request
+     * time expression value, or a named attribute (specified using the
+     * jsp:attribute standard action).
+     */
+
+    public static class JspAttribute {
+
+        private String qName;
+
+        private String uri;
+
+        private String localName;
+
+        private String value;
+
+        private boolean expression;
+
+        private boolean dynamic;
+
+        private final ELNode.Nodes el;
+
+        private final TagAttributeInfo tai;
+
+        // If true, this JspAttribute represents a <jsp:attribute>
+        private boolean namedAttribute;
+
+        // The node in the parse tree for the NamedAttribute
+        private NamedAttribute namedAttributeNode;
+
+        JspAttribute(TagAttributeInfo tai, String qName, String uri,
+                String localName, String value, boolean expr, ELNode.Nodes el,
+                boolean dyn) {
+            this.qName = qName;
+            this.uri = uri;
+            this.localName = localName;
+            this.value = value;
+            this.namedAttributeNode = null;
+            this.expression = expr;
+            this.el = el;
+            this.dynamic = dyn;
+            this.namedAttribute = false;
+            this.tai = tai;
+        }
+
+        /**
+         * Allow node to validate itself
+         * 
+         * @param ef
+         * @param ctx
+         * @throws ELException
+         */
+        public void validateEL(ExpressionFactory ef, ELContext ctx)
+                throws ELException {
+            if (this.el != null) {
+                // determine exact type
+                ef.createValueExpression(ctx, this.value, String.class);
+            }
+        }
+
+        /**
+         * Use this constructor if the JspAttribute represents a named
+         * attribute. In this case, we have to store the nodes of the body of
+         * the attribute.
+         */
+        JspAttribute(NamedAttribute na, TagAttributeInfo tai, boolean dyn) {
+            this.qName = na.getName();
+            this.localName = na.getLocalName();
+            this.value = null;
+            this.namedAttributeNode = na;
+            this.expression = false;
+            this.el = null;
+            this.dynamic = dyn;
+            this.namedAttribute = true;
+            this.tai = null;
+        }
+
+        /**
+         * @return The name of the attribute
+         */
+        public String getName() {
+            return qName;
+        }
+
+        /**
+         * @return The local name of the attribute
+         */
+        public String getLocalName() {
+            return localName;
+        }
+
+        /**
+         * @return The namespace of the attribute, or null if in the default
+         *         namespace
+         */
+        public String getURI() {
+            return uri;
+        }
+
+        public TagAttributeInfo getTagAttributeInfo() {
+            return this.tai;
+        }
+
+        /**
+         * 
+         * @return return true if there's TagAttributeInfo meaning we need to
+         *         assign a ValueExpression
+         */
+        public boolean isDeferredInput() {
+            return (this.tai != null) ? this.tai.isDeferredValue() : false;
+        }
+
+        /**
+         * 
+         * @return return true if there's TagAttributeInfo meaning we need to
+         *         assign a MethodExpression
+         */
+        public boolean isDeferredMethodInput() {
+            return (this.tai != null) ? this.tai.isDeferredMethod() : false;
+        }
+
+        public String getExpectedTypeName() {
+            if (this.tai != null) {
+                if (this.isDeferredInput()) {
+                    return this.tai.getExpectedTypeName();
+                } else if (this.isDeferredMethodInput()) {
+                    String m = this.tai.getMethodSignature();
+                    if (m != null) {
+                        int rti = m.trim().indexOf(' ');
+                        if (rti > 0) {
+                            return m.substring(0, rti).trim();
+                        }
+                    }
+                }
+            }
+            return "java.lang.Object";
+        }
+        
+        public String[] getParameterTypeNames() {
+            if (this.tai != null) {
+                if (this.isDeferredMethodInput()) {
+                    String m = this.tai.getMethodSignature();
+                    if (m != null) {
+                        m = m.trim();
+                        m = m.substring(m.indexOf('(') + 1);
+                        m = m.substring(0, m.length() - 1);
+                        if (m.trim().length() > 0) {
+                            String[] p = m.split(",");
+                            for (int i = 0; i < p.length; i++) {
+                                p[i] = p[i].trim();
+                            }
+                            return p;
+                        }
+                    }
+                }
+            }
+            return new String[0];
+        }
+
+        /**
+         * Only makes sense if namedAttribute is false.
+         * 
+         * @return the value for the attribute, or the expression string
+         *         (stripped of "<%=", "%>", "%=", or "%" but containing "${"
+         *         and "}" for EL expressions)
+         */
+        public String getValue() {
+            return value;
+        }
+
+        /**
+         * Only makes sense if namedAttribute is true.
+         * 
+         * @return the nodes that evaluate to the body of this attribute.
+         */
+        public NamedAttribute getNamedAttributeNode() {
+            return namedAttributeNode;
+        }
+
+        /**
+         * @return true if the value represents a traditional rtexprvalue
+         */
+        public boolean isExpression() {
+            return expression;
+        }
+
+        /**
+         * @return true if the value represents a NamedAttribute value.
+         */
+        public boolean isNamedAttribute() {
+            return namedAttribute;
+        }
+
+        /**
+         * @return true if the value represents an expression that should be fed
+         *         to the expression interpreter
+         *         false for string literals or rtexprvalues that should not be
+         *         interpreted or reevaluated
+         */
+        public boolean isELInterpreterInput() {
+            return el != null || this.isDeferredInput()
+                    || this.isDeferredMethodInput();
+        }
+
+        /**
+         * @return true if the value is a string literal known at translation
+         *         time.
+         */
+        public boolean isLiteral() {
+            return !expression && (el != null) && !namedAttribute;
+        }
+
+        /**
+         * XXX
+         */
+        public boolean isDynamic() {
+            return dynamic;
+        }
+
+        public ELNode.Nodes getEL() {
+            return el;
+        }
+    }
+
+    /**
+     * An ordered list of Node, used to represent the body of an element, or a
+     * jsp page of jsp document.
+     */
+    public static class Nodes {
+
+        private List<Node> list;
+
+        private Node.Root root; // null if this is not a page
+
+        private boolean generatedInBuffer;
+
+        public Nodes() {
+            list = new Vector<Node>();
+        }
+
+        public Nodes(Node.Root root) {
+            this.root = root;
+            list = new Vector<Node>();
+            list.add(root);
+        }
+
+        /**
+         * Appends a node to the list
+         * 
+         * @param n
+         *            The node to add
+         */
+        public void add(Node n) {
+            list.add(n);
+            root = null;
+        }
+
+        /**
+         * Removes the given node from the list.
+         * 
+         * @param n
+         *            The node to be removed
+         */
+        public void remove(Node n) {
+            list.remove(n);
+        }
+
+        /**
+         * Visit the nodes in the list with the supplied visitor
+         * 
+         * @param v
+         *            The visitor used
+         */
+        public void visit(Visitor v) throws JasperException {
+            Iterator<Node> iter = list.iterator();
+            while (iter.hasNext()) {
+                Node n = iter.next();
+                n.accept(v);
+            }
+        }
+
+        public int size() {
+            return list.size();
+        }
+
+        public Node getNode(int index) {
+            Node n = null;
+            try {
+                n = list.get(index);
+            } catch (ArrayIndexOutOfBoundsException e) {
+            }
+            return n;
+        }
+
+        public Node.Root getRoot() {
+            return root;
+        }
+
+        public boolean isGeneratedInBuffer() {
+            return generatedInBuffer;
+        }
+
+        public void setGeneratedInBuffer(boolean g) {
+            generatedInBuffer = g;
+        }
+    }
+
+    /**
+     * A visitor class for visiting the node. This class also provides the
+     * default action (i.e. nop) for each of the child class of the Node. An
+     * actual visitor should extend this class and supply the visit method for
+     * the nodes that it cares.
+     */
+    public static class Visitor {
+
+        /**
+         * This method provides a place to put actions that are common to all
+         * nodes. Override this in the child visitor class if need to.
+         */
+        @SuppressWarnings("unused")
+        protected void doVisit(Node n) throws JasperException {
+            // NOOP by default
+        }
+
+        /**
+         * Visit the body of a node, using the current visitor
+         */
+        protected void visitBody(Node n) throws JasperException {
+            if (n.getBody() != null) {
+                n.getBody().visit(this);
+            }
+        }
+
+        public void visit(Root n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(JspRoot n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(PageDirective n) throws JasperException {
+            doVisit(n);
+        }
+
+        public void visit(TagDirective n) throws JasperException {
+            doVisit(n);
+        }
+
+        public void visit(IncludeDirective n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(TaglibDirective n) throws JasperException {
+            doVisit(n);
+        }
+
+        public void visit(AttributeDirective n) throws JasperException {
+            doVisit(n);
+        }
+
+        public void visit(VariableDirective n) throws JasperException {
+            doVisit(n);
+        }
+
+        public void visit(Comment n) throws JasperException {
+            doVisit(n);
+        }
+
+        public void visit(Declaration n) throws JasperException {
+            doVisit(n);
+        }
+
+        public void visit(Expression n) throws JasperException {
+            doVisit(n);
+        }
+
+        public void visit(Scriptlet n) throws JasperException {
+            doVisit(n);
+        }
+
+        public void visit(ELExpression n) throws JasperException {
+            doVisit(n);
+        }
+
+        public void visit(IncludeAction n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(ForwardAction n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(GetProperty n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(SetProperty n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(ParamAction n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(ParamsAction n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(FallBackAction n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(UseBean n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(PlugIn n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(CustomTag n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(UninterpretedTag n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(JspElement n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(JspText n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(NamedAttribute n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(JspBody n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(InvokeAction n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(DoBodyAction n) throws JasperException {
+            doVisit(n);
+            visitBody(n);
+        }
+
+        public void visit(TemplateText n) throws JasperException {
+            doVisit(n);
+        }
+
+        public void visit(JspOutput n) throws JasperException {
+            doVisit(n);
+        }
+
+        public void visit(AttributeGenerator n) throws JasperException {
+            doVisit(n);
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/PageDataImpl.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/PageDataImpl.java
new file mode 100644
index 0000000..705fbc9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/PageDataImpl.java
@@ -0,0 +1,747 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.compiler;
+
+import java.io.ByteArrayInputStream;
+import java.io.CharArrayWriter;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ListIterator;
+
+import javax.servlet.jsp.tagext.PageData;
+
+import org.apache.jasper.JasperException;
+import org.xml.sax.Attributes;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * An implementation of <tt>javax.servlet.jsp.tagext.PageData</tt> which
+ * builds the XML view of a given page.
+ *
+ * The XML view is built in two passes:
+ *
+ * During the first pass, the FirstPassVisitor collects the attributes of the
+ * top-level jsp:root and those of the jsp:root elements of any included
+ * pages, and adds them to the jsp:root element of the XML view.
+ * In addition, any taglib directives are converted into xmlns: attributes and
+ * added to the jsp:root element of the XML view.
+ * This pass ignores any nodes other than JspRoot and TaglibDirective.
+ *
+ * During the second pass, the SecondPassVisitor produces the XML view, using
+ * the combined jsp:root attributes determined in the first pass and any
+ * remaining pages nodes (this pass ignores any JspRoot and TaglibDirective
+ * nodes).
+ *
+ * @author Jan Luehe
+ */
+class PageDataImpl extends PageData implements TagConstants {
+
+    private static final String JSP_VERSION = "2.0";
+    private static final String CDATA_START_SECTION = "<![CDATA[\n";
+    private static final String CDATA_END_SECTION = "]]>\n";
+
+    // string buffer used to build XML view
+    private StringBuilder buf;
+
+    /**
+     * Constructor.
+     *
+     * @param page the page nodes from which to generate the XML view
+     */
+    public PageDataImpl(Node.Nodes page, Compiler compiler)
+                throws JasperException {
+
+        // First pass
+        FirstPassVisitor firstPass = new FirstPassVisitor(page.getRoot(),
+                                                          compiler.getPageInfo());
+        page.visit(firstPass);
+
+        // Second pass
+        buf = new StringBuilder();
+        SecondPassVisitor secondPass
+            = new SecondPassVisitor(page.getRoot(), buf, compiler,
+                                    firstPass.getJspIdPrefix());
+        page.visit(secondPass);
+    }
+
+    /**
+     * Returns the input stream of the XML view.
+     *
+     * @return the input stream of the XML view
+     */
+    @Override
+    public InputStream getInputStream() {
+        // Turn StringBuilder into InputStream
+        try {
+            return new ByteArrayInputStream(buf.toString().getBytes("UTF-8"));
+        } catch (UnsupportedEncodingException uee) {
+            // should never happen
+            throw new RuntimeException(uee.toString());
+        }
+    }
+
+    /*
+     * First-pass Visitor for JspRoot nodes (representing jsp:root elements)
+     * and TablibDirective nodes, ignoring any other nodes.
+     *
+     * The purpose of this Visitor is to collect the attributes of the
+     * top-level jsp:root and those of the jsp:root elements of any included
+     * pages, and add them to the jsp:root element of the XML view.
+     * In addition, this Visitor converts any taglib directives into xmlns:
+     * attributes and adds them to the jsp:root element of the XML view.
+     */
+    static class FirstPassVisitor
+                extends Node.Visitor implements TagConstants {
+
+        private Node.Root root;
+        private AttributesImpl rootAttrs;
+        private PageInfo pageInfo;
+
+        // Prefix for the 'id' attribute
+        private String jspIdPrefix;
+
+        /*
+         * Constructor
+         */
+        public FirstPassVisitor(Node.Root root, PageInfo pageInfo) {
+            this.root = root;
+            this.pageInfo = pageInfo;
+            this.rootAttrs = new AttributesImpl();
+            this.rootAttrs.addAttribute("", "", "version", "CDATA",
+                                        JSP_VERSION);
+            this.jspIdPrefix = "jsp";
+        }
+
+        @Override
+        public void visit(Node.Root n) throws JasperException {
+            visitBody(n);
+            if (n == root) {
+                /*
+                 * Top-level page.
+                 *
+                 * Add
+                 *   xmlns:jsp="http://java.sun.com/JSP/Page"
+                 * attribute only if not already present.
+                 */
+                if (!JSP_URI.equals(rootAttrs.getValue("xmlns:jsp"))) {
+                    rootAttrs.addAttribute("", "", "xmlns:jsp", "CDATA",
+                                           JSP_URI);
+                }
+
+                if (pageInfo.isJspPrefixHijacked()) {
+                    /*
+                     * 'jsp' prefix has been hijacked, that is, bound to a
+                     * namespace other than the JSP namespace. This means that
+                     * when adding an 'id' attribute to each element, we can't
+                     * use the 'jsp' prefix. Therefore, create a new prefix 
+                     * (one that is unique across the translation unit) for use
+                     * by the 'id' attribute, and bind it to the JSP namespace
+                     */
+                    jspIdPrefix += "jsp";
+                    while (pageInfo.containsPrefix(jspIdPrefix)) {
+                        jspIdPrefix += "jsp";
+                    }
+                    rootAttrs.addAttribute("", "", "xmlns:" + jspIdPrefix,
+                                           "CDATA", JSP_URI);
+                }
+
+                root.setAttributes(rootAttrs);
+            }
+        }
+
+        @Override
+        public void visit(Node.JspRoot n) throws JasperException {
+            addAttributes(n.getTaglibAttributes());
+            addAttributes(n.getNonTaglibXmlnsAttributes());
+            addAttributes(n.getAttributes());
+
+            visitBody(n);
+        }
+
+        /*
+         * Converts taglib directive into "xmlns:..." attribute of jsp:root
+         * element.
+         */
+        @Override
+        public void visit(Node.TaglibDirective n) throws JasperException {
+            Attributes attrs = n.getAttributes();
+            if (attrs != null) {
+                String qName = "xmlns:" + attrs.getValue("prefix");
+                /*
+                 * According to javadocs of org.xml.sax.helpers.AttributesImpl,
+                 * the addAttribute method does not check to see if the
+                 * specified attribute is already contained in the list: This
+                 * is the application's responsibility!
+                 */
+                if (rootAttrs.getIndex(qName) == -1) {
+                    String location = attrs.getValue("uri");
+                    if (location != null) {
+                        if (location.startsWith("/")) {
+                            location = URN_JSPTLD + location;
+                        }
+                        rootAttrs.addAttribute("", "", qName, "CDATA",
+                                               location);
+                    } else {
+                        location = attrs.getValue("tagdir");
+                        rootAttrs.addAttribute("", "", qName, "CDATA",
+                                               URN_JSPTAGDIR + location);
+                    }
+                }
+            }
+        }
+
+        public String getJspIdPrefix() {
+            return jspIdPrefix;
+        }
+
+        private void addAttributes(Attributes attrs) {
+            if (attrs != null) {
+                int len = attrs.getLength();
+
+                for (int i=0; i<len; i++) {
+                    String qName = attrs.getQName(i);
+                    if ("version".equals(qName)) {
+                        continue;
+                    }
+
+                    // Bugzilla 35252: http://issues.apache.org/bugzilla/show_bug.cgi?id=35252
+                    if(rootAttrs.getIndex(qName) == -1) {
+                        rootAttrs.addAttribute(attrs.getURI(i),
+                                               attrs.getLocalName(i),
+                                               qName,
+                                               attrs.getType(i),
+                                               attrs.getValue(i));
+                    }
+                }
+            }
+        }
+    }
+
+
+    /*
+     * Second-pass Visitor responsible for producing XML view and assigning
+     * each element a unique jsp:id attribute.
+     */
+    static class SecondPassVisitor extends Node.Visitor
+                implements TagConstants {
+
+        private Node.Root root;
+        private StringBuilder buf;
+        private Compiler compiler;
+        private String jspIdPrefix;
+        private boolean resetDefaultNS = false;
+
+        // Current value of jsp:id attribute
+        private int jspId;
+
+        /*
+         * Constructor
+         */
+        public SecondPassVisitor(Node.Root root, StringBuilder buf,
+                                 Compiler compiler, String jspIdPrefix) {
+            this.root = root;
+            this.buf = buf;
+            this.compiler = compiler;
+            this.jspIdPrefix = jspIdPrefix;
+        }
+
+        /*
+         * Visits root node.
+         */
+        @Override
+    public void visit(Node.Root n) throws JasperException {
+            if (n == this.root) {
+                // top-level page
+                appendXmlProlog();
+                appendTag(n);
+            } else {
+                boolean resetDefaultNSSave = resetDefaultNS;
+                if (n.isXmlSyntax()) {
+                    resetDefaultNS = true;
+                }
+                visitBody(n);
+                resetDefaultNS = resetDefaultNSSave;
+            }
+        }
+
+        /*
+         * Visits jsp:root element of JSP page in XML syntax.
+         *
+         * Any nested jsp:root elements (from pages included via an
+         * include directive) are ignored.
+         */
+        @Override
+    public void visit(Node.JspRoot n) throws JasperException {
+            visitBody(n);
+        }
+
+        @Override
+    public void visit(Node.PageDirective n) throws JasperException {
+            appendPageDirective(n);
+        }
+
+        @Override
+    public void visit(Node.IncludeDirective n) throws JasperException {
+            // expand in place
+            visitBody(n);
+        }
+
+        @Override
+    public void visit(Node.Comment n) throws JasperException {
+            // Comments are ignored in XML view
+        }
+
+        @Override
+    public void visit(Node.Declaration n) throws JasperException {
+            appendTag(n);
+        }
+
+        @Override
+    public void visit(Node.Expression n) throws JasperException {
+            appendTag(n);
+        }
+
+        @Override
+    public void visit(Node.Scriptlet n) throws JasperException {
+            appendTag(n);
+        }
+
+        @Override
+    public void visit(Node.JspElement n) throws JasperException {
+            appendTag(n);
+        }
+
+        @Override
+    public void visit(Node.ELExpression n) throws JasperException {
+            if (!n.getRoot().isXmlSyntax()) {
+                buf.append("<").append(JSP_TEXT_ACTION);
+                buf.append(" ");
+                buf.append(jspIdPrefix);
+                buf.append(":id=\"");
+                buf.append(jspId++).append("\">");
+            }
+            buf.append("${");
+            buf.append(JspUtil.escapeXml(n.getText()));
+            buf.append("}");
+            if (!n.getRoot().isXmlSyntax()) {
+                buf.append(JSP_TEXT_ACTION_END);
+            }
+            buf.append("\n");
+        }
+
+        @Override
+    public void visit(Node.IncludeAction n) throws JasperException {
+            appendTag(n);
+        }
+    
+        @Override
+    public void visit(Node.ForwardAction n) throws JasperException {
+            appendTag(n);
+        }
+
+        @Override
+    public void visit(Node.GetProperty n) throws JasperException {
+            appendTag(n);
+        }
+
+        @Override
+    public void visit(Node.SetProperty n) throws JasperException {
+            appendTag(n);
+        }
+
+        @Override
+    public void visit(Node.ParamAction n) throws JasperException {
+            appendTag(n);
+        }
+
+        @Override
+    public void visit(Node.ParamsAction n) throws JasperException {
+            appendTag(n);
+        }
+
+        @Override
+    public void visit(Node.FallBackAction n) throws JasperException {
+            appendTag(n);
+        }
+
+        @Override
+    public void visit(Node.UseBean n) throws JasperException {
+            appendTag(n);
+        }
+        
+        @Override
+    public void visit(Node.PlugIn n) throws JasperException {
+            appendTag(n);
+        }
+
+        @Override
+        public void visit(Node.NamedAttribute n) throws JasperException {
+            appendTag(n);
+        }
+        
+        @Override
+        public void visit(Node.JspBody n) throws JasperException {
+            appendTag(n);
+        }
+
+        @Override
+    public void visit(Node.CustomTag n) throws JasperException {
+            boolean resetDefaultNSSave = resetDefaultNS;
+            appendTag(n, resetDefaultNS);
+            resetDefaultNS = resetDefaultNSSave;
+        }
+
+        @Override
+    public void visit(Node.UninterpretedTag n) throws JasperException {
+            boolean resetDefaultNSSave = resetDefaultNS;
+            appendTag(n, resetDefaultNS);
+            resetDefaultNS = resetDefaultNSSave;
+        }
+
+        @Override
+    public void visit(Node.JspText n) throws JasperException {
+            appendTag(n);
+        }
+
+        @Override
+    public void visit(Node.DoBodyAction n) throws JasperException {
+            appendTag(n);
+        }
+
+        @Override
+        public void visit(Node.InvokeAction n) throws JasperException {
+            appendTag(n);
+        }
+
+        @Override
+    public void visit(Node.TagDirective n) throws JasperException {
+            appendTagDirective(n);
+        }
+
+        @Override
+    public void visit(Node.AttributeDirective n) throws JasperException {
+            appendTag(n);
+        }
+
+        @Override
+    public void visit(Node.VariableDirective n) throws JasperException {
+            appendTag(n);
+        }
+        
+        @Override
+    public void visit(Node.TemplateText n) throws JasperException {
+            /*
+             * If the template text came from a JSP page written in JSP syntax,
+             * create a jsp:text element for it (JSP 5.3.2).
+             */
+            appendText(n.getText(), !n.getRoot().isXmlSyntax());
+        }
+
+        /*
+         * Appends the given tag, including its body, to the XML view.
+         */
+        private void appendTag(Node n) throws JasperException {
+            appendTag(n, false);
+        }
+
+        /*
+         * Appends the given tag, including its body, to the XML view,
+         * and optionally reset default namespace to "", if none specified.
+         */
+        private void appendTag(Node n, boolean addDefaultNS)
+                throws JasperException {
+
+            Node.Nodes body = n.getBody();
+            String text = n.getText();
+
+            buf.append("<").append(n.getQName());
+            buf.append("\n");
+
+            printAttributes(n, addDefaultNS);
+            buf.append("  ").append(jspIdPrefix).append(":id").append("=\"");
+            buf.append(jspId++).append("\"\n");
+
+            if (ROOT_ACTION.equals(n.getLocalName()) || body != null
+                        || text != null) {
+                buf.append(">\n");
+                if (ROOT_ACTION.equals(n.getLocalName())) {
+                    if (compiler.getCompilationContext().isTagFile()) {
+                        appendTagDirective();
+                    } else {
+                        appendPageDirective();
+                    }
+                }
+                if (body != null) {
+                    body.visit(this);
+                } else {
+                    appendText(text, false);
+                }
+                buf.append("</" + n.getQName() + ">\n");
+            } else {
+                buf.append("/>\n");
+            }
+        }
+
+        /*
+         * Appends the page directive with the given attributes to the XML
+         * view.
+         *
+         * Since the import attribute of the page directive is the only page
+         * attribute that is allowed to appear multiple times within the same
+         * document, and since XML allows only single-value attributes,
+         * the values of multiple import attributes must be combined into one,
+         * separated by comma.
+         *
+         * If the given page directive contains just 'contentType' and/or
+         * 'pageEncoding' attributes, we ignore it, as we've already appended
+         * a page directive containing just these two attributes.
+         */
+        private void appendPageDirective(Node.PageDirective n) {
+            boolean append = false;
+            Attributes attrs = n.getAttributes();
+            int len = (attrs == null) ? 0 : attrs.getLength();
+            for (int i=0; i<len; i++) {
+                String attrName = attrs.getQName(i);
+                if (!"pageEncoding".equals(attrName)
+                        && !"contentType".equals(attrName)) {
+                    append = true;
+                    break;
+                }
+            }
+            if (!append) {
+                return;
+            }
+
+            buf.append("<").append(n.getQName());
+            buf.append("\n");
+
+            // append jsp:id
+            buf.append("  ").append(jspIdPrefix).append(":id").append("=\"");
+            buf.append(jspId++).append("\"\n");
+
+            // append remaining attributes
+            for (int i=0; i<len; i++) {
+                String attrName = attrs.getQName(i);
+                if ("import".equals(attrName) || "contentType".equals(attrName)
+                        || "pageEncoding".equals(attrName)) {
+                    /*
+                     * Page directive's 'import' attribute is considered
+                     * further down, and its 'pageEncoding' and 'contentType'
+                     * attributes are ignored, since we've already appended
+                     * a new page directive containing just these two
+                     * attributes
+                     */
+                    continue;
+                }
+                String value = attrs.getValue(i);
+                buf.append("  ").append(attrName).append("=\"");
+                buf.append(JspUtil.getExprInXml(value)).append("\"\n");
+            }
+            if (n.getImports().size() > 0) {
+                // Concatenate names of imported classes/packages
+                boolean first = true;
+                ListIterator<String> iter = n.getImports().listIterator();
+                while (iter.hasNext()) {
+                    if (first) {
+                        first = false;
+                        buf.append("  import=\"");
+                    } else {
+                        buf.append(",");
+                    }
+                    buf.append(JspUtil.getExprInXml(iter.next()));
+                }
+                buf.append("\"\n");
+            }
+            buf.append("/>\n");
+        }
+
+        /*
+         * Appends a page directive with 'pageEncoding' and 'contentType'
+         * attributes.
+         *
+         * The value of the 'pageEncoding' attribute is hard-coded
+         * to UTF-8, whereas the value of the 'contentType' attribute, which
+         * is identical to what the container will pass to
+         * ServletResponse.setContentType(), is derived from the pageInfo.
+         */
+        private void appendPageDirective() {
+            buf.append("<").append(JSP_PAGE_DIRECTIVE_ACTION);
+            buf.append("\n");
+
+            // append jsp:id
+            buf.append("  ").append(jspIdPrefix).append(":id").append("=\"");
+            buf.append(jspId++).append("\"\n");
+            buf.append("  ").append("pageEncoding").append("=\"UTF-8\"\n");
+            buf.append("  ").append("contentType").append("=\"");
+            buf.append(compiler.getPageInfo().getContentType()).append("\"\n");
+            buf.append("/>\n");            
+        }
+
+        /*
+         * Appends the tag directive with the given attributes to the XML
+         * view.
+         *
+         * If the given tag directive contains just a 'pageEncoding'
+         * attributes, we ignore it, as we've already appended
+         * a tag directive containing just this attributes.
+         */
+        private void appendTagDirective(Node.TagDirective n)
+                throws JasperException {
+
+            boolean append = false;
+            Attributes attrs = n.getAttributes();
+            int len = (attrs == null) ? 0 : attrs.getLength();
+            for (int i=0; i<len; i++) {
+                String attrName = attrs.getQName(i);
+                if (!"pageEncoding".equals(attrName)) {
+                    append = true;
+                    break;
+                }
+            }
+            if (!append) {
+                return;
+            }
+
+            appendTag(n);
+        }
+
+        /*
+         * Appends a tag directive containing a single 'pageEncoding'
+         * attribute whose value is hard-coded to UTF-8.
+         */
+        private void appendTagDirective() {
+            buf.append("<").append(JSP_TAG_DIRECTIVE_ACTION);
+            buf.append("\n");
+
+            // append jsp:id
+            buf.append("  ").append(jspIdPrefix).append(":id").append("=\"");
+            buf.append(jspId++).append("\"\n");
+            buf.append("  ").append("pageEncoding").append("=\"UTF-8\"\n");
+            buf.append("/>\n");            
+        }
+
+        private void appendText(String text, boolean createJspTextElement) {
+            if (createJspTextElement) {
+                buf.append("<").append(JSP_TEXT_ACTION);
+                buf.append("\n");
+
+                // append jsp:id
+                buf.append("  ").append(jspIdPrefix).append(":id").append("=\"");
+                buf.append(jspId++).append("\"\n");
+                buf.append(">\n");
+
+                appendCDATA(text);
+                buf.append(JSP_TEXT_ACTION_END);
+                buf.append("\n");
+            } else {
+                appendCDATA(text);
+            }
+        }
+        
+        /*
+         * Appends the given text as a CDATA section to the XML view, unless
+         * the text has already been marked as CDATA.
+         */
+        private void appendCDATA(String text) {
+            buf.append(CDATA_START_SECTION);
+            buf.append(escapeCDATA(text));
+            buf.append(CDATA_END_SECTION);
+        }
+
+        /*
+         * Escapes any occurrences of "]]>" (by replacing them with "]]&gt;")
+         * within the given text, so it can be included in a CDATA section.
+         */
+        private String escapeCDATA(String text) {
+            if( text==null ) return "";
+            int len = text.length();
+            CharArrayWriter result = new CharArrayWriter(len);
+            for (int i=0; i<len; i++) {
+                if (((i+2) < len)
+                        && (text.charAt(i) == ']')
+                        && (text.charAt(i+1) == ']')
+                        && (text.charAt(i+2) == '>')) {
+                    // match found
+                    result.write(']');
+                    result.write(']');
+                    result.write('&');
+                    result.write('g');
+                    result.write('t');
+                    result.write(';');
+                    i += 2;
+                } else {
+                    result.write(text.charAt(i));
+                }
+            }
+            return result.toString();
+        }
+
+        /*
+         * Appends the attributes of the given Node to the XML view.
+         */
+        private void printAttributes(Node n, boolean addDefaultNS) {
+
+            /*
+             * Append "xmlns" attributes that represent tag libraries
+             */
+            Attributes attrs = n.getTaglibAttributes();
+            int len = (attrs == null) ? 0 : attrs.getLength();
+            for (int i=0; i<len; i++) {
+                String name = attrs.getQName(i);
+                String value = attrs.getValue(i);
+                buf.append("  ").append(name).append("=\"").append(value).append("\"\n");
+            }
+
+            /*
+             * Append "xmlns" attributes that do not represent tag libraries
+             */
+            attrs = n.getNonTaglibXmlnsAttributes();
+            len = (attrs == null) ? 0 : attrs.getLength();
+            boolean defaultNSSeen = false;
+            for (int i=0; i<len; i++) {
+                String name = attrs.getQName(i);
+                String value = attrs.getValue(i);
+                buf.append("  ").append(name).append("=\"").append(value).append("\"\n");
+                defaultNSSeen |= "xmlns".equals(name);
+            }
+            if (addDefaultNS && !defaultNSSeen) {
+                buf.append("  xmlns=\"\"\n");
+            }
+            resetDefaultNS = false;
+
+            /*
+             * Append all other attributes
+             */
+            attrs = n.getAttributes();
+            len = (attrs == null) ? 0 : attrs.getLength();
+            for (int i=0; i<len; i++) {
+                String name = attrs.getQName(i);
+                String value = attrs.getValue(i);
+                buf.append("  ").append(name).append("=\"");
+                buf.append(JspUtil.getExprInXml(value)).append("\"\n");
+            }
+        }
+
+        /*
+         * Appends XML prolog with encoding declaration.
+         */
+        private void appendXmlProlog() {
+            buf.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
+        }
+    }
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/PageInfo.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/PageInfo.java
new file mode 100644
index 0000000..f4274ba
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/PageInfo.java
@@ -0,0 +1,737 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.compiler;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.el.ExpressionFactory;
+import javax.servlet.jsp.tagext.TagLibraryInfo;
+
+import org.apache.jasper.Constants;
+import org.apache.jasper.JasperException;
+
+/**
+ * A repository for various info about the translation unit under compilation.
+ *
+ * @author Kin-man Chung
+ */
+
+class PageInfo {
+
+    private Vector<String> imports;
+    private Vector<String> dependants;
+
+    private BeanRepository beanRepository;
+    private Set<String> varInfoNames;
+    private HashMap<String,TagLibraryInfo> taglibsMap;
+    private HashMap<String, String> jspPrefixMapper;
+    private HashMap<String, LinkedList<String>> xmlPrefixMapper;
+    private HashMap<String, Mark> nonCustomTagPrefixMap;
+    private String jspFile;
+    private String defaultLanguage = "java";
+    private String language;
+    private String defaultExtends = Constants.JSP_SERVLET_BASE;
+    private String xtends;
+    private String contentType = null;
+    private String session;
+    private boolean isSession = true;
+    private String bufferValue;
+    private int buffer = 8*1024;    // XXX confirm
+    private String autoFlush;
+    private boolean isAutoFlush = true;
+    private String isThreadSafeValue;
+    private boolean isThreadSafe = true;
+    private String isErrorPageValue;
+    private boolean isErrorPage = false;
+    private String errorPage = null;
+    private String info;
+
+    private boolean scriptless = false;
+    private boolean scriptingInvalid = false;
+    
+    private String isELIgnoredValue;
+    private boolean isELIgnored = false;
+    
+    // JSP 2.1
+    private String deferredSyntaxAllowedAsLiteralValue;
+    private boolean deferredSyntaxAllowedAsLiteral = false;
+    private ExpressionFactory expressionFactory =
+        ExpressionFactory.newInstance();
+    private String trimDirectiveWhitespacesValue;
+    private boolean trimDirectiveWhitespaces = false;
+    
+    private String omitXmlDecl = null;
+    private String doctypeName = null;
+    private String doctypePublic = null;
+    private String doctypeSystem = null;
+
+    private boolean isJspPrefixHijacked;
+
+    // Set of all element and attribute prefixes used in this translation unit
+    private HashSet<String> prefixes;
+
+    private boolean hasJspRoot = false;
+    private Vector<String> includePrelude;
+    private Vector<String> includeCoda;
+    private Vector<String> pluginDcls;      // Id's for tagplugin declarations
+
+    // JSP 2.2
+    private boolean errorOnUndeclaredNamepsace = false;
+
+    PageInfo(BeanRepository beanRepository, String jspFile) {
+
+        this.jspFile = jspFile;
+        this.beanRepository = beanRepository;
+        this.varInfoNames = new HashSet<String>();
+        this.taglibsMap = new HashMap<String, TagLibraryInfo>();
+        this.jspPrefixMapper = new HashMap<String, String>();
+        this.xmlPrefixMapper = new HashMap<String, LinkedList<String>>();
+        this.nonCustomTagPrefixMap = new HashMap<String, Mark>();
+        this.imports = new Vector<String>();
+        this.dependants = new Vector<String>();
+        this.includePrelude = new Vector<String>();
+        this.includeCoda = new Vector<String>();
+        this.pluginDcls = new Vector<String>();
+        this.prefixes = new HashSet<String>();
+
+        // Enter standard imports
+        imports.addAll(Constants.STANDARD_IMPORTS);
+    }
+
+    /**
+     * Check if the plugin ID has been previously declared.  Make a not
+     * that this Id is now declared.
+     * @return true if Id has been declared.
+     */
+    public boolean isPluginDeclared(String id) {
+        if (pluginDcls.contains(id))
+            return true;
+        pluginDcls.add(id);
+        return false;
+    }
+
+    public void addImports(List<String> imports) {
+        this.imports.addAll(imports);
+    }
+
+    public void addImport(String imp) {
+        this.imports.add(imp);
+    }
+
+    public List<String> getImports() {
+        return imports;
+    }
+
+    public String getJspFile() {
+        return jspFile;
+    }
+
+    public void addDependant(String d) {
+        if (!dependants.contains(d) && !jspFile.equals(d))
+                dependants.add(d);
+    }
+
+    public List<String> getDependants() {
+        return dependants;
+    }
+
+    public BeanRepository getBeanRepository() {
+        return beanRepository;
+    }
+
+    public void setScriptless(boolean s) {
+        scriptless = s;
+    }
+
+    public boolean isScriptless() {
+        return scriptless;
+    }
+
+    public void setScriptingInvalid(boolean s) {
+        scriptingInvalid = s;
+    }
+
+    public boolean isScriptingInvalid() {
+        return scriptingInvalid;
+    }
+
+    public List<String> getIncludePrelude() {
+        return includePrelude;
+    }
+
+    public void setIncludePrelude(Vector<String> prelude) {
+        includePrelude = prelude;
+    }
+
+    public List<String> getIncludeCoda() {
+        return includeCoda;
+    }
+
+    public void setIncludeCoda(Vector<String> coda) {
+        includeCoda = coda;
+    }
+
+    public void setHasJspRoot(boolean s) {
+        hasJspRoot = s;
+    }
+
+    public boolean hasJspRoot() {
+        return hasJspRoot;
+    }
+
+    public String getOmitXmlDecl() {
+        return omitXmlDecl;
+    }
+
+    public void setOmitXmlDecl(String omit) {
+        omitXmlDecl = omit;
+    }
+
+    public String getDoctypeName() {
+        return doctypeName;
+    }
+
+    public void setDoctypeName(String doctypeName) {
+        this.doctypeName = doctypeName;
+    }
+
+    public String getDoctypeSystem() {
+        return doctypeSystem;
+    }
+
+    public void setDoctypeSystem(String doctypeSystem) {
+        this.doctypeSystem = doctypeSystem;
+    }
+
+    public String getDoctypePublic() {
+        return doctypePublic;
+    }
+
+    public void setDoctypePublic(String doctypePublic) {
+        this.doctypePublic = doctypePublic;
+    }
+
+    /* Tag library and XML namespace management methods */
+
+    public void setIsJspPrefixHijacked(boolean isHijacked) {
+        isJspPrefixHijacked = isHijacked;
+    }
+
+    public boolean isJspPrefixHijacked() {
+        return isJspPrefixHijacked;
+    }
+
+    /*
+     * Adds the given prefix to the set of prefixes of this translation unit.
+     *
+     * @param prefix The prefix to add
+     */
+    public void addPrefix(String prefix) {
+        prefixes.add(prefix);
+    }
+
+    /*
+     * Checks to see if this translation unit contains the given prefix.
+     *
+     * @param prefix The prefix to check
+     *
+     * @return true if this translation unit contains the given prefix, false
+     * otherwise
+     */
+    public boolean containsPrefix(String prefix) {
+        return prefixes.contains(prefix);
+    }
+
+    /*
+     * Maps the given URI to the given tag library.
+     *
+     * @param uri The URI to map
+     * @param info The tag library to be associated with the given URI
+     */
+    public void addTaglib(String uri, TagLibraryInfo info) {
+        taglibsMap.put(uri, info);
+    }
+
+    /*
+     * Gets the tag library corresponding to the given URI.
+     *
+     * @return Tag library corresponding to the given URI
+     */
+    public TagLibraryInfo getTaglib(String uri) {
+        return taglibsMap.get(uri);
+    }
+
+    /*
+     * Gets the collection of tag libraries that are associated with a URI
+     *
+     * @return Collection of tag libraries that are associated with a URI
+     */
+    public Collection<TagLibraryInfo> getTaglibs() {
+        return taglibsMap.values();
+    }
+
+    /*
+     * Checks to see if the given URI is mapped to a tag library.
+     *
+     * @param uri The URI to map
+     *
+     * @return true if the given URI is mapped to a tag library, false
+     * otherwise
+     */
+    public boolean hasTaglib(String uri) {
+        return taglibsMap.containsKey(uri);
+    }
+
+    /*
+     * Maps the given prefix to the given URI.
+     *
+     * @param prefix The prefix to map
+     * @param uri The URI to be associated with the given prefix
+     */
+    public void addPrefixMapping(String prefix, String uri) {
+        jspPrefixMapper.put(prefix, uri);
+    }
+
+    /*
+     * Pushes the given URI onto the stack of URIs to which the given prefix
+     * is mapped.
+     *
+     * @param prefix The prefix whose stack of URIs is to be pushed
+     * @param uri The URI to be pushed onto the stack
+     */
+    public void pushPrefixMapping(String prefix, String uri) {
+        LinkedList<String> stack = xmlPrefixMapper.get(prefix);
+        if (stack == null) {
+            stack = new LinkedList<String>();
+            xmlPrefixMapper.put(prefix, stack);
+        }
+        stack.addFirst(uri);
+    }
+
+    /*
+     * Removes the URI at the top of the stack of URIs to which the given
+     * prefix is mapped.
+     *
+     * @param prefix The prefix whose stack of URIs is to be popped
+     */
+    public void popPrefixMapping(String prefix) {
+        LinkedList<String> stack = xmlPrefixMapper.get(prefix);
+        if (stack == null || stack.size() == 0) {
+            // XXX throw new Exception("XXX");
+        }
+        stack.removeFirst();
+    }
+
+    /*
+     * Returns the URI to which the given prefix maps.
+     *
+     * @param prefix The prefix whose URI is sought
+     *
+     * @return The URI to which the given prefix maps
+     */
+    public String getURI(String prefix) {
+
+        String uri = null;
+
+        LinkedList<String> stack = xmlPrefixMapper.get(prefix);
+        if (stack == null || stack.size() == 0) {
+            uri = jspPrefixMapper.get(prefix);
+        } else {
+            uri = stack.getFirst();
+        }
+
+        return uri;
+    }
+
+
+    /* Page/Tag directive attributes */
+
+    /*
+     * language
+     */
+    public void setLanguage(String value, Node n, ErrorDispatcher err,
+                boolean pagedir)
+        throws JasperException {
+
+        if (!"java".equalsIgnoreCase(value)) {
+            if (pagedir)
+                err.jspError(n, "jsp.error.page.language.nonjava");
+            else
+                err.jspError(n, "jsp.error.tag.language.nonjava");
+        }
+
+        language = value;
+    }
+
+    public String getLanguage(boolean useDefault) {
+        return (language == null && useDefault ? defaultLanguage : language);
+    }
+
+    public String getLanguage() {
+        return getLanguage(true);
+    }
+
+
+    /*
+     * extends
+     */
+    public void setExtends(String value, Node.PageDirective n) {
+
+        xtends = value;
+
+        /*
+         * If page superclass is top level class (i.e. not in a package)
+         * explicitly import it. If this is not done, the compiler will assume
+         * the extended class is in the same pkg as the generated servlet.
+         */
+        if (value.indexOf('.') < 0)
+            n.addImport(value);
+    }
+
+    /**
+     * Gets the value of the 'extends' page directive attribute.
+     *
+     * @param useDefault TRUE if the default
+     * (org.apache.jasper.runtime.HttpJspBase) should be returned if this
+     * attribute has not been set, FALSE otherwise
+     *
+     * @return The value of the 'extends' page directive attribute, or the
+     * default (org.apache.jasper.runtime.HttpJspBase) if this attribute has
+     * not been set and useDefault is TRUE
+     */
+    public String getExtends(boolean useDefault) {
+        return (xtends == null && useDefault ? defaultExtends : xtends);
+    }
+
+    /**
+     * Gets the value of the 'extends' page directive attribute.
+     *
+     * @return The value of the 'extends' page directive attribute, or the
+     * default (org.apache.jasper.runtime.HttpJspBase) if this attribute has
+     * not been set
+     */
+    public String getExtends() {
+        return getExtends(true);
+    }
+
+
+    /*
+     * contentType
+     */
+    public void setContentType(String value) {
+        contentType = value;
+    }
+
+    public String getContentType() {
+        return contentType;
+    }
+
+
+    /*
+     * buffer
+     */
+    public void setBufferValue(String value, Node n, ErrorDispatcher err)
+        throws JasperException {
+
+        if ("none".equalsIgnoreCase(value))
+            buffer = 0;
+        else {
+            if (value == null || !value.endsWith("kb")) {
+                if (n == null) {
+                    err.jspError("jsp.error.page.invalid.buffer");
+                } else {
+                    err.jspError(n, "jsp.error.page.invalid.buffer");
+                }
+            }
+            try {
+                Integer k = new Integer(value.substring(0, value.length()-2));
+                buffer = k.intValue() * 1024;
+            } catch (NumberFormatException e) {
+                if (n == null) {
+                    err.jspError("jsp.error.page.invalid.buffer");
+                } else {
+                    err.jspError(n, "jsp.error.page.invalid.buffer");
+                }
+            }
+        }
+
+        bufferValue = value;
+    }
+
+    public String getBufferValue() {
+        return bufferValue;
+    }
+
+    public int getBuffer() {
+        return buffer;
+    }
+
+
+    /*
+     * session
+     */
+    public void setSession(String value, Node n, ErrorDispatcher err)
+        throws JasperException {
+
+        if ("true".equalsIgnoreCase(value))
+            isSession = true;
+        else if ("false".equalsIgnoreCase(value))
+            isSession = false;
+        else
+            err.jspError(n, "jsp.error.page.invalid.session");
+
+        session = value;
+    }
+
+    public String getSession() {
+        return session;
+    }
+
+    public boolean isSession() {
+        return isSession;
+    }
+
+
+    /*
+     * autoFlush
+     */
+    public void setAutoFlush(String value, Node n, ErrorDispatcher err)
+        throws JasperException {
+
+        if ("true".equalsIgnoreCase(value))
+            isAutoFlush = true;
+        else if ("false".equalsIgnoreCase(value))
+            isAutoFlush = false;
+        else
+            err.jspError(n, "jsp.error.autoFlush.invalid");
+
+        autoFlush = value;
+    }
+
+    public String getAutoFlush() {
+        return autoFlush;
+    }
+
+    public boolean isAutoFlush() {
+        return isAutoFlush;
+    }
+
+
+    /*
+     * isThreadSafe
+     */
+    public void setIsThreadSafe(String value, Node n, ErrorDispatcher err)
+        throws JasperException {
+
+        if ("true".equalsIgnoreCase(value))
+            isThreadSafe = true;
+        else if ("false".equalsIgnoreCase(value))
+            isThreadSafe = false;
+        else
+            err.jspError(n, "jsp.error.page.invalid.isthreadsafe");
+
+        isThreadSafeValue = value;
+    }
+
+    public String getIsThreadSafe() {
+        return isThreadSafeValue;
+    }
+
+    public boolean isThreadSafe() {
+        return isThreadSafe;
+    }
+
+
+    /*
+     * info
+     */
+    public void setInfo(String value) {
+        info = value;
+    }
+
+    public String getInfo() {
+        return info;
+    }
+
+
+    /*
+     * errorPage
+     */
+    public void setErrorPage(String value) {
+        errorPage = value;
+    }
+
+    public String getErrorPage() {
+        return errorPage;
+    }
+
+
+    /*
+     * isErrorPage
+     */
+    public void setIsErrorPage(String value, Node n, ErrorDispatcher err)
+        throws JasperException {
+
+        if ("true".equalsIgnoreCase(value))
+            isErrorPage = true;
+        else if ("false".equalsIgnoreCase(value))
+            isErrorPage = false;
+        else
+            err.jspError(n, "jsp.error.page.invalid.iserrorpage");
+
+        isErrorPageValue = value;
+    }
+
+    public String getIsErrorPage() {
+        return isErrorPageValue;
+    }
+
+    public boolean isErrorPage() {
+        return isErrorPage;
+    }
+
+
+    /*
+     * isELIgnored
+     */
+    public void setIsELIgnored(String value, Node n, ErrorDispatcher err,
+                   boolean pagedir)
+        throws JasperException {
+
+        if ("true".equalsIgnoreCase(value))
+            isELIgnored = true;
+        else if ("false".equalsIgnoreCase(value))
+            isELIgnored = false;
+        else {
+            if (pagedir)
+                err.jspError(n, "jsp.error.page.invalid.iselignored");
+            else
+                err.jspError(n, "jsp.error.tag.invalid.iselignored");
+        }
+
+        isELIgnoredValue = value;
+    }
+    
+    /*
+     * deferredSyntaxAllowedAsLiteral
+     */
+    public void setDeferredSyntaxAllowedAsLiteral(String value, Node n, ErrorDispatcher err,
+                   boolean pagedir)
+        throws JasperException {
+
+        if ("true".equalsIgnoreCase(value))
+            deferredSyntaxAllowedAsLiteral = true;
+        else if ("false".equalsIgnoreCase(value))
+            deferredSyntaxAllowedAsLiteral = false;
+        else {
+            if (pagedir)
+                err.jspError(n, "jsp.error.page.invalid.deferredsyntaxallowedasliteral");
+            else
+                err.jspError(n, "jsp.error.tag.invalid.deferredsyntaxallowedasliteral");
+        }
+
+        deferredSyntaxAllowedAsLiteralValue = value;
+    }
+    
+    /*
+     * trimDirectiveWhitespaces
+     */
+    public void setTrimDirectiveWhitespaces(String value, Node n, ErrorDispatcher err,
+                   boolean pagedir)
+        throws JasperException {
+
+        if ("true".equalsIgnoreCase(value))
+            trimDirectiveWhitespaces = true;
+        else if ("false".equalsIgnoreCase(value))
+            trimDirectiveWhitespaces = false;
+        else {
+            if (pagedir)
+                err.jspError(n, "jsp.error.page.invalid.trimdirectivewhitespaces");
+            else
+                err.jspError(n, "jsp.error.tag.invalid.trimdirectivewhitespaces");
+        }
+
+        trimDirectiveWhitespacesValue = value;
+    }
+
+    public void setELIgnored(boolean s) {
+        isELIgnored = s;
+    }
+
+    public String getIsELIgnored() {
+        return isELIgnoredValue;
+    }
+
+    public boolean isELIgnored() {
+        return isELIgnored;
+    }
+
+    public void putNonCustomTagPrefix(String prefix, Mark where) {
+        nonCustomTagPrefixMap.put(prefix, where);
+    }
+
+    public Mark getNonCustomTagPrefix(String prefix) {
+        return nonCustomTagPrefixMap.get(prefix);
+    }
+    
+    public String getDeferredSyntaxAllowedAsLiteral() {
+        return deferredSyntaxAllowedAsLiteralValue;
+    }
+
+    public boolean isDeferredSyntaxAllowedAsLiteral() {
+        return deferredSyntaxAllowedAsLiteral;
+    }
+
+    public void setDeferredSyntaxAllowedAsLiteral(boolean isELDeferred) {
+        this.deferredSyntaxAllowedAsLiteral = isELDeferred;
+    }
+
+    public ExpressionFactory getExpressionFactory() {
+        return expressionFactory;
+    }
+
+    public String getTrimDirectiveWhitespaces() {
+        return trimDirectiveWhitespacesValue;
+    }
+
+    public boolean isTrimDirectiveWhitespaces() {
+        return trimDirectiveWhitespaces;
+    }
+
+    public void setTrimDirectiveWhitespaces(boolean trimDirectiveWhitespaces) {
+        this.trimDirectiveWhitespaces = trimDirectiveWhitespaces;
+    }
+
+    public Set<String> getVarInfoNames() {
+        return varInfoNames;
+    }
+    
+    public boolean isErrorOnUndeclaredNamespace() {
+        return errorOnUndeclaredNamepsace;
+    }
+    
+    public void setErrorOnUndeclaredNamespace(
+            boolean errorOnUndeclaredNamespace) {
+        this.errorOnUndeclaredNamepsace = errorOnUndeclaredNamespace; 
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Parser.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Parser.java
new file mode 100644
index 0000000..d7be14c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Parser.java
@@ -0,0 +1,1793 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.compiler;
+
+import java.io.CharArrayWriter;
+import java.io.FileNotFoundException;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.servlet.jsp.tagext.TagAttributeInfo;
+import javax.servlet.jsp.tagext.TagFileInfo;
+import javax.servlet.jsp.tagext.TagInfo;
+import javax.servlet.jsp.tagext.TagLibraryInfo;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.apache.jasper.util.UniqueAttributesImpl;
+import org.xml.sax.Attributes;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * This class implements a parser for a JSP page (non-xml view). JSP page
+ * grammar is included here for reference. The token '#' that appears in the
+ * production indicates the current input token location in the production.
+ * 
+ * @author Kin-man Chung
+ * @author Shawn Bayern
+ * @author Mark Roth
+ */
+
+class Parser implements TagConstants {
+
+    private ParserController parserController;
+
+    private JspCompilationContext ctxt;
+
+    private JspReader reader;
+
+    private Mark start;
+
+    private ErrorDispatcher err;
+
+    private int scriptlessCount;
+
+    private boolean isTagFile;
+
+    private boolean directivesOnly;
+
+    private JarResource jarResource;
+
+    private PageInfo pageInfo;
+
+    // Virtual body content types, to make parsing a little easier.
+    // These are not accessible from outside the parser.
+    private static final String JAVAX_BODY_CONTENT_PARAM =
+        "JAVAX_BODY_CONTENT_PARAM";
+
+    private static final String JAVAX_BODY_CONTENT_PLUGIN =
+        "JAVAX_BODY_CONTENT_PLUGIN";
+
+    private static final String JAVAX_BODY_CONTENT_TEMPLATE_TEXT =
+        "JAVAX_BODY_CONTENT_TEMPLATE_TEXT";
+
+    /* System property that controls if the strict white space rules are
+     * applied.
+     */ 
+    private static final boolean STRICT_WHITESPACE = Boolean.valueOf(
+            System.getProperty(
+                    "org.apache.jasper.compiler.Parser.STRICT_WHITESPACE",
+                    "true")).booleanValue();
+    /**
+     * The constructor
+     */
+    private Parser(ParserController pc, JspReader reader, boolean isTagFile,
+            boolean directivesOnly, JarResource jarResource) {
+        this.parserController = pc;
+        this.ctxt = pc.getJspCompilationContext();
+        this.pageInfo = pc.getCompiler().getPageInfo();
+        this.err = pc.getCompiler().getErrorDispatcher();
+        this.reader = reader;
+        this.scriptlessCount = 0;
+        this.isTagFile = isTagFile;
+        this.directivesOnly = directivesOnly;
+        this.jarResource = jarResource;
+        start = reader.mark();
+    }
+
+    /**
+     * The main entry for Parser
+     * 
+     * @param pc
+     *            The ParseController, use for getting other objects in compiler
+     *            and for parsing included pages
+     * @param reader
+     *            To read the page
+     * @param parent
+     *            The parent node to this page, null for top level page
+     * @return list of nodes representing the parsed page
+     */
+    public static Node.Nodes parse(ParserController pc, JspReader reader,
+            Node parent, boolean isTagFile, boolean directivesOnly,
+            JarResource jarResource, String pageEnc, String jspConfigPageEnc,
+            boolean isDefaultPageEncoding, boolean isBomPresent)
+            throws JasperException {
+
+        Parser parser = new Parser(pc, reader, isTagFile, directivesOnly,
+                jarResource);
+
+        Node.Root root = new Node.Root(reader.mark(), parent, false);
+        root.setPageEncoding(pageEnc);
+        root.setJspConfigPageEncoding(jspConfigPageEnc);
+        root.setIsDefaultPageEncoding(isDefaultPageEncoding);
+        root.setIsBomPresent(isBomPresent);
+
+        // For the Top level page, add include-prelude and include-coda
+        PageInfo pageInfo = pc.getCompiler().getPageInfo();
+        if (parent == null && !isTagFile) {
+            parser.addInclude(root, pageInfo.getIncludePrelude());
+        }
+        if (directivesOnly) {
+            parser.parseFileDirectives(root);
+        } else {
+            while (reader.hasMoreInput()) {
+                parser.parseElements(root);
+            }
+        }
+        if (parent == null && !isTagFile) {
+            parser.addInclude(root, pageInfo.getIncludeCoda());
+        }
+
+        Node.Nodes page = new Node.Nodes(root);
+        return page;
+    }
+
+    /**
+     * Attributes ::= (S Attribute)* S?
+     */
+    Attributes parseAttributes() throws JasperException {
+        return parseAttributes(false);
+    }
+    Attributes parseAttributes(boolean pageDirective) throws JasperException {
+        UniqueAttributesImpl attrs = new UniqueAttributesImpl(pageDirective);
+
+        reader.skipSpaces();
+        int ws = 1;
+
+        try {
+            while (parseAttribute(attrs)) {
+                if (ws == 0 && STRICT_WHITESPACE) {
+                    err.jspError(reader.mark(),
+                            "jsp.error.attribute.nowhitespace");
+                }
+                ws = reader.skipSpaces();
+            }
+        } catch (IllegalArgumentException iae) {
+            // Duplicate attribute
+            err.jspError(reader.mark(), "jsp.error.attribute.duplicate");
+        }
+
+        return attrs;
+    }
+
+    /**
+     * Parse Attributes for a reader, provided for external use
+     */
+    public static Attributes parseAttributes(ParserController pc,
+            JspReader reader) throws JasperException {
+        Parser tmpParser = new Parser(pc, reader, false, false, null);
+        return tmpParser.parseAttributes(true);
+    }
+
+    /**
+     * Attribute ::= Name S? Eq S? ( '"<%=' RTAttributeValueDouble | '"'
+     * AttributeValueDouble | "'<%=" RTAttributeValueSingle | "'"
+     * AttributeValueSingle } Note: JSP and XML spec does not allow while spaces
+     * around Eq. It is added to be backward compatible with Tomcat, and with
+     * other xml parsers.
+     */
+    private boolean parseAttribute(AttributesImpl attrs)
+            throws JasperException {
+
+        // Get the qualified name
+        String qName = parseName();
+        if (qName == null)
+            return false;
+
+        // Determine prefix and local name components
+        String localName = qName;
+        String uri = "";
+        int index = qName.indexOf(':');
+        if (index != -1) {
+            String prefix = qName.substring(0, index);
+            uri = pageInfo.getURI(prefix);
+            if (uri == null) {
+                err.jspError(reader.mark(),
+                        "jsp.error.attribute.invalidPrefix", prefix);
+            }
+            localName = qName.substring(index + 1);
+        }
+
+        reader.skipSpaces();
+        if (!reader.matches("="))
+            err.jspError(reader.mark(), "jsp.error.attribute.noequal");
+
+        reader.skipSpaces();
+        char quote = (char) reader.nextChar();
+        if (quote != '\'' && quote != '"')
+            err.jspError(reader.mark(), "jsp.error.attribute.noquote");
+
+        String watchString = "";
+        if (reader.matches("<%="))
+            watchString = "%>";
+        watchString = watchString + quote;
+
+        String attrValue = parseAttributeValue(watchString);
+        attrs.addAttribute(uri, localName, qName, "CDATA", attrValue);
+        return true;
+    }
+
+    /**
+     * Name ::= (Letter | '_' | ':') (Letter | Digit | '.' | '_' | '-' | ':')*
+     */
+    private String parseName() throws JasperException {
+        char ch = (char) reader.peekChar();
+        if (Character.isLetter(ch) || ch == '_' || ch == ':') {
+            StringBuilder buf = new StringBuilder();
+            buf.append(ch);
+            reader.nextChar();
+            ch = (char) reader.peekChar();
+            while (Character.isLetter(ch) || Character.isDigit(ch) || ch == '.'
+                    || ch == '_' || ch == '-' || ch == ':') {
+                buf.append(ch);
+                reader.nextChar();
+                ch = (char) reader.peekChar();
+            }
+            return buf.toString();
+        }
+        return null;
+    }
+
+    /**
+     * AttributeValueDouble ::= (QuotedChar - '"')* ('"' | <TRANSLATION_ERROR>)
+     * RTAttributeValueDouble ::= ((QuotedChar - '"')* - ((QuotedChar-'"')'%>"')
+     * ('%>"' | TRANSLATION_ERROR)
+     */
+    private String parseAttributeValue(String watch) throws JasperException {
+        Mark start = reader.mark();
+        Mark stop = reader.skipUntilIgnoreEsc(watch);
+        if (stop == null) {
+            err.jspError(start, "jsp.error.attribute.unterminated", watch);
+        }
+
+        String ret = null;
+        try {
+            char quote = watch.charAt(watch.length() - 1);
+            
+            // If watch is longer than 1 character this is a scripting
+            // expression and EL is always ignored
+            boolean isElIgnored =
+                pageInfo.isELIgnored() || watch.length() > 1;
+            
+            ret = AttributeParser.getUnquoted(reader.getText(start, stop),
+                    quote, isElIgnored,
+                    pageInfo.isDeferredSyntaxAllowedAsLiteral());
+        } catch (IllegalArgumentException iae) {
+            err.jspError(start, iae.getMessage());
+        }
+        if (watch.length() == 1) // quote
+            return ret;
+
+        // Put back delimiter '<%=' and '%>', since they are needed if the
+        // attribute does not allow RTexpression.
+        return "<%=" + ret + "%>";
+    }
+
+    private String parseScriptText(String tx) {
+        CharArrayWriter cw = new CharArrayWriter();
+        int size = tx.length();
+        int i = 0;
+        while (i < size) {
+            char ch = tx.charAt(i);
+            if (i + 2 < size && ch == '%' && tx.charAt(i + 1) == '\\'
+                    && tx.charAt(i + 2) == '>') {
+                cw.write('%');
+                cw.write('>');
+                i += 3;
+            } else {
+                cw.write(ch);
+                ++i;
+            }
+        }
+        cw.close();
+        return cw.toString();
+    }
+
+    /*
+     * Invokes parserController to parse the included page
+     */
+    private void processIncludeDirective(String file, Node parent)
+            throws JasperException {
+        if (file == null) {
+            return;
+        }
+
+        try {
+            parserController.parse(file, parent, jarResource);
+        } catch (FileNotFoundException ex) {
+            err.jspError(start, "jsp.error.file.not.found", file);
+        } catch (Exception ex) {
+            err.jspError(start, ex.getMessage());
+        }
+    }
+
+    /*
+     * Parses a page directive with the following syntax: PageDirective ::= ( S
+     * Attribute)*
+     */
+    private void parsePageDirective(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes(true);
+        Node.PageDirective n = new Node.PageDirective(attrs, start, parent);
+
+        /*
+         * A page directive may contain multiple 'import' attributes, each of
+         * which consists of a comma-separated list of package names. Store each
+         * list with the node, where it is parsed.
+         */
+        for (int i = 0; i < attrs.getLength(); i++) {
+            if ("import".equals(attrs.getQName(i))) {
+                n.addImport(attrs.getValue(i));
+            }
+        }
+    }
+
+    /*
+     * Parses an include directive with the following syntax: IncludeDirective
+     * ::= ( S Attribute)*
+     */
+    private void parseIncludeDirective(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+
+        // Included file expanded here
+        Node includeNode = new Node.IncludeDirective(attrs, start, parent);
+        processIncludeDirective(attrs.getValue("file"), includeNode);
+    }
+
+    /**
+     * Add a list of files. This is used for implementing include-prelude and
+     * include-coda of jsp-config element in web.xml
+     */
+    private void addInclude(Node parent, List<String> files) throws JasperException {
+        if (files != null) {
+            Iterator<String> iter = files.iterator();
+            while (iter.hasNext()) {
+                String file = iter.next();
+                AttributesImpl attrs = new AttributesImpl();
+                attrs.addAttribute("", "file", "file", "CDATA", file);
+
+                // Create a dummy Include directive node
+                Node includeNode = new Node.IncludeDirective(attrs, reader
+                        .mark(), parent);
+                processIncludeDirective(file, includeNode);
+            }
+        }
+    }
+
+    /*
+     * Parses a taglib directive with the following syntax: Directive ::= ( S
+     * Attribute)*
+     */
+    private void parseTaglibDirective(Node parent) throws JasperException {
+
+        Attributes attrs = parseAttributes();
+        String uri = attrs.getValue("uri");
+        String prefix = attrs.getValue("prefix");
+        if (prefix != null) {
+            Mark prevMark = pageInfo.getNonCustomTagPrefix(prefix);
+            if (prevMark != null) {
+                err.jspError(reader.mark(), "jsp.error.prefix.use_before_dcl",
+                        prefix, prevMark.getFile(), ""
+                                + prevMark.getLineNumber());
+            }
+            if (uri != null) {
+                String uriPrev = pageInfo.getURI(prefix);
+                if (uriPrev != null && !uriPrev.equals(uri)) {
+                    err.jspError(reader.mark(), "jsp.error.prefix.refined",
+                            prefix, uri, uriPrev);
+                }
+                if (pageInfo.getTaglib(uri) == null) {
+                    TagLibraryInfoImpl impl = null;
+                    if (ctxt.getOptions().isCaching()) {
+                        impl = (TagLibraryInfoImpl) ctxt.getOptions()
+                                .getCache().get(uri);
+                    }
+                    if (impl == null) {
+                        TldLocation location = ctxt.getTldLocation(uri);
+                        impl = new TagLibraryInfoImpl(ctxt, parserController,
+                                pageInfo, prefix, uri, location, err,
+                                reader.mark());
+                        if (ctxt.getOptions().isCaching()) {
+                            ctxt.getOptions().getCache().put(uri, impl);
+                        }
+                    } else {
+                        // Current compilation context needs location of cached
+                        // tag files
+                        for (TagFileInfo info : impl.getTagFiles()) {
+                            ctxt.setTagFileJarResource(info.getPath(),
+                                    ctxt.getTagFileJarResource());
+                        }
+                    }
+                    pageInfo.addTaglib(uri, impl);
+                }
+                pageInfo.addPrefixMapping(prefix, uri);
+            } else {
+                String tagdir = attrs.getValue("tagdir");
+                if (tagdir != null) {
+                    String urnTagdir = URN_JSPTAGDIR + tagdir;
+                    if (pageInfo.getTaglib(urnTagdir) == null) {
+                        pageInfo.addTaglib(urnTagdir,
+                                new ImplicitTagLibraryInfo(ctxt,
+                                        parserController, pageInfo, prefix,
+                                        tagdir, err));
+                    }
+                    pageInfo.addPrefixMapping(prefix, urnTagdir);
+                }
+            }
+        }
+
+        new Node.TaglibDirective(attrs, start, parent);
+    }
+
+    /*
+     * Parses a directive with the following syntax: Directive ::= S? ( 'page'
+     * PageDirective | 'include' IncludeDirective | 'taglib' TagLibDirective) S?
+     * '%>'
+     * 
+     * TagDirective ::= S? ('tag' PageDirective | 'include' IncludeDirective |
+     * 'taglib' TagLibDirective) | 'attribute AttributeDirective | 'variable
+     * VariableDirective S? '%>'
+     */
+    private void parseDirective(Node parent) throws JasperException {
+        reader.skipSpaces();
+
+        String directive = null;
+        if (reader.matches("page")) {
+            directive = "&lt;%@ page";
+            if (isTagFile) {
+                err.jspError(reader.mark(), "jsp.error.directive.istagfile",
+                        directive);
+            }
+            parsePageDirective(parent);
+        } else if (reader.matches("include")) {
+            directive = "&lt;%@ include";
+            parseIncludeDirective(parent);
+        } else if (reader.matches("taglib")) {
+            if (directivesOnly) {
+                // No need to get the tagLibInfo objects. This alos suppresses
+                // parsing of any tag files used in this tag file.
+                return;
+            }
+            directive = "&lt;%@ taglib";
+            parseTaglibDirective(parent);
+        } else if (reader.matches("tag")) {
+            directive = "&lt;%@ tag";
+            if (!isTagFile) {
+                err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
+                        directive);
+            }
+            parseTagDirective(parent);
+        } else if (reader.matches("attribute")) {
+            directive = "&lt;%@ attribute";
+            if (!isTagFile) {
+                err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
+                        directive);
+            }
+            parseAttributeDirective(parent);
+        } else if (reader.matches("variable")) {
+            directive = "&lt;%@ variable";
+            if (!isTagFile) {
+                err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
+                        directive);
+            }
+            parseVariableDirective(parent);
+        } else {
+            err.jspError(reader.mark(), "jsp.error.invalid.directive");
+        }
+
+        reader.skipSpaces();
+        if (!reader.matches("%>")) {
+            err.jspError(start, "jsp.error.unterminated", directive);
+        }
+    }
+
+    /*
+     * Parses a directive with the following syntax:
+     * 
+     * XMLJSPDirectiveBody ::= S? ( ( 'page' PageDirectiveAttrList S? ( '/>' | (
+     * '>' S? ETag ) ) | ( 'include' IncludeDirectiveAttrList S? ( '/>' | ( '>'
+     * S? ETag ) ) | <TRANSLATION_ERROR>
+     * 
+     * XMLTagDefDirectiveBody ::= ( ( 'tag' TagDirectiveAttrList S? ( '/>' | (
+     * '>' S? ETag ) ) | ( 'include' IncludeDirectiveAttrList S? ( '/>' | ( '>'
+     * S? ETag ) ) | ( 'attribute' AttributeDirectiveAttrList S? ( '/>' | ( '>'
+     * S? ETag ) ) | ( 'variable' VariableDirectiveAttrList S? ( '/>' | ( '>' S?
+     * ETag ) ) ) | <TRANSLATION_ERROR>
+     */
+    private void parseXMLDirective(Node parent) throws JasperException {
+        reader.skipSpaces();
+
+        String eTag = null;
+        if (reader.matches("page")) {
+            eTag = "jsp:directive.page";
+            if (isTagFile) {
+                err.jspError(reader.mark(), "jsp.error.directive.istagfile",
+                        "&lt;" + eTag);
+            }
+            parsePageDirective(parent);
+        } else if (reader.matches("include")) {
+            eTag = "jsp:directive.include";
+            parseIncludeDirective(parent);
+        } else if (reader.matches("tag")) {
+            eTag = "jsp:directive.tag";
+            if (!isTagFile) {
+                err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
+                        "&lt;" + eTag);
+            }
+            parseTagDirective(parent);
+        } else if (reader.matches("attribute")) {
+            eTag = "jsp:directive.attribute";
+            if (!isTagFile) {
+                err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
+                        "&lt;" + eTag);
+            }
+            parseAttributeDirective(parent);
+        } else if (reader.matches("variable")) {
+            eTag = "jsp:directive.variable";
+            if (!isTagFile) {
+                err.jspError(reader.mark(), "jsp.error.directive.isnottagfile",
+                        "&lt;" + eTag);
+            }
+            parseVariableDirective(parent);
+        } else {
+            err.jspError(reader.mark(), "jsp.error.invalid.directive");
+        }
+
+        reader.skipSpaces();
+        if (reader.matches(">")) {
+            reader.skipSpaces();
+            if (!reader.matchesETag(eTag)) {
+                err.jspError(start, "jsp.error.unterminated", "&lt;" + eTag);
+            }
+        } else if (!reader.matches("/>")) {
+            err.jspError(start, "jsp.error.unterminated", "&lt;" + eTag);
+        }
+    }
+
+    /*
+     * Parses a tag directive with the following syntax: PageDirective ::= ( S
+     * Attribute)*
+     */
+    private void parseTagDirective(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes(true);
+        Node.TagDirective n = new Node.TagDirective(attrs, start, parent);
+
+        /*
+         * A page directive may contain multiple 'import' attributes, each of
+         * which consists of a comma-separated list of package names. Store each
+         * list with the node, where it is parsed.
+         */
+        for (int i = 0; i < attrs.getLength(); i++) {
+            if ("import".equals(attrs.getQName(i))) {
+                n.addImport(attrs.getValue(i));
+            }
+        }
+    }
+
+    /*
+     * Parses a attribute directive with the following syntax:
+     * AttributeDirective ::= ( S Attribute)*
+     */
+    private void parseAttributeDirective(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+        new Node.AttributeDirective(attrs, start, parent);
+    }
+
+    /*
+     * Parses a variable directive with the following syntax:
+     * PageDirective ::= ( S Attribute)*
+     */
+    private void parseVariableDirective(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+        new Node.VariableDirective(attrs, start, parent);
+    }
+
+    /*
+     * JSPCommentBody ::= (Char* - (Char* '--%>')) '--%>'
+     */
+    private void parseComment(Node parent) throws JasperException {
+        start = reader.mark();
+        Mark stop = reader.skipUntil("--%>");
+        if (stop == null) {
+            err.jspError(start, "jsp.error.unterminated", "&lt;%--");
+        }
+
+        new Node.Comment(reader.getText(start, stop), start, parent);
+    }
+
+    /*
+     * DeclarationBody ::= (Char* - (char* '%>')) '%>'
+     */
+    private void parseDeclaration(Node parent) throws JasperException {
+        start = reader.mark();
+        Mark stop = reader.skipUntil("%>");
+        if (stop == null) {
+            err.jspError(start, "jsp.error.unterminated", "&lt;%!");
+        }
+
+        new Node.Declaration(parseScriptText(reader.getText(start, stop)),
+                start, parent);
+    }
+
+    /*
+     * XMLDeclarationBody ::= ( S? '/>' ) | ( S? '>' (Char* - (char* '<'))
+     * CDSect?)* ETag | <TRANSLATION_ERROR> CDSect ::= CDStart CData CDEnd
+     * CDStart ::= '<![CDATA[' CData ::= (Char* - (Char* ']]>' Char*)) CDEnd
+     * ::= ']]>'
+     */
+    private void parseXMLDeclaration(Node parent) throws JasperException {
+        reader.skipSpaces();
+        if (!reader.matches("/>")) {
+            if (!reader.matches(">")) {
+                err.jspError(start, "jsp.error.unterminated",
+                        "&lt;jsp:declaration&gt;");
+            }
+            Mark stop;
+            String text;
+            while (true) {
+                start = reader.mark();
+                stop = reader.skipUntil("<");
+                if (stop == null) {
+                    err.jspError(start, "jsp.error.unterminated",
+                            "&lt;jsp:declaration&gt;");
+                }
+                text = parseScriptText(reader.getText(start, stop));
+                new Node.Declaration(text, start, parent);
+                if (reader.matches("![CDATA[")) {
+                    start = reader.mark();
+                    stop = reader.skipUntil("]]>");
+                    if (stop == null) {
+                        err.jspError(start, "jsp.error.unterminated", "CDATA");
+                    }
+                    text = parseScriptText(reader.getText(start, stop));
+                    new Node.Declaration(text, start, parent);
+                } else {
+                    break;
+                }
+            }
+
+            if (!reader.matchesETagWithoutLessThan("jsp:declaration")) {
+                err.jspError(start, "jsp.error.unterminated",
+                        "&lt;jsp:declaration&gt;");
+            }
+        }
+    }
+
+    /*
+     * ExpressionBody ::= (Char* - (char* '%>')) '%>'
+     */
+    private void parseExpression(Node parent) throws JasperException {
+        start = reader.mark();
+        Mark stop = reader.skipUntil("%>");
+        if (stop == null) {
+            err.jspError(start, "jsp.error.unterminated", "&lt;%=");
+        }
+
+        new Node.Expression(parseScriptText(reader.getText(start, stop)),
+                start, parent);
+    }
+
+    /*
+     * XMLExpressionBody ::= ( S? '/>' ) | ( S? '>' (Char* - (char* '<'))
+     * CDSect?)* ETag ) | <TRANSLATION_ERROR>
+     */
+    private void parseXMLExpression(Node parent) throws JasperException {
+        reader.skipSpaces();
+        if (!reader.matches("/>")) {
+            if (!reader.matches(">")) {
+                err.jspError(start, "jsp.error.unterminated",
+                        "&lt;jsp:expression&gt;");
+            }
+            Mark stop;
+            String text;
+            while (true) {
+                start = reader.mark();
+                stop = reader.skipUntil("<");
+                if (stop == null) {
+                    err.jspError(start, "jsp.error.unterminated",
+                            "&lt;jsp:expression&gt;");
+                }
+                text = parseScriptText(reader.getText(start, stop));
+                new Node.Expression(text, start, parent);
+                if (reader.matches("![CDATA[")) {
+                    start = reader.mark();
+                    stop = reader.skipUntil("]]>");
+                    if (stop == null) {
+                        err.jspError(start, "jsp.error.unterminated", "CDATA");
+                    }
+                    text = parseScriptText(reader.getText(start, stop));
+                    new Node.Expression(text, start, parent);
+                } else {
+                    break;
+                }
+            }
+            if (!reader.matchesETagWithoutLessThan("jsp:expression")) {
+                err.jspError(start, "jsp.error.unterminated",
+                        "&lt;jsp:expression&gt;");
+            }
+        }
+    }
+
+    /*
+     * ELExpressionBody (following "${" to first unquoted "}") // XXX add formal
+     * production and confirm implementation against it, // once it's decided
+     */
+    private void parseELExpression(Node parent, char type)
+            throws JasperException {
+        start = reader.mark();
+        Mark last = null;
+        boolean singleQuoted = false, doubleQuoted = false;
+        int currentChar;
+        do {
+            // XXX could move this logic to JspReader
+            last = reader.mark(); // XXX somewhat wasteful
+            currentChar = reader.nextChar();
+            if (currentChar == '\\' && (singleQuoted || doubleQuoted)) {
+                // skip character following '\' within quotes
+                reader.nextChar();
+                currentChar = reader.nextChar();
+            }
+            if (currentChar == -1)
+                err.jspError(start, "jsp.error.unterminated", type + "{");
+            if (currentChar == '"' && !singleQuoted)
+                doubleQuoted = !doubleQuoted;
+            if (currentChar == '\'' && !doubleQuoted)
+                singleQuoted = !singleQuoted;
+        } while (currentChar != '}' || (singleQuoted || doubleQuoted));
+
+        new Node.ELExpression(type, reader.getText(start, last), start, parent);
+    }
+
+    /*
+     * ScriptletBody ::= (Char* - (char* '%>')) '%>'
+     */
+    private void parseScriptlet(Node parent) throws JasperException {
+        start = reader.mark();
+        Mark stop = reader.skipUntil("%>");
+        if (stop == null) {
+            err.jspError(start, "jsp.error.unterminated", "&lt;%");
+        }
+
+        new Node.Scriptlet(parseScriptText(reader.getText(start, stop)), start,
+                parent);
+    }
+
+    /*
+     * XMLScriptletBody ::= ( S? '/>' ) | ( S? '>' (Char* - (char* '<'))
+     * CDSect?)* ETag ) | <TRANSLATION_ERROR>
+     */
+    private void parseXMLScriptlet(Node parent) throws JasperException {
+        reader.skipSpaces();
+        if (!reader.matches("/>")) {
+            if (!reader.matches(">")) {
+                err.jspError(start, "jsp.error.unterminated",
+                        "&lt;jsp:scriptlet&gt;");
+            }
+            Mark stop;
+            String text;
+            while (true) {
+                start = reader.mark();
+                stop = reader.skipUntil("<");
+                if (stop == null) {
+                    err.jspError(start, "jsp.error.unterminated",
+                            "&lt;jsp:scriptlet&gt;");
+                }
+                text = parseScriptText(reader.getText(start, stop));
+                new Node.Scriptlet(text, start, parent);
+                if (reader.matches("![CDATA[")) {
+                    start = reader.mark();
+                    stop = reader.skipUntil("]]>");
+                    if (stop == null) {
+                        err.jspError(start, "jsp.error.unterminated", "CDATA");
+                    }
+                    text = parseScriptText(reader.getText(start, stop));
+                    new Node.Scriptlet(text, start, parent);
+                } else {
+                    break;
+                }
+            }
+
+            if (!reader.matchesETagWithoutLessThan("jsp:scriptlet")) {
+                err.jspError(start, "jsp.error.unterminated",
+                        "&lt;jsp:scriptlet&gt;");
+            }
+        }
+    }
+
+    /**
+     * Param ::= '<jsp:param' S Attributes S? EmptyBody S?
+     */
+    private void parseParam(Node parent) throws JasperException {
+        if (!reader.matches("<jsp:param")) {
+            err.jspError(reader.mark(), "jsp.error.paramexpected");
+        }
+        Attributes attrs = parseAttributes();
+        reader.skipSpaces();
+
+        Node paramActionNode = new Node.ParamAction(attrs, start, parent);
+
+        parseEmptyBody(paramActionNode, "jsp:param");
+
+        reader.skipSpaces();
+    }
+
+    /*
+     * For Include: StdActionContent ::= Attributes ParamBody
+     * 
+     * ParamBody ::= EmptyBody | ( '>' S? ( '<jsp:attribute' NamedAttributes )? '<jsp:body'
+     * (JspBodyParam | <TRANSLATION_ERROR> ) S? ETag ) | ( '>' S? Param* ETag )
+     * 
+     * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '<jsp:attribute'
+     * NamedAttributes ETag )
+     * 
+     * JspBodyParam ::= S? '>' Param* '</jsp:body>'
+     */
+    private void parseInclude(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+        reader.skipSpaces();
+
+        Node includeNode = new Node.IncludeAction(attrs, start, parent);
+
+        parseOptionalBody(includeNode, "jsp:include", JAVAX_BODY_CONTENT_PARAM);
+    }
+
+    /*
+     * For Forward: StdActionContent ::= Attributes ParamBody
+     */
+    private void parseForward(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+        reader.skipSpaces();
+
+        Node forwardNode = new Node.ForwardAction(attrs, start, parent);
+
+        parseOptionalBody(forwardNode, "jsp:forward", JAVAX_BODY_CONTENT_PARAM);
+    }
+
+    private void parseInvoke(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+        reader.skipSpaces();
+
+        Node invokeNode = new Node.InvokeAction(attrs, start, parent);
+
+        parseEmptyBody(invokeNode, "jsp:invoke");
+    }
+
+    private void parseDoBody(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+        reader.skipSpaces();
+
+        Node doBodyNode = new Node.DoBodyAction(attrs, start, parent);
+
+        parseEmptyBody(doBodyNode, "jsp:doBody");
+    }
+
+    private void parseElement(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+        reader.skipSpaces();
+
+        Node elementNode = new Node.JspElement(attrs, start, parent);
+
+        parseOptionalBody(elementNode, "jsp:element", TagInfo.BODY_CONTENT_JSP);
+    }
+
+    /*
+     * For GetProperty: StdActionContent ::= Attributes EmptyBody
+     */
+    private void parseGetProperty(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+        reader.skipSpaces();
+
+        Node getPropertyNode = new Node.GetProperty(attrs, start, parent);
+
+        parseOptionalBody(getPropertyNode, "jsp:getProperty",
+                TagInfo.BODY_CONTENT_EMPTY);
+    }
+
+    /*
+     * For SetProperty: StdActionContent ::= Attributes EmptyBody
+     */
+    private void parseSetProperty(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+        reader.skipSpaces();
+
+        Node setPropertyNode = new Node.SetProperty(attrs, start, parent);
+
+        parseOptionalBody(setPropertyNode, "jsp:setProperty",
+                TagInfo.BODY_CONTENT_EMPTY);
+    }
+
+    /*
+     * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '<jsp:attribute'
+     * NamedAttributes ETag )
+     */
+    private void parseEmptyBody(Node parent, String tag) throws JasperException {
+        if (reader.matches("/>")) {
+            // Done
+        } else if (reader.matches(">")) {
+            if (reader.matchesETag(tag)) {
+                // Done
+            } else if (reader.matchesOptionalSpacesFollowedBy("<jsp:attribute")) {
+                // Parse the one or more named attribute nodes
+                parseNamedAttributes(parent);
+                if (!reader.matchesETag(tag)) {
+                    // Body not allowed
+                    err.jspError(reader.mark(),
+                            "jsp.error.jspbody.emptybody.only", "&lt;" + tag);
+                }
+            } else {
+                err.jspError(reader.mark(), "jsp.error.jspbody.emptybody.only",
+                        "&lt;" + tag);
+            }
+        } else {
+            err.jspError(reader.mark(), "jsp.error.unterminated", "&lt;" + tag);
+        }
+    }
+
+    /*
+     * For UseBean: StdActionContent ::= Attributes OptionalBody
+     */
+    private void parseUseBean(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+        reader.skipSpaces();
+
+        Node useBeanNode = new Node.UseBean(attrs, start, parent);
+
+        parseOptionalBody(useBeanNode, "jsp:useBean", TagInfo.BODY_CONTENT_JSP);
+    }
+
+    /*
+     * Parses OptionalBody, but also reused to parse bodies for plugin and param
+     * since the syntax is identical (the only thing that differs substantially
+     * is how to process the body, and thus we accept the body type as a
+     * parameter).
+     * 
+     * OptionalBody ::= EmptyBody | ActionBody
+     * 
+     * ScriptlessOptionalBody ::= EmptyBody | ScriptlessActionBody
+     * 
+     * TagDependentOptionalBody ::= EmptyBody | TagDependentActionBody
+     * 
+     * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '<jsp:attribute'
+     * NamedAttributes ETag )
+     * 
+     * ActionBody ::= JspAttributeAndBody | ( '>' Body ETag )
+     * 
+     * ScriptlessActionBody ::= JspAttributeAndBody | ( '>' ScriptlessBody ETag )
+     * 
+     * TagDependentActionBody ::= JspAttributeAndBody | ( '>' TagDependentBody
+     * ETag )
+     * 
+     */
+    private void parseOptionalBody(Node parent, String tag, String bodyType)
+            throws JasperException {
+        if (reader.matches("/>")) {
+            // EmptyBody
+            return;
+        }
+
+        if (!reader.matches(">")) {
+            err.jspError(reader.mark(), "jsp.error.unterminated", "&lt;" + tag);
+        }
+
+        if (reader.matchesETag(tag)) {
+            // EmptyBody
+            return;
+        }
+
+        if (!parseJspAttributeAndBody(parent, tag, bodyType)) {
+            // Must be ( '>' # Body ETag )
+            parseBody(parent, tag, bodyType);
+        }
+    }
+
+    /**
+     * Attempts to parse 'JspAttributeAndBody' production. Returns true if it
+     * matched, or false if not. Assumes EmptyBody is okay as well.
+     * 
+     * JspAttributeAndBody ::= ( '>' # S? ( '<jsp:attribute' NamedAttributes )? '<jsp:body' (
+     * JspBodyBody | <TRANSLATION_ERROR> ) S? ETag )
+     */
+    private boolean parseJspAttributeAndBody(Node parent, String tag,
+            String bodyType) throws JasperException {
+        boolean result = false;
+
+        if (reader.matchesOptionalSpacesFollowedBy("<jsp:attribute")) {
+            // May be an EmptyBody, depending on whether
+            // There's a "<jsp:body" before the ETag
+
+            // First, parse <jsp:attribute> elements:
+            parseNamedAttributes(parent);
+
+            result = true;
+        }
+
+        if (reader.matchesOptionalSpacesFollowedBy("<jsp:body")) {
+            // ActionBody
+            parseJspBody(parent, bodyType);
+            reader.skipSpaces();
+            if (!reader.matchesETag(tag)) {
+                err.jspError(reader.mark(), "jsp.error.unterminated", "&lt;"
+                        + tag);
+            }
+
+            result = true;
+        } else if (result && !reader.matchesETag(tag)) {
+            // If we have <jsp:attribute> but something other than
+            // <jsp:body> or the end tag, translation error.
+            err.jspError(reader.mark(), "jsp.error.jspbody.required", "&lt;"
+                    + tag);
+        }
+
+        return result;
+    }
+
+    /*
+     * Params ::= `>' S? ( ( `<jsp:body>' ( ( S? Param+ S? `</jsp:body>' ) |
+     * <TRANSLATION_ERROR> ) ) | Param+ ) '</jsp:params>'
+     */
+    private void parseJspParams(Node parent) throws JasperException {
+        Node jspParamsNode = new Node.ParamsAction(start, parent);
+        parseOptionalBody(jspParamsNode, "jsp:params", JAVAX_BODY_CONTENT_PARAM);
+    }
+
+    /*
+     * Fallback ::= '/>' | ( `>' S? `<jsp:body>' ( ( S? ( Char* - ( Char* `</jsp:body>' ) ) `</jsp:body>'
+     * S? ) | <TRANSLATION_ERROR> ) `</jsp:fallback>' ) | ( '>' ( Char* - (
+     * Char* '</jsp:fallback>' ) ) '</jsp:fallback>' )
+     */
+    private void parseFallBack(Node parent) throws JasperException {
+        Node fallBackNode = new Node.FallBackAction(start, parent);
+        parseOptionalBody(fallBackNode, "jsp:fallback",
+                JAVAX_BODY_CONTENT_TEMPLATE_TEXT);
+    }
+
+    /*
+     * For Plugin: StdActionContent ::= Attributes PluginBody
+     * 
+     * PluginBody ::= EmptyBody | ( '>' S? ( '<jsp:attribute' NamedAttributes )? '<jsp:body' (
+     * JspBodyPluginTags | <TRANSLATION_ERROR> ) S? ETag ) | ( '>' S? PluginTags
+     * ETag )
+     * 
+     * EmptyBody ::= '/>' | ( '>' ETag ) | ( '>' S? '<jsp:attribute'
+     * NamedAttributes ETag )
+     * 
+     */
+    private void parsePlugin(Node parent) throws JasperException {
+        Attributes attrs = parseAttributes();
+        reader.skipSpaces();
+
+        Node pluginNode = new Node.PlugIn(attrs, start, parent);
+
+        parseOptionalBody(pluginNode, "jsp:plugin", JAVAX_BODY_CONTENT_PLUGIN);
+    }
+
+    /*
+     * PluginTags ::= ( '<jsp:params' Params S? )? ( '<jsp:fallback' Fallback?
+     * S? )?
+     */
+    private void parsePluginTags(Node parent) throws JasperException {
+        reader.skipSpaces();
+
+        if (reader.matches("<jsp:params")) {
+            parseJspParams(parent);
+            reader.skipSpaces();
+        }
+
+        if (reader.matches("<jsp:fallback")) {
+            parseFallBack(parent);
+            reader.skipSpaces();
+        }
+    }
+
+    /*
+     * StandardAction ::= 'include' StdActionContent | 'forward'
+     * StdActionContent | 'invoke' StdActionContent | 'doBody' StdActionContent |
+     * 'getProperty' StdActionContent | 'setProperty' StdActionContent |
+     * 'useBean' StdActionContent | 'plugin' StdActionContent | 'element'
+     * StdActionContent
+     */
+    private void parseStandardAction(Node parent) throws JasperException {
+        Mark start = reader.mark();
+
+        if (reader.matches(INCLUDE_ACTION)) {
+            parseInclude(parent);
+        } else if (reader.matches(FORWARD_ACTION)) {
+            parseForward(parent);
+        } else if (reader.matches(INVOKE_ACTION)) {
+            if (!isTagFile) {
+                err.jspError(reader.mark(), "jsp.error.action.isnottagfile",
+                        "&lt;jsp:invoke");
+            }
+            parseInvoke(parent);
+        } else if (reader.matches(DOBODY_ACTION)) {
+            if (!isTagFile) {
+                err.jspError(reader.mark(), "jsp.error.action.isnottagfile",
+                        "&lt;jsp:doBody");
+            }
+            parseDoBody(parent);
+        } else if (reader.matches(GET_PROPERTY_ACTION)) {
+            parseGetProperty(parent);
+        } else if (reader.matches(SET_PROPERTY_ACTION)) {
+            parseSetProperty(parent);
+        } else if (reader.matches(USE_BEAN_ACTION)) {
+            parseUseBean(parent);
+        } else if (reader.matches(PLUGIN_ACTION)) {
+            parsePlugin(parent);
+        } else if (reader.matches(ELEMENT_ACTION)) {
+            parseElement(parent);
+        } else if (reader.matches(ATTRIBUTE_ACTION)) {
+            err.jspError(start, "jsp.error.namedAttribute.invalidUse");
+        } else if (reader.matches(BODY_ACTION)) {
+            err.jspError(start, "jsp.error.jspbody.invalidUse");
+        } else if (reader.matches(FALLBACK_ACTION)) {
+            err.jspError(start, "jsp.error.fallback.invalidUse");
+        } else if (reader.matches(PARAMS_ACTION)) {
+            err.jspError(start, "jsp.error.params.invalidUse");
+        } else if (reader.matches(PARAM_ACTION)) {
+            err.jspError(start, "jsp.error.param.invalidUse");
+        } else if (reader.matches(OUTPUT_ACTION)) {
+            err.jspError(start, "jsp.error.jspoutput.invalidUse");
+        } else {
+            err.jspError(start, "jsp.error.badStandardAction");
+        }
+    }
+
+    /*
+     * # '<' CustomAction CustomActionBody
+     * 
+     * CustomAction ::= TagPrefix ':' CustomActionName
+     * 
+     * TagPrefix ::= Name
+     * 
+     * CustomActionName ::= Name
+     * 
+     * CustomActionBody ::= ( Attributes CustomActionEnd ) | <TRANSLATION_ERROR>
+     * 
+     * Attributes ::= ( S Attribute )* S?
+     * 
+     * CustomActionEnd ::= CustomActionTagDependent | CustomActionJSPContent |
+     * CustomActionScriptlessContent
+     * 
+     * CustomActionTagDependent ::= TagDependentOptionalBody
+     * 
+     * CustomActionJSPContent ::= OptionalBody
+     * 
+     * CustomActionScriptlessContent ::= ScriptlessOptionalBody
+     */
+    private boolean parseCustomTag(Node parent) throws JasperException {
+
+        if (reader.peekChar() != '<') {
+            return false;
+        }
+
+        // Parse 'CustomAction' production (tag prefix and custom action name)
+        reader.nextChar(); // skip '<'
+        String tagName = reader.parseToken(false);
+        int i = tagName.indexOf(':');
+        if (i == -1) {
+            reader.reset(start);
+            return false;
+        }
+
+        String prefix = tagName.substring(0, i);
+        String shortTagName = tagName.substring(i + 1);
+
+        // Check if this is a user-defined tag.
+        String uri = pageInfo.getURI(prefix);
+        if (uri == null) {
+            if (pageInfo.isErrorOnUndeclaredNamespace()) {
+                err.jspError(start, "jsp.error.undeclared_namespace", prefix);
+            } else {
+                reader.reset(start);
+                // Remember the prefix for later error checking
+                pageInfo.putNonCustomTagPrefix(prefix, reader.mark());
+                return false;
+            }
+        }
+
+        TagLibraryInfo tagLibInfo = pageInfo.getTaglib(uri);
+        TagInfo tagInfo = tagLibInfo.getTag(shortTagName);
+        TagFileInfo tagFileInfo = tagLibInfo.getTagFile(shortTagName);
+        if (tagInfo == null && tagFileInfo == null) {
+            err.jspError(start, "jsp.error.bad_tag", shortTagName, prefix);
+        }
+        Class<?> tagHandlerClass = null;
+        if (tagInfo != null) {
+            // Must be a classic tag, load it here.
+            // tag files will be loaded later, in TagFileProcessor
+            String handlerClassName = tagInfo.getTagClassName();
+            try {
+                tagHandlerClass = ctxt.getClassLoader().loadClass(
+                        handlerClassName);
+            } catch (Exception e) {
+                err.jspError(start, "jsp.error.loadclass.taghandler",
+                        handlerClassName, tagName);
+            }
+        }
+
+        // Parse 'CustomActionBody' production:
+        // At this point we are committed - if anything fails, we produce
+        // a translation error.
+
+        // Parse 'Attributes' production:
+        Attributes attrs = parseAttributes();
+        reader.skipSpaces();
+
+        // Parse 'CustomActionEnd' production:
+        if (reader.matches("/>")) {
+            if (tagInfo != null) {
+                new Node.CustomTag(tagName, prefix, shortTagName, uri, attrs,
+                        start, parent, tagInfo, tagHandlerClass);
+            } else {
+                new Node.CustomTag(tagName, prefix, shortTagName, uri, attrs,
+                        start, parent, tagFileInfo);
+            }
+            return true;
+        }
+
+        // Now we parse one of 'CustomActionTagDependent',
+        // 'CustomActionJSPContent', or 'CustomActionScriptlessContent'.
+        // depending on body-content in TLD.
+
+        // Looking for a body, it still can be empty; but if there is a
+        // a tag body, its syntax would be dependent on the type of
+        // body content declared in the TLD.
+        String bc;
+        if (tagInfo != null) {
+            bc = tagInfo.getBodyContent();
+        } else {
+            bc = tagFileInfo.getTagInfo().getBodyContent();
+        }
+
+        Node tagNode = null;
+        if (tagInfo != null) {
+            tagNode = new Node.CustomTag(tagName, prefix, shortTagName, uri,
+                    attrs, start, parent, tagInfo, tagHandlerClass);
+        } else {
+            tagNode = new Node.CustomTag(tagName, prefix, shortTagName, uri,
+                    attrs, start, parent, tagFileInfo);
+        }
+
+        parseOptionalBody(tagNode, tagName, bc);
+
+        return true;
+    }
+
+    /*
+     * Parse for a template text string until '<' or "${" or "#{" is encountered,
+     * recognizing escape sequences "\%", "\$", and "\#".
+     */
+    private void parseTemplateText(Node parent) throws JasperException {
+
+        if (!reader.hasMoreInput())
+            return;
+
+        CharArrayWriter ttext = new CharArrayWriter();
+        // Output the first character
+        int ch = reader.nextChar();
+        if (ch == '\\') {
+            reader.pushChar();
+        } else {
+            ttext.write(ch);
+        }
+
+        while (reader.hasMoreInput()) {
+            ch = reader.nextChar();
+            if (ch == '<') {
+                reader.pushChar();
+                break;
+            } else if ((ch == '$' || ch == '#') && !pageInfo.isELIgnored()) {
+                if (!reader.hasMoreInput()) {
+                    ttext.write(ch);
+                    break;
+                }
+                if (reader.nextChar() == '{') {
+                    reader.pushChar();
+                    reader.pushChar();
+                    break;
+                }
+                ttext.write(ch);
+                reader.pushChar();
+                continue;
+            } else if (ch == '\\') {
+                if (!reader.hasMoreInput()) {
+                    ttext.write('\\');
+                    break;
+                }
+                char next = (char) reader.peekChar();
+                // Looking for \% or \$ or \#
+                if (next == '%' || ((next == '$' || next == '#') &&
+                        !pageInfo.isELIgnored())) {
+                    ch = reader.nextChar();
+                }
+            }
+            ttext.write(ch);
+        }
+        new Node.TemplateText(ttext.toString(), start, parent);
+    }
+
+    /*
+     * XMLTemplateText ::= ( S? '/>' ) | ( S? '>' ( ( Char* - ( Char* ( '<' |
+     * '${' ) ) ) ( '${' ELExpressionBody )? CDSect? )* ETag ) |
+     * <TRANSLATION_ERROR>
+     */
+    private void parseXMLTemplateText(Node parent) throws JasperException {
+        reader.skipSpaces();
+        if (!reader.matches("/>")) {
+            if (!reader.matches(">")) {
+                err.jspError(start, "jsp.error.unterminated",
+                        "&lt;jsp:text&gt;");
+            }
+            CharArrayWriter ttext = new CharArrayWriter();
+            while (reader.hasMoreInput()) {
+                int ch = reader.nextChar();
+                if (ch == '<') {
+                    // Check for <![CDATA[
+                    if (!reader.matches("![CDATA[")) {
+                        break;
+                    }
+                    start = reader.mark();
+                    Mark stop = reader.skipUntil("]]>");
+                    if (stop == null) {
+                        err.jspError(start, "jsp.error.unterminated", "CDATA");
+                    }
+                    String text = reader.getText(start, stop);
+                    ttext.write(text, 0, text.length());
+                } else if (ch == '\\') {
+                    if (!reader.hasMoreInput()) {
+                        ttext.write('\\');
+                        break;
+                    }
+                    ch = reader.nextChar();
+                    if (ch != '$' && ch != '#') {
+                        ttext.write('\\');
+                    }
+                    ttext.write(ch);
+                } else if (ch == '$' || ch == '#') {
+                    if (!reader.hasMoreInput()) {
+                        ttext.write(ch);
+                        break;
+                    }
+                    if (reader.nextChar() != '{') {
+                        ttext.write(ch);
+                        reader.pushChar();
+                        continue;
+                    }
+                    // Create a template text node
+                    new Node.TemplateText(ttext.toString(), start, parent);
+
+                    // Mark and parse the EL expression and create its node:
+                    start = reader.mark();
+                    parseELExpression(parent, (char) ch);
+
+                    start = reader.mark();
+                    ttext = new CharArrayWriter();
+                } else {
+                    ttext.write(ch);
+                }
+            }
+
+            new Node.TemplateText(ttext.toString(), start, parent);
+
+            if (!reader.hasMoreInput()) {
+                err.jspError(start, "jsp.error.unterminated",
+                        "&lt;jsp:text&gt;");
+            } else if (!reader.matchesETagWithoutLessThan("jsp:text")) {
+                err.jspError(start, "jsp.error.jsptext.badcontent");
+            }
+        }
+    }
+
+    /*
+     * AllBody ::= ( '<%--' JSPCommentBody ) | ( '<%@' DirectiveBody ) | ( '<jsp:directive.'
+     * XMLDirectiveBody ) | ( '<%!' DeclarationBody ) | ( '<jsp:declaration'
+     * XMLDeclarationBody ) | ( '<%=' ExpressionBody ) | ( '<jsp:expression'
+     * XMLExpressionBody ) | ( '${' ELExpressionBody ) | ( '<%' ScriptletBody ) | ( '<jsp:scriptlet'
+     * XMLScriptletBody ) | ( '<jsp:text' XMLTemplateText ) | ( '<jsp:'
+     * StandardAction ) | ( '<' CustomAction CustomActionBody ) | TemplateText
+     */
+    private void parseElements(Node parent) throws JasperException {
+        if (scriptlessCount > 0) {
+            // vc: ScriptlessBody
+            // We must follow the ScriptlessBody production if one of
+            // our parents is ScriptlessBody.
+            parseElementsScriptless(parent);
+            return;
+        }
+
+        start = reader.mark();
+        if (reader.matches("<%--")) {
+            parseComment(parent);
+        } else if (reader.matches("<%@")) {
+            parseDirective(parent);
+        } else if (reader.matches("<jsp:directive.")) {
+            parseXMLDirective(parent);
+        } else if (reader.matches("<%!")) {
+            parseDeclaration(parent);
+        } else if (reader.matches("<jsp:declaration")) {
+            parseXMLDeclaration(parent);
+        } else if (reader.matches("<%=")) {
+            parseExpression(parent);
+        } else if (reader.matches("<jsp:expression")) {
+            parseXMLExpression(parent);
+        } else if (reader.matches("<%")) {
+            parseScriptlet(parent);
+        } else if (reader.matches("<jsp:scriptlet")) {
+            parseXMLScriptlet(parent);
+        } else if (reader.matches("<jsp:text")) {
+            parseXMLTemplateText(parent);
+        } else if (!pageInfo.isELIgnored() && reader.matches("${")) {
+            parseELExpression(parent, '$');
+        } else if (!pageInfo.isELIgnored()
+                && !pageInfo.isDeferredSyntaxAllowedAsLiteral()
+                && reader.matches("#{")) {
+            parseELExpression(parent, '#');
+        } else if (reader.matches("<jsp:")) {
+            parseStandardAction(parent);
+        } else if (!parseCustomTag(parent)) {
+            checkUnbalancedEndTag();
+            parseTemplateText(parent);
+        }
+    }
+
+    /*
+     * ScriptlessBody ::= ( '<%--' JSPCommentBody ) | ( '<%@' DirectiveBody ) | ( '<jsp:directive.'
+     * XMLDirectiveBody ) | ( '<%!' <TRANSLATION_ERROR> ) | ( '<jsp:declaration'
+     * <TRANSLATION_ERROR> ) | ( '<%=' <TRANSLATION_ERROR> ) | ( '<jsp:expression'
+     * <TRANSLATION_ERROR> ) | ( '<%' <TRANSLATION_ERROR> ) | ( '<jsp:scriptlet'
+     * <TRANSLATION_ERROR> ) | ( '<jsp:text' XMLTemplateText ) | ( '${'
+     * ELExpressionBody ) | ( '<jsp:' StandardAction ) | ( '<' CustomAction
+     * CustomActionBody ) | TemplateText
+     */
+    private void parseElementsScriptless(Node parent) throws JasperException {
+        // Keep track of how many scriptless nodes we've encountered
+        // so we know whether our child nodes are forced scriptless
+        scriptlessCount++;
+
+        start = reader.mark();
+        if (reader.matches("<%--")) {
+            parseComment(parent);
+        } else if (reader.matches("<%@")) {
+            parseDirective(parent);
+        } else if (reader.matches("<jsp:directive.")) {
+            parseXMLDirective(parent);
+        } else if (reader.matches("<%!")) {
+            err.jspError(reader.mark(), "jsp.error.no.scriptlets");
+        } else if (reader.matches("<jsp:declaration")) {
+            err.jspError(reader.mark(), "jsp.error.no.scriptlets");
+        } else if (reader.matches("<%=")) {
+            err.jspError(reader.mark(), "jsp.error.no.scriptlets");
+        } else if (reader.matches("<jsp:expression")) {
+            err.jspError(reader.mark(), "jsp.error.no.scriptlets");
+        } else if (reader.matches("<%")) {
+            err.jspError(reader.mark(), "jsp.error.no.scriptlets");
+        } else if (reader.matches("<jsp:scriptlet")) {
+            err.jspError(reader.mark(), "jsp.error.no.scriptlets");
+        } else if (reader.matches("<jsp:text")) {
+            parseXMLTemplateText(parent);
+        } else if (!pageInfo.isELIgnored() && reader.matches("${")) {
+            parseELExpression(parent, '$');
+        } else if (!pageInfo.isELIgnored()
+                && !pageInfo.isDeferredSyntaxAllowedAsLiteral()
+                && reader.matches("#{")) {
+            parseELExpression(parent, '#');
+        } else if (reader.matches("<jsp:")) {
+            parseStandardAction(parent);
+        } else if (!parseCustomTag(parent)) {
+            checkUnbalancedEndTag();
+            parseTemplateText(parent);
+        }
+
+        scriptlessCount--;
+    }
+
+    /*
+     * TemplateTextBody ::= ( '<%--' JSPCommentBody ) | ( '<%@' DirectiveBody ) | ( '<jsp:directive.'
+     * XMLDirectiveBody ) | ( '<%!' <TRANSLATION_ERROR> ) | ( '<jsp:declaration'
+     * <TRANSLATION_ERROR> ) | ( '<%=' <TRANSLATION_ERROR> ) | ( '<jsp:expression'
+     * <TRANSLATION_ERROR> ) | ( '<%' <TRANSLATION_ERROR> ) | ( '<jsp:scriptlet'
+     * <TRANSLATION_ERROR> ) | ( '<jsp:text' <TRANSLATION_ERROR> ) | ( '${'
+     * <TRANSLATION_ERROR> ) | ( '<jsp:' <TRANSLATION_ERROR> ) | TemplateText
+     */
+    private void parseElementsTemplateText(Node parent) throws JasperException {
+        start = reader.mark();
+        if (reader.matches("<%--")) {
+            parseComment(parent);
+        } else if (reader.matches("<%@")) {
+            parseDirective(parent);
+        } else if (reader.matches("<jsp:directive.")) {
+            parseXMLDirective(parent);
+        } else if (reader.matches("<%!")) {
+            err.jspError(reader.mark(), "jsp.error.not.in.template",
+                    "Declarations");
+        } else if (reader.matches("<jsp:declaration")) {
+            err.jspError(reader.mark(), "jsp.error.not.in.template",
+                    "Declarations");
+        } else if (reader.matches("<%=")) {
+            err.jspError(reader.mark(), "jsp.error.not.in.template",
+                    "Expressions");
+        } else if (reader.matches("<jsp:expression")) {
+            err.jspError(reader.mark(), "jsp.error.not.in.template",
+                    "Expressions");
+        } else if (reader.matches("<%")) {
+            err.jspError(reader.mark(), "jsp.error.not.in.template",
+                    "Scriptlets");
+        } else if (reader.matches("<jsp:scriptlet")) {
+            err.jspError(reader.mark(), "jsp.error.not.in.template",
+                    "Scriptlets");
+        } else if (reader.matches("<jsp:text")) {
+            err.jspError(reader.mark(), "jsp.error.not.in.template",
+                    "&lt;jsp:text");
+        } else if (!pageInfo.isELIgnored() && reader.matches("${")) {
+            err.jspError(reader.mark(), "jsp.error.not.in.template",
+                    "Expression language");
+        } else if (!pageInfo.isELIgnored()
+                && !pageInfo.isDeferredSyntaxAllowedAsLiteral()
+                && reader.matches("#{")) {
+            err.jspError(reader.mark(), "jsp.error.not.in.template",
+                    "Expression language");
+        } else if (reader.matches("<jsp:")) {
+            err.jspError(reader.mark(), "jsp.error.not.in.template",
+                    "Standard actions");
+        } else if (parseCustomTag(parent)) {
+            err.jspError(reader.mark(), "jsp.error.not.in.template",
+                    "Custom actions");
+        } else {
+            checkUnbalancedEndTag();
+            parseTemplateText(parent);
+        }
+    }
+
+    /*
+     * Flag as error if an unbalanced end tag appears by itself.
+     */
+    private void checkUnbalancedEndTag() throws JasperException {
+
+        if (!reader.matches("</")) {
+            return;
+        }
+
+        // Check for unbalanced standard actions
+        if (reader.matches("jsp:")) {
+            err.jspError(start, "jsp.error.unbalanced.endtag", "jsp:");
+        }
+
+        // Check for unbalanced custom actions
+        String tagName = reader.parseToken(false);
+        int i = tagName.indexOf(':');
+        if (i == -1 || pageInfo.getURI(tagName.substring(0, i)) == null) {
+            reader.reset(start);
+            return;
+        }
+
+        err.jspError(start, "jsp.error.unbalanced.endtag", tagName);
+    }
+
+    /**
+     * TagDependentBody :=
+     */
+    private void parseTagDependentBody(Node parent, String tag)
+            throws JasperException {
+        Mark bodyStart = reader.mark();
+        Mark bodyEnd = reader.skipUntilETag(tag);
+        if (bodyEnd == null) {
+            err.jspError(start, "jsp.error.unterminated", "&lt;" + tag);
+        }
+        new Node.TemplateText(reader.getText(bodyStart, bodyEnd), bodyStart,
+                parent);
+    }
+
+    /*
+     * Parses jsp:body action.
+     */
+    private void parseJspBody(Node parent, String bodyType)
+            throws JasperException {
+        Mark start = reader.mark();
+        Node bodyNode = new Node.JspBody(start, parent);
+
+        reader.skipSpaces();
+        if (!reader.matches("/>")) {
+            if (!reader.matches(">")) {
+                err.jspError(start, "jsp.error.unterminated", "&lt;jsp:body");
+            }
+            parseBody(bodyNode, "jsp:body", bodyType);
+        }
+    }
+
+    /*
+     * Parse the body as JSP content. @param tag The name of the tag whose end
+     * tag would terminate the body @param bodyType One of the TagInfo body
+     * types
+     */
+    private void parseBody(Node parent, String tag, String bodyType)
+            throws JasperException {
+        if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_TAG_DEPENDENT)) {
+            parseTagDependentBody(parent, tag);
+        } else if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_EMPTY)) {
+            if (!reader.matchesETag(tag)) {
+                err.jspError(start, "jasper.error.emptybodycontent.nonempty",
+                        tag);
+            }
+        } else if (bodyType == JAVAX_BODY_CONTENT_PLUGIN) {
+            // (note the == since we won't recognize JAVAX_*
+            // from outside this module).
+            parsePluginTags(parent);
+            if (!reader.matchesETag(tag)) {
+                err.jspError(reader.mark(), "jsp.error.unterminated", "&lt;"
+                        + tag);
+            }
+        } else if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_JSP)
+                || bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_SCRIPTLESS)
+                || (bodyType == JAVAX_BODY_CONTENT_PARAM)
+                || (bodyType == JAVAX_BODY_CONTENT_TEMPLATE_TEXT)) {
+            while (reader.hasMoreInput()) {
+                if (reader.matchesETag(tag)) {
+                    return;
+                }
+
+                // Check for nested jsp:body or jsp:attribute
+                if (tag.equals("jsp:body") || tag.equals("jsp:attribute")) {
+                    if (reader.matches("<jsp:attribute")) {
+                        err.jspError(reader.mark(),
+                                "jsp.error.nested.jspattribute");
+                    } else if (reader.matches("<jsp:body")) {
+                        err.jspError(reader.mark(), "jsp.error.nested.jspbody");
+                    }
+                }
+
+                if (bodyType.equalsIgnoreCase(TagInfo.BODY_CONTENT_JSP)) {
+                    parseElements(parent);
+                } else if (bodyType
+                        .equalsIgnoreCase(TagInfo.BODY_CONTENT_SCRIPTLESS)) {
+                    parseElementsScriptless(parent);
+                } else if (bodyType == JAVAX_BODY_CONTENT_PARAM) {
+                    // (note the == since we won't recognize JAVAX_*
+                    // from outside this module).
+                    reader.skipSpaces();
+                    parseParam(parent);
+                } else if (bodyType == JAVAX_BODY_CONTENT_TEMPLATE_TEXT) {
+                    parseElementsTemplateText(parent);
+                }
+            }
+            err.jspError(start, "jsp.error.unterminated", "&lt;" + tag);
+        } else {
+            err.jspError(start, "jasper.error.bad.bodycontent.type");
+        }
+    }
+
+    /*
+     * Parses named attributes.
+     */
+    private void parseNamedAttributes(Node parent) throws JasperException {
+        do {
+            Mark start = reader.mark();
+            Attributes attrs = parseAttributes();
+            Node.NamedAttribute namedAttributeNode = new Node.NamedAttribute(
+                    attrs, start, parent);
+
+            reader.skipSpaces();
+            if (!reader.matches("/>")) {
+                if (!reader.matches(">")) {
+                    err.jspError(start, "jsp.error.unterminated",
+                            "&lt;jsp:attribute");
+                }
+                if (namedAttributeNode.isTrim()) {
+                    reader.skipSpaces();
+                }
+                parseBody(namedAttributeNode, "jsp:attribute",
+                        getAttributeBodyType(parent, attrs.getValue("name")));
+                if (namedAttributeNode.isTrim()) {
+                    Node.Nodes subElems = namedAttributeNode.getBody();
+                    if (subElems != null) {
+                        Node lastNode = subElems.getNode(subElems.size() - 1);
+                        if (lastNode instanceof Node.TemplateText) {
+                            ((Node.TemplateText) lastNode).rtrim();
+                        }
+                    }
+                }
+            }
+            reader.skipSpaces();
+        } while (reader.matches("<jsp:attribute"));
+    }
+
+    /**
+     * Determine the body type of <jsp:attribute> from the enclosing node
+     */
+    private String getAttributeBodyType(Node n, String name) {
+
+        if (n instanceof Node.CustomTag) {
+            TagInfo tagInfo = ((Node.CustomTag) n).getTagInfo();
+            TagAttributeInfo[] tldAttrs = tagInfo.getAttributes();
+            for (int i = 0; i < tldAttrs.length; i++) {
+                if (name.equals(tldAttrs[i].getName())) {
+                    if (tldAttrs[i].isFragment()) {
+                        return TagInfo.BODY_CONTENT_SCRIPTLESS;
+                    }
+                    if (tldAttrs[i].canBeRequestTime()) {
+                        return TagInfo.BODY_CONTENT_JSP;
+                    }
+                }
+            }
+            if (tagInfo.hasDynamicAttributes()) {
+                return TagInfo.BODY_CONTENT_JSP;
+            }
+        } else if (n instanceof Node.IncludeAction) {
+            if ("page".equals(name)) {
+                return TagInfo.BODY_CONTENT_JSP;
+            }
+        } else if (n instanceof Node.ForwardAction) {
+            if ("page".equals(name)) {
+                return TagInfo.BODY_CONTENT_JSP;
+            }
+        } else if (n instanceof Node.SetProperty) {
+            if ("value".equals(name)) {
+                return TagInfo.BODY_CONTENT_JSP;
+            }
+        } else if (n instanceof Node.UseBean) {
+            if ("beanName".equals(name)) {
+                return TagInfo.BODY_CONTENT_JSP;
+            }
+        } else if (n instanceof Node.PlugIn) {
+            if ("width".equals(name) || "height".equals(name)) {
+                return TagInfo.BODY_CONTENT_JSP;
+            }
+        } else if (n instanceof Node.ParamAction) {
+            if ("value".equals(name)) {
+                return TagInfo.BODY_CONTENT_JSP;
+            }
+        } else if (n instanceof Node.JspElement) {
+            return TagInfo.BODY_CONTENT_JSP;
+        }
+
+        return JAVAX_BODY_CONTENT_TEMPLATE_TEXT;
+    }
+
+    private void parseFileDirectives(Node parent) throws JasperException {
+        reader.setSingleFile(true);
+        reader.skipUntil("<");
+        while (reader.hasMoreInput()) {
+            start = reader.mark();
+            if (reader.matches("%--")) {
+                // Comment
+                reader.skipUntil("--%>");
+            } else if (reader.matches("%@")) {
+                parseDirective(parent);
+            } else if (reader.matches("jsp:directive.")) {
+                parseXMLDirective(parent);
+            } else if (reader.matches("%!")) {
+                // Declaration
+                reader.skipUntil("%>");
+            } else if (reader.matches("%=")) {
+                // Expression
+                reader.skipUntil("%>");
+            } else if (reader.matches("%")) {
+                // Scriptlet
+                reader.skipUntil("%>");
+            }
+            reader.skipUntil("<");
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ParserController.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ParserController.java
new file mode 100644
index 0000000..67f4797
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ParserController.java
@@ -0,0 +1,612 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Stack;
+import java.util.jar.JarFile;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.apache.jasper.util.ExceptionUtils;
+import org.apache.jasper.xmlparser.XMLEncodingDetector;
+import org.xml.sax.Attributes;
+
+/**
+ * Controller for the parsing of a JSP page.
+ * <p>
+ * The same ParserController instance is used for a JSP page and any JSP
+ * segments included by it (via an include directive), where each segment may
+ * be provided in standard or XML syntax. This class selects and invokes the
+ * appropriate parser for the JSP page and its included segments.
+ *
+ * @author Pierre Delisle
+ * @author Jan Luehe
+ */
+class ParserController implements TagConstants {
+
+    private static final String CHARSET = "charset=";
+
+    private JspCompilationContext ctxt;
+    private Compiler compiler;
+    private ErrorDispatcher err;
+
+    /*
+     * Indicates the syntax (XML or standard) of the file being processed
+     */
+    private boolean isXml;
+
+    /*
+     * A stack to keep track of the 'current base directory'
+     * for include directives that refer to relative paths.
+     */
+    private Stack<String> baseDirStack = new Stack<String>();
+
+    private boolean isEncodingSpecifiedInProlog;
+    private boolean isBomPresent;
+    private int skip;
+
+    private String sourceEnc;
+
+    private boolean isDefaultPageEncoding;
+    private boolean isTagFile;
+    private boolean directiveOnly;
+
+    /*
+     * Constructor
+     */
+    public ParserController(JspCompilationContext ctxt, Compiler compiler) {
+        this.ctxt = ctxt; 
+        this.compiler = compiler;
+        this.err = compiler.getErrorDispatcher();
+    }
+
+    public JspCompilationContext getJspCompilationContext () {
+        return ctxt;
+    }
+
+    public Compiler getCompiler () {
+        return compiler;
+    }
+
+    /**
+     * Parses a JSP page or tag file. This is invoked by the compiler.
+     *
+     * @param inFileName The path to the JSP page or tag file to be parsed.
+     */
+    public Node.Nodes parse(String inFileName)
+    throws FileNotFoundException, JasperException, IOException {
+        // If we're parsing a packaged tag file or a resource included by it
+        // (using an include directive), ctxt.getTagFileJar() returns the 
+        // JAR file from which to read the tag file or included resource,
+        // respectively.
+        isTagFile = ctxt.isTagFile();
+        directiveOnly = false;
+        return doParse(inFileName, null, ctxt.getTagFileJarResource());
+    }
+
+    /**
+     * Parses the directives of a JSP page or tag file. This is invoked by the
+     * compiler.
+     *
+     * @param inFileName The path to the JSP page or tag file to be parsed.
+     */
+    public Node.Nodes parseDirectives(String inFileName)
+    throws FileNotFoundException, JasperException, IOException {
+        // If we're parsing a packaged tag file or a resource included by it
+        // (using an include directive), ctxt.getTagFileJar() returns the 
+        // JAR file from which to read the tag file or included resource,
+        // respectively.
+        isTagFile = ctxt.isTagFile();
+        directiveOnly = true;
+        return doParse(inFileName, null, ctxt.getTagFileJarResource());
+    }
+
+
+    /**
+     * Processes an include directive with the given path.
+     *
+     * @param inFileName The path to the resource to be included.
+     * @param parent The parent node of the include directive.
+     * @param jarResource The JAR file from which to read the included resource,
+     * or null of the included resource is to be read from the filesystem
+     */
+    public Node.Nodes parse(String inFileName, Node parent,
+            JarResource jarResource)
+    throws FileNotFoundException, JasperException, IOException {
+        // For files that are statically included, isTagfile and directiveOnly
+        // remain unchanged.
+        return doParse(inFileName, parent, jarResource);
+    }
+
+    /**
+     * Extracts tag file directive information from the given tag file.
+     *
+     * This is invoked by the compiler 
+     *
+     * @param inFileName    The name of the tag file to be parsed.
+     * @param jarResource The location of the tag file.
+     */
+    public Node.Nodes parseTagFileDirectives(String inFileName,
+            JarResource jarResource)
+            throws FileNotFoundException, JasperException, IOException {
+        boolean isTagFileSave = isTagFile;
+        boolean directiveOnlySave = directiveOnly;
+        isTagFile = true;
+        directiveOnly = true;
+        Node.Nodes page = doParse(inFileName, null, jarResource);
+        directiveOnly = directiveOnlySave;
+        isTagFile = isTagFileSave;
+        return page;
+    }
+
+    /**
+     * Parses the JSP page or tag file with the given path name.
+     *
+     * @param inFileName The name of the JSP page or tag file to be parsed.
+     * @param parent The parent node (non-null when processing an include
+     * directive)
+     * @param isTagFile true if file to be parsed is tag file, and false if it
+     * is a regular JSP page
+     * @param directivesOnly true if the file to be parsed is a tag file and
+     * we are only interested in the directives needed for constructing a
+     * TagFileInfo.
+     * @param jarResource The JAR file from which to read the JSP page or tag file,
+     * or null if the JSP page or tag file is to be read from the filesystem
+     */
+    private Node.Nodes doParse(String inFileName,
+            Node parent,
+            JarResource jarResource)
+    throws FileNotFoundException, JasperException, IOException {
+
+        Node.Nodes parsedPage = null;
+        isEncodingSpecifiedInProlog = false;
+        isBomPresent = false;
+        isDefaultPageEncoding = false;
+
+        JarFile jarFile = (jarResource == null) ? null : jarResource.getJarFile();
+        String absFileName = resolveFileName(inFileName);
+        String jspConfigPageEnc = getJspConfigPageEncoding(absFileName);
+
+        // Figure out what type of JSP document and encoding type we are
+        // dealing with
+        determineSyntaxAndEncoding(absFileName, jarFile, jspConfigPageEnc);
+
+        if (parent != null) {
+            // Included resource, add to dependent list
+            if (jarFile == null) {
+                compiler.getPageInfo().addDependant(absFileName);
+            } else {
+                compiler.getPageInfo().addDependant(
+                        jarResource.getEntry(absFileName.substring(1)).toString());
+                        
+            }
+        }
+
+        if ((isXml && isEncodingSpecifiedInProlog) || isBomPresent) {
+            /*
+             * Make sure the encoding explicitly specified in the XML
+             * prolog (if any) matches that in the JSP config element
+             * (if any), treating "UTF-16", "UTF-16BE", and "UTF-16LE" as
+             * identical.
+             */
+            if (jspConfigPageEnc != null && !jspConfigPageEnc.equals(sourceEnc)
+                    && (!jspConfigPageEnc.startsWith("UTF-16")
+                            || !sourceEnc.startsWith("UTF-16"))) {
+                err.jspError("jsp.error.prolog_config_encoding_mismatch",
+                        sourceEnc, jspConfigPageEnc);
+            }
+        }
+
+        // Dispatch to the appropriate parser
+        if (isXml) {
+            // JSP document (XML syntax)
+            // InputStream for jspx page is created and properly closed in
+            // JspDocumentParser.
+            parsedPage = JspDocumentParser.parse(this, absFileName,
+                    jarFile, parent,
+                    isTagFile, directiveOnly,
+                    sourceEnc,
+                    jspConfigPageEnc,
+                    isEncodingSpecifiedInProlog,
+                    isBomPresent);
+        } else {
+            // Standard syntax
+            InputStreamReader inStreamReader = null;
+            try {
+                inStreamReader = JspUtil.getReader(absFileName, sourceEnc,
+                        jarFile, ctxt, err, skip);
+                JspReader jspReader = new JspReader(ctxt, absFileName,
+                        sourceEnc, inStreamReader,
+                        err);
+                parsedPage = Parser.parse(this, jspReader, parent, isTagFile,
+                        directiveOnly, jarResource,
+                        sourceEnc, jspConfigPageEnc,
+                        isDefaultPageEncoding, isBomPresent);
+            } finally {
+                if (inStreamReader != null) {
+                    try {
+                        inStreamReader.close();
+                    } catch (Exception any) {
+                    }
+                }
+            }
+        }
+
+        if (jarFile != null) {
+            try {
+                jarFile.close();
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+            }
+        }
+
+        baseDirStack.pop();
+
+        return parsedPage;
+    }
+
+    /*
+     * Checks to see if the given URI is matched by a URL pattern specified in
+     * a jsp-property-group in web.xml, and if so, returns the value of the
+     * <page-encoding> element.
+     *
+     * @param absFileName The URI to match
+     *
+     * @return The value of the <page-encoding> attribute of the 
+     * jsp-property-group with matching URL pattern
+     */
+    private String getJspConfigPageEncoding(String absFileName)
+    throws JasperException {
+
+        JspConfig jspConfig = ctxt.getOptions().getJspConfig();
+        JspConfig.JspProperty jspProperty
+            = jspConfig.findJspProperty(absFileName);
+        return jspProperty.getPageEncoding();
+    }
+
+    /**
+     * Determines the syntax (standard or XML) and page encoding properties
+     * for the given file, and stores them in the 'isXml' and 'sourceEnc'
+     * instance variables, respectively.
+     */
+    private void determineSyntaxAndEncoding(String absFileName,
+            JarFile jarFile,
+            String jspConfigPageEnc)
+    throws JasperException, IOException {
+
+        isXml = false;
+
+        /*
+         * 'true' if the syntax (XML or standard) of the file is given
+         * from external information: either via a JSP configuration element,
+         * the ".jspx" suffix, or the enclosing file (for included resources)
+         */
+        boolean isExternal = false;
+
+        /*
+         * Indicates whether we need to revert from temporary usage of
+         * "ISO-8859-1" back to "UTF-8"
+         */
+        boolean revert = false;
+
+        JspConfig jspConfig = ctxt.getOptions().getJspConfig();
+        JspConfig.JspProperty jspProperty = jspConfig.findJspProperty(
+                absFileName);
+        if (jspProperty.isXml() != null) {
+            // If <is-xml> is specified in a <jsp-property-group>, it is used.
+            isXml = JspUtil.booleanValue(jspProperty.isXml());
+            isExternal = true;
+        } else if (absFileName.endsWith(".jspx")
+                || absFileName.endsWith(".tagx")) {
+            isXml = true;
+            isExternal = true;
+        }
+
+        if (isExternal && !isXml) {
+            // JSP (standard) syntax. Use encoding specified in jsp-config
+            // if provided.
+            sourceEnc = jspConfigPageEnc;
+            if (sourceEnc != null) {
+                return;
+            }
+            // We don't know the encoding, so use BOM to determine it
+            sourceEnc = "ISO-8859-1";
+        } else {
+            // XML syntax or unknown, (auto)detect encoding ...
+            Object[] ret = XMLEncodingDetector.getEncoding(absFileName,
+                    jarFile, ctxt, err);
+            sourceEnc = (String) ret[0];
+            if (((Boolean) ret[1]).booleanValue()) {
+                isEncodingSpecifiedInProlog = true;
+            }
+            if (((Boolean) ret[2]).booleanValue()) {
+                isBomPresent = true;
+            }
+            skip = ((Integer) ret[3]).intValue();
+
+            if (!isXml && sourceEnc.equals("UTF-8")) {
+                /*
+                 * We don't know if we're dealing with XML or standard syntax.
+                 * Therefore, we need to check to see if the page contains
+                 * a <jsp:root> element.
+                 *
+                 * We need to be careful, because the page may be encoded in
+                 * ISO-8859-1 (or something entirely different), and may
+                 * contain byte sequences that will cause a UTF-8 converter to
+                 * throw exceptions. 
+                 *
+                 * It is safe to use a source encoding of ISO-8859-1 in this
+                 * case, as there are no invalid byte sequences in ISO-8859-1,
+                 * and the byte/character sequences we're looking for (i.e.,
+                 * <jsp:root>) are identical in either encoding (both UTF-8
+                 * and ISO-8859-1 are extensions of ASCII).
+                 */
+                sourceEnc = "ISO-8859-1";
+                revert = true;
+            }
+        }
+
+        if (isXml) {
+            // (This implies 'isExternal' is TRUE.)
+            // We know we're dealing with a JSP document (via JSP config or
+            // ".jspx" suffix), so we're done.
+            return;
+        }
+
+        /*
+         * At this point, 'isExternal' or 'isXml' is FALSE.
+         * Search for jsp:root action, in order to determine if we're dealing 
+         * with XML or standard syntax (unless we already know what we're 
+         * dealing with, i.e., when 'isExternal' is TRUE and 'isXml' is FALSE).
+         * No check for XML prolog, since nothing prevents a page from
+         * outputting XML and still using JSP syntax (in this case, the 
+         * XML prolog is treated as template text).
+         */
+        JspReader jspReader = null;
+        try {
+            jspReader = new JspReader(ctxt, absFileName, sourceEnc, jarFile,
+                    err);
+        } catch (FileNotFoundException ex) {
+            throw new JasperException(ex);
+        }
+        jspReader.setSingleFile(true);
+        Mark startMark = jspReader.mark();
+        if (!isExternal) {
+            jspReader.reset(startMark);
+            if (hasJspRoot(jspReader)) {
+                if (revert) {
+                    sourceEnc = "UTF-8";
+                }
+                isXml = true;
+                return;
+            } else {
+                if (revert && isBomPresent) {
+                    sourceEnc = "UTF-8";
+                }
+                isXml = false;
+            }
+        }
+
+        /*
+         * At this point, we know we're dealing with JSP syntax.
+         * If an XML prolog is provided, it's treated as template text.
+         * Determine the page encoding from the page directive, unless it's
+         * specified via JSP config.
+         */
+        if (!isBomPresent) {
+            sourceEnc = jspConfigPageEnc;
+            if (sourceEnc == null) {
+                sourceEnc = getPageEncodingForJspSyntax(jspReader, startMark);
+                if (sourceEnc == null) {
+                    // Default to "ISO-8859-1" per JSP spec
+                    sourceEnc = "ISO-8859-1";
+                    isDefaultPageEncoding = true;
+                }
+            }
+        }
+        
+    }
+
+    /*
+     * Determines page source encoding for page or tag file in JSP syntax,
+     * by reading (in this order) the value of the 'pageEncoding' page
+     * directive attribute, or the charset value of the 'contentType' page
+     * directive attribute.
+     *
+     * @return The page encoding, or null if not found
+     */
+    private String getPageEncodingForJspSyntax(JspReader jspReader,
+            Mark startMark)
+    throws JasperException {
+
+        String encoding = null;
+        String saveEncoding = null;
+
+        jspReader.reset(startMark);
+
+        /*
+         * Determine page encoding from directive of the form <%@ page %>,
+         * <%@ tag %>, <jsp:directive.page > or <jsp:directive.tag >.
+         */
+        while (true) {
+            if (jspReader.skipUntil("<") == null) {
+                break;
+            }
+            // If this is a comment, skip until its end
+            if (jspReader.matches("%--")) {
+                if (jspReader.skipUntil("--%>") == null) {
+                    // error will be caught in Parser
+                    break;
+                }
+                continue;
+            }
+            boolean isDirective = jspReader.matches("%@");
+            if (isDirective) {
+                jspReader.skipSpaces();
+            }
+            else {
+                isDirective = jspReader.matches("jsp:directive.");
+            }
+            if (!isDirective) {
+                continue;
+            }
+
+            // compare for "tag ", so we don't match "taglib"
+            if (jspReader.matches("tag ") || jspReader.matches("page")) {
+
+                jspReader.skipSpaces();
+                Attributes attrs = Parser.parseAttributes(this, jspReader);
+                encoding = getPageEncodingFromDirective(attrs, "pageEncoding");
+                if (encoding != null) {
+                    break;
+                }
+                encoding = getPageEncodingFromDirective(attrs, "contentType");
+                if (encoding != null) {
+                    saveEncoding = encoding;
+                }
+            }
+        }
+
+        if (encoding == null) {
+            encoding = saveEncoding;
+        }
+
+        return encoding;
+    }
+
+    /*
+     * Scans the given attributes for the attribute with the given name,
+     * which is either 'pageEncoding' or 'contentType', and returns the
+     * specified page encoding.
+     *
+     * In the case of 'contentType', the page encoding is taken from the
+     * content type's 'charset' component.
+     *
+     * @param attrs The page directive attributes
+     * @param attrName The name of the attribute to search for (either
+     * 'pageEncoding' or 'contentType')
+     *
+     * @return The page encoding, or null
+     */
+    private String getPageEncodingFromDirective(Attributes attrs,
+            String attrName) {
+        String value = attrs.getValue(attrName);
+        if (attrName.equals("pageEncoding")) {
+            return value;
+        }
+
+        // attrName = contentType
+        String contentType = value;
+        String encoding = null;
+        if (contentType != null) {
+            int loc = contentType.indexOf(CHARSET);
+            if (loc != -1) {
+                encoding = contentType.substring(loc + CHARSET.length());
+            }
+        }
+
+        return encoding;
+    }
+
+    /*
+     * Resolve the name of the file and update baseDirStack() to keep track of
+     * the current base directory for each included file.
+     * The 'root' file is always an 'absolute' path, so no need to put an
+     * initial value in the baseDirStack.
+     */
+    private String resolveFileName(String inFileName) {
+        String fileName = inFileName.replace('\\', '/');
+        boolean isAbsolute = fileName.startsWith("/");
+        fileName = isAbsolute ? fileName 
+                : baseDirStack.peek() + fileName;
+        String baseDir = 
+            fileName.substring(0, fileName.lastIndexOf("/") + 1);
+        baseDirStack.push(baseDir);
+        return fileName;
+    }
+
+    /*
+     * Checks to see if the given page contains, as its first element, a <root>
+     * element whose prefix is bound to the JSP namespace, as in:
+     *
+     * <wombat:root xmlns:wombat="http://java.sun.com/JSP/Page" version="1.2">
+     *   ...
+     * </wombat:root>
+     *
+     * @param reader The reader for this page
+     *
+     * @return true if this page contains a root element whose prefix is bound
+     * to the JSP namespace, and false otherwise
+     */
+    private boolean hasJspRoot(JspReader reader) throws JasperException {
+
+        // <prefix>:root must be the first element
+        Mark start = null;
+        while ((start = reader.skipUntil("<")) != null) {
+            int c = reader.nextChar();
+            if (c != '!' && c != '?') break;
+        }
+        if (start == null) {
+            return false;
+        }
+        Mark stop = reader.skipUntil(":root");
+        if (stop == null) {
+            return false;
+        }
+        // call substring to get rid of leading '<'
+        String prefix = reader.getText(start, stop).substring(1);
+
+        start = stop;
+        stop = reader.skipUntil(">");
+        if (stop == null) {
+            return false;
+        }
+
+        // Determine namespace associated with <root> element's prefix
+        String root = reader.getText(start, stop);
+        String xmlnsDecl = "xmlns:" + prefix;
+        int index = root.indexOf(xmlnsDecl);
+        if (index == -1) {
+            return false;
+        }
+        index += xmlnsDecl.length();
+        while (index < root.length()
+                && Character.isWhitespace(root.charAt(index))) {
+            index++;
+        }
+        if (index < root.length() && root.charAt(index) == '=') {
+            index++;
+            while (index < root.length()
+                    && Character.isWhitespace(root.charAt(index))) {
+                index++;
+            }
+            if (index < root.length() 
+                && (root.charAt(index) == '"' || root.charAt(index) == '\'')) {
+                index++;
+                if (root.regionMatches(index, JSP_URI, 0, JSP_URI.length())) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ScriptingVariabler.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ScriptingVariabler.java
new file mode 100644
index 0000000..3249496
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ScriptingVariabler.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.jasper.compiler;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.jsp.tagext.TagVariableInfo;
+import javax.servlet.jsp.tagext.VariableInfo;
+
+import org.apache.jasper.JasperException;
+
+/**
+ * Class responsible for determining the scripting variables that every
+ * custom action needs to declare.
+ *
+ * @author Jan Luehe
+ */
+class ScriptingVariabler {
+
+    private static final Integer MAX_SCOPE = new Integer(Integer.MAX_VALUE);
+
+    /*
+     * Assigns an identifier (of type integer) to every custom tag, in order
+     * to help identify, for every custom tag, the scripting variables that it
+     * needs to declare.
+     */
+    static class CustomTagCounter extends Node.Visitor {
+
+        private int count;
+        private Node.CustomTag parent;
+
+        @Override
+        public void visit(Node.CustomTag n) throws JasperException {
+            n.setCustomTagParent(parent);
+            Node.CustomTag tmpParent = parent;
+            parent = n;
+            visitBody(n);
+            parent = tmpParent;
+            n.setNumCount(new Integer(count++));
+        }
+    }
+
+    /*
+     * For every custom tag, determines the scripting variables it needs to
+     * declare. 
+     */
+    static class ScriptingVariableVisitor extends Node.Visitor {
+
+        private ErrorDispatcher err;
+        private Map<String, Integer> scriptVars;
+
+        public ScriptingVariableVisitor(ErrorDispatcher err) {
+            this.err = err;
+            scriptVars = new HashMap<String,Integer>();
+        }
+
+        @Override
+        public void visit(Node.CustomTag n) throws JasperException {
+            setScriptingVars(n, VariableInfo.AT_BEGIN);
+            setScriptingVars(n, VariableInfo.NESTED);
+            visitBody(n);
+            setScriptingVars(n, VariableInfo.AT_END);
+        }
+
+        private void setScriptingVars(Node.CustomTag n, int scope)
+                throws JasperException {
+
+            TagVariableInfo[] tagVarInfos = n.getTagVariableInfos();
+            VariableInfo[] varInfos = n.getVariableInfos();
+            if (tagVarInfos.length == 0 && varInfos.length == 0) {
+                return;
+            }
+
+            List<Object> vec = new ArrayList<Object>();
+
+            Integer ownRange = null;
+            Node.CustomTag parent = n.getCustomTagParent();
+            if (scope == VariableInfo.AT_BEGIN
+                    || scope == VariableInfo.AT_END) {
+                if (parent == null)
+                    ownRange = MAX_SCOPE;
+                else
+                    ownRange = parent.getNumCount();
+            } else {
+                // NESTED
+                ownRange = n.getNumCount();
+            }
+
+            if (varInfos.length > 0) {
+                for (int i=0; i<varInfos.length; i++) {
+                    if (varInfos[i].getScope() != scope
+                            || !varInfos[i].getDeclare()) {
+                        continue;
+                    }
+                    String varName = varInfos[i].getVarName();
+                    
+                    Integer currentRange = scriptVars.get(varName);
+                    if (currentRange == null ||
+                            ownRange.compareTo(currentRange) > 0) {
+                        scriptVars.put(varName, ownRange);
+                        vec.add(varInfos[i]);
+                    }
+                }
+            } else {
+                for (int i=0; i<tagVarInfos.length; i++) {
+                    if (tagVarInfos[i].getScope() != scope
+                            || !tagVarInfos[i].getDeclare()) {
+                        continue;
+                    }
+                    String varName = tagVarInfos[i].getNameGiven();
+                    if (varName == null) {
+                        varName = n.getTagData().getAttributeString(
+                                        tagVarInfos[i].getNameFromAttribute());
+                        if (varName == null) {
+                            err.jspError(n,
+                                    "jsp.error.scripting.variable.missing_name",
+                                    tagVarInfos[i].getNameFromAttribute());
+                        }
+                    }
+
+                    Integer currentRange = scriptVars.get(varName);
+                    if (currentRange == null ||
+                            ownRange.compareTo(currentRange) > 0) {
+                        scriptVars.put(varName, ownRange);
+                        vec.add(tagVarInfos[i]);
+                    }
+                }
+            }
+
+            n.setScriptingVars(vec, scope);
+        }
+    }
+
+    public static void set(Node.Nodes page, ErrorDispatcher err)
+            throws JasperException {
+        page.visit(new CustomTagCounter());
+        page.visit(new ScriptingVariableVisitor(err));
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ServletWriter.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ServletWriter.java
new file mode 100644
index 0000000..3a21386
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/ServletWriter.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.compiler;
+
+import java.io.PrintWriter;
+
+/**
+ * This is what is used to generate servlets. 
+ *
+ * @author Anil K. Vijendran
+ * @author Kin-man Chung
+ */
+public class ServletWriter {
+    public static final int TAB_WIDTH = 2;
+    public static final String SPACES = "                              ";
+
+    // Current indent level:
+    private int indent = 0;
+    private int virtual_indent = 0;
+
+    // The sink writer:
+    PrintWriter writer;
+    
+    // servlet line numbers start from 1
+    private int javaLine = 1;
+
+
+    public ServletWriter(PrintWriter writer) {
+        this.writer = writer;
+    }
+
+    public void close() {
+        writer.close();
+    }
+
+    
+    // -------------------- Access informations --------------------
+
+    public int getJavaLine() {
+        return javaLine;
+    }
+
+
+    // -------------------- Formatting --------------------
+
+    public void pushIndent() {
+        virtual_indent += TAB_WIDTH;
+        if (virtual_indent >= 0 && virtual_indent <= SPACES.length())
+            indent = virtual_indent;
+    }
+
+    public void popIndent() {
+        virtual_indent -= TAB_WIDTH;
+        if (virtual_indent >= 0 && virtual_indent <= SPACES.length())
+            indent = virtual_indent;
+    }
+
+    /**
+     * Prints the given string followed by '\n'
+     */
+    public void println(String s) {
+        javaLine++;
+        writer.println(s);
+    }
+
+    /**
+     * Prints a '\n'
+     */
+    public void println() {
+        javaLine++;
+        writer.println("");
+    }
+
+    /**
+     * Prints the current indention
+     */
+    public void printin() {
+        writer.print(SPACES.substring(0, indent));
+    }
+
+    /**
+     * Prints the current indention, followed by the given string
+     */
+    public void printin(String s) {
+        writer.print(SPACES.substring(0, indent));
+        writer.print(s);
+    }
+
+    /**
+     * Prints the current indention, and then the string, and a '\n'.
+     */
+    public void printil(String s) {
+        javaLine++;
+        writer.print(SPACES.substring(0, indent));
+        writer.println(s);
+    }
+
+    /**
+     * Prints the given char.
+     *
+     * Use println() to print a '\n'.
+     */
+    public void print(char c) {
+        writer.print(c);
+    }
+
+    /**
+     * Prints the given int.
+     */
+    public void print(int i) {
+        writer.print(i);
+    }
+
+    /**
+     * Prints the given string.
+     *
+     * The string must not contain any '\n', otherwise the line count will be
+     * off.
+     */
+    public void print(String s) {
+        writer.print(s);
+    }
+
+    /**
+     * Prints the given string.
+     *
+     * If the string spans multiple lines, the line count will be adjusted
+     * accordingly.
+     */
+    public void printMultiLn(String s) {
+        int index = 0;
+
+        // look for hidden newlines inside strings
+        while ((index=s.indexOf('\n',index)) > -1 ) {
+            javaLine++;
+            index++;
+        }
+
+        writer.print(s);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/SmapGenerator.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/SmapGenerator.java
new file mode 100644
index 0000000..97d4f39
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/SmapGenerator.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.compiler;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a source map (SMAP), which serves to associate lines
+ * of the input JSP file(s) to lines in the generated servlet in the
+ * final .class file, according to the JSR-045 spec.
+ * 
+ * @author Shawn Bayern
+ */
+public class SmapGenerator {
+
+    //*********************************************************************
+    // Overview
+
+    /*
+     * The SMAP syntax is reasonably straightforward.  The purpose of this
+     * class is currently twofold:
+     *  - to provide a simple but low-level Java interface to build
+     *    a logical SMAP
+     *  - to serialize this logical SMAP for eventual inclusion directly
+     *    into a .class file.
+     */
+
+
+    //*********************************************************************
+    // Private state
+
+    private String outputFileName;
+    private String defaultStratum = "Java";
+    private List<SmapStratum> strata = new ArrayList<SmapStratum>();
+    private List<String> embedded = new ArrayList<String>();
+    private boolean doEmbedded = true;
+
+    //*********************************************************************
+    // Methods for adding mapping data
+
+    /**
+     * Sets the filename (without path information) for the generated
+     * source file.  E.g., "foo$jsp.java".
+     */
+    public synchronized void setOutputFileName(String x) {
+        outputFileName = x;
+    }
+
+    /**
+     * Adds the given SmapStratum object, representing a Stratum with
+     * logically associated FileSection and LineSection blocks, to
+     * the current SmapGenerator.  If <tt>default</tt> is true, this
+     * stratum is made the default stratum, overriding any previously
+     * set default.
+     *
+     * @param stratum the SmapStratum object to add
+     * @param defaultStratum if <tt>true</tt>, this SmapStratum is considered
+     *                to represent the default SMAP stratum unless
+     *                overwritten
+     */
+    public synchronized void addStratum(SmapStratum stratum,
+                                        boolean defaultStratum) {
+        strata.add(stratum);
+        if (defaultStratum)
+            this.defaultStratum = stratum.getStratumName();
+    }
+
+    /**
+     * Adds the given string as an embedded SMAP with the given stratum name.
+     *
+     * @param smap the SMAP to embed
+     * @param stratumName the name of the stratum output by the compilation
+     *                    that produced the <tt>smap</tt> to be embedded
+     */
+    public synchronized void addSmap(String smap, String stratumName) {
+        embedded.add("*O " + stratumName + "\n"
+                   + smap
+                   + "*C " + stratumName + "\n");
+    }
+
+    /**
+     * Instructs the SmapGenerator whether to actually print any embedded
+     * SMAPs or not.  Intended for situations without an SMAP resolver.
+     *
+     * @param status If <tt>false</tt>, ignore any embedded SMAPs.
+     */
+    public void setDoEmbedded(boolean status) {
+        doEmbedded = status;
+    }
+
+    //*********************************************************************
+    // Methods for serializing the logical SMAP
+
+    public synchronized String getString() {
+        // check state and initialize buffer
+        if (outputFileName == null)
+            throw new IllegalStateException();
+        StringBuilder out = new StringBuilder();
+
+        // start the SMAP
+        out.append("SMAP\n");
+        out.append(outputFileName + '\n');
+        out.append(defaultStratum + '\n');
+
+        // include embedded SMAPs
+        if (doEmbedded) {
+            int nEmbedded = embedded.size();
+            for (int i = 0; i < nEmbedded; i++) {
+                out.append(embedded.get(i));
+            }
+        }
+
+        // print our StratumSections, FileSections, and LineSections
+        int nStrata = strata.size();
+        for (int i = 0; i < nStrata; i++) {
+            SmapStratum s = strata.get(i);
+            out.append(s.getString());
+        }
+
+        // end the SMAP
+        out.append("*E\n");
+
+        return out.toString();
+    }
+
+    @Override
+    public String toString() { return getString(); }
+
+    //*********************************************************************
+    // For testing (and as an example of use)...
+
+    public static void main(String args[]) {
+        SmapGenerator g = new SmapGenerator();
+        g.setOutputFileName("foo.java");
+        SmapStratum s = new SmapStratum("JSP");
+        s.addFile("foo.jsp");
+        s.addFile("bar.jsp", "/foo/foo/bar.jsp");
+        s.addLineData(1, "foo.jsp", 1, 1, 1);
+        s.addLineData(2, "foo.jsp", 1, 6, 1);
+        s.addLineData(3, "foo.jsp", 2, 10, 5);
+        s.addLineData(20, "bar.jsp", 1, 30, 1);
+        g.addStratum(s, true);
+        System.out.print(g);
+
+        System.out.println("---");
+
+        SmapGenerator embedded = new SmapGenerator();
+        embedded.setOutputFileName("blargh.tier2");
+        s = new SmapStratum("Tier2");
+        s.addFile("1.tier2");
+        s.addLineData(1, "1.tier2", 1, 1, 1);
+        embedded.addStratum(s, true);
+        g.addSmap(embedded.toString(), "JSP");
+        System.out.println(g);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/SmapStratum.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/SmapStratum.java
new file mode 100644
index 0000000..a737c02
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/SmapStratum.java
@@ -0,0 +1,338 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents the line and file mappings associated with a JSR-045
+ * "stratum".
+ *
+ * @author Jayson Falkner
+ * @author Shawn Bayern
+ */
+public class SmapStratum {
+
+    //*********************************************************************
+    // Class for storing LineInfo data
+
+    /**
+     * Represents a single LineSection in an SMAP, associated with
+     * a particular stratum.
+     */
+    public static class LineInfo {
+        private int inputStartLine = -1;
+        private int outputStartLine = -1;
+        private int lineFileID = 0;
+        private int inputLineCount = 1;
+        private int outputLineIncrement = 1;
+        private boolean lineFileIDSet = false;
+
+        /** Sets InputStartLine. */
+        public void setInputStartLine(int inputStartLine) {
+            if (inputStartLine < 0)
+                throw new IllegalArgumentException("" + inputStartLine);
+            this.inputStartLine = inputStartLine;
+        }
+
+        /** Sets OutputStartLine. */
+        public void setOutputStartLine(int outputStartLine) {
+            if (outputStartLine < 0)
+                throw new IllegalArgumentException("" + outputStartLine);
+            this.outputStartLine = outputStartLine;
+        }
+
+        /**
+             * Sets lineFileID.  Should be called only when different from
+             * that of prior LineInfo object (in any given context) or 0
+             * if the current LineInfo has no (logical) predecessor.
+             * <tt>LineInfo</tt> will print this file number no matter what.
+             */
+        public void setLineFileID(int lineFileID) {
+            if (lineFileID < 0)
+                throw new IllegalArgumentException("" + lineFileID);
+            this.lineFileID = lineFileID;
+            this.lineFileIDSet = true;
+        }
+
+        /** Sets InputLineCount. */
+        public void setInputLineCount(int inputLineCount) {
+            if (inputLineCount < 0)
+                throw new IllegalArgumentException("" + inputLineCount);
+            this.inputLineCount = inputLineCount;
+        }
+
+        /** Sets OutputLineIncrement. */
+        public void setOutputLineIncrement(int outputLineIncrement) {
+            if (outputLineIncrement < 0)
+                throw new IllegalArgumentException("" + outputLineIncrement);
+            this.outputLineIncrement = outputLineIncrement;
+        }
+
+        /**
+         * Retrieves the current LineInfo as a String, print all values
+         * only when appropriate (but LineInfoID if and only if it's been
+         * specified, as its necessity is sensitive to context).
+         */
+        public String getString() {
+            if (inputStartLine == -1 || outputStartLine == -1)
+                throw new IllegalStateException();
+            StringBuilder out = new StringBuilder();
+            out.append(inputStartLine);
+            if (lineFileIDSet)
+                out.append("#" + lineFileID);
+            if (inputLineCount != 1)
+                out.append("," + inputLineCount);
+            out.append(":" + outputStartLine);
+            if (outputLineIncrement != 1)
+                out.append("," + outputLineIncrement);
+            out.append('\n');
+            return out.toString();
+        }
+
+        @Override
+        public String toString() {
+            return getString();
+        }
+    }
+
+    //*********************************************************************
+    // Private state
+
+    private String stratumName;
+    private List<String> fileNameList;
+    private List<String> filePathList;
+    private List<LineInfo> lineData;
+    private int lastFileID;
+
+    //*********************************************************************
+    // Constructor
+
+    /**
+     * Constructs a new SmapStratum object for the given stratum name
+     * (e.g., JSP).
+     *
+     * @param stratumName the name of the stratum (e.g., JSP)
+     */
+    public SmapStratum(String stratumName) {
+        this.stratumName = stratumName;
+        fileNameList = new ArrayList<String>();
+        filePathList = new ArrayList<String>();
+        lineData = new ArrayList<LineInfo>();
+        lastFileID = 0;
+    }
+
+    //*********************************************************************
+    // Methods to add mapping information
+
+    /**
+     * Adds record of a new file, by filename.
+     *
+     * @param filename the filename to add, unqualified by path.
+     */
+    public void addFile(String filename) {
+        addFile(filename, filename);
+    }
+
+    /**
+     * Adds record of a new file, by filename and path.  The path
+     * may be relative to a source compilation path.
+     *
+     * @param filename the filename to add, unqualified by path
+     * @param filePath the path for the filename, potentially relative
+     *                 to a source compilation path
+     */
+    public void addFile(String filename, String filePath) {
+        int pathIndex = filePathList.indexOf(filePath);
+        if (pathIndex == -1) {
+            fileNameList.add(filename);
+            filePathList.add(filePath);
+        }
+    }
+
+    /**
+     * Combines consecutive LineInfos wherever possible
+     */
+    public void optimizeLineSection() {
+
+/* Some debugging code
+        for (int i = 0; i < lineData.size(); i++) {
+            LineInfo li = (LineInfo)lineData.get(i);
+            System.out.print(li.toString());
+        }
+*/
+        //Incorporate each LineInfo into the previous LineInfo's 
+        //outputLineIncrement, if possible
+        int i = 0;
+        while (i < lineData.size() - 1) {
+            LineInfo li = lineData.get(i);
+            LineInfo liNext = lineData.get(i + 1);
+            if (!liNext.lineFileIDSet
+                && liNext.inputStartLine == li.inputStartLine
+                && liNext.inputLineCount == 1
+                && li.inputLineCount == 1
+                && liNext.outputStartLine
+                    == li.outputStartLine
+                        + li.inputLineCount * li.outputLineIncrement) {
+                li.setOutputLineIncrement(
+                    liNext.outputStartLine
+                        - li.outputStartLine
+                        + liNext.outputLineIncrement);
+                lineData.remove(i + 1);
+            } else {
+                i++;
+            }
+        }
+
+        //Incorporate each LineInfo into the previous LineInfo's
+        //inputLineCount, if possible
+        i = 0;
+        while (i < lineData.size() - 1) {
+            LineInfo li = lineData.get(i);
+            LineInfo liNext = lineData.get(i + 1);
+            if (!liNext.lineFileIDSet
+                && liNext.inputStartLine == li.inputStartLine + li.inputLineCount
+                && liNext.outputLineIncrement == li.outputLineIncrement
+                && liNext.outputStartLine
+                    == li.outputStartLine
+                        + li.inputLineCount * li.outputLineIncrement) {
+                li.setInputLineCount(li.inputLineCount + liNext.inputLineCount);
+                lineData.remove(i + 1);
+            } else {
+                i++;
+            }
+        }
+    }
+
+    /**
+     * Adds complete information about a simple line mapping.  Specify
+     * all the fields in this method; the back-end machinery takes care
+     * of printing only those that are necessary in the final SMAP.
+     * (My view is that fields are optional primarily for spatial efficiency,
+     * not for programmer convenience.  Could always add utility methods
+     * later.)
+     *
+     * @param inputStartLine starting line in the source file
+     *        (SMAP <tt>InputStartLine</tt>)
+     * @param inputFileName the filepath (or name) from which the input comes
+     *        (yields SMAP <tt>LineFileID</tt>)  Use unqualified names
+     *        carefully, and only when they uniquely identify a file.
+     * @param inputLineCount the number of lines in the input to map
+     *        (SMAP <tt>LineFileCount</tt>)
+     * @param outputStartLine starting line in the output file 
+     *        (SMAP <tt>OutputStartLine</tt>)
+     * @param outputLineIncrement number of output lines to map to each
+     *        input line (SMAP <tt>OutputLineIncrement</tt>).  <i>Given the
+     *        fact that the name starts with "output", I continuously have
+     *        the subconscious urge to call this field
+     *        <tt>OutputLineExcrement</tt>.</i>
+     */
+    public void addLineData(
+        int inputStartLine,
+        String inputFileName,
+        int inputLineCount,
+        int outputStartLine,
+        int outputLineIncrement) {
+        // check the input - what are you doing here??
+        int fileIndex = filePathList.indexOf(inputFileName);
+        if (fileIndex == -1) // still
+            throw new IllegalArgumentException(
+                "inputFileName: " + inputFileName);
+
+        //Jasper incorrectly SMAPs certain Nodes, giving them an 
+        //outputStartLine of 0.  This can cause a fatal error in
+        //optimizeLineSection, making it impossible for Jasper to
+        //compile the JSP.  Until we can fix the underlying
+        //SMAPping problem, we simply ignore the flawed SMAP entries.
+        if (outputStartLine == 0)
+            return;
+
+        // build the LineInfo
+        LineInfo li = new LineInfo();
+        li.setInputStartLine(inputStartLine);
+        li.setInputLineCount(inputLineCount);
+        li.setOutputStartLine(outputStartLine);
+        li.setOutputLineIncrement(outputLineIncrement);
+        if (fileIndex != lastFileID)
+            li.setLineFileID(fileIndex);
+        lastFileID = fileIndex;
+
+        // save it
+        lineData.add(li);
+    }
+
+    //*********************************************************************
+    // Methods to retrieve information
+
+    /**
+     * Returns the name of the stratum.
+     */
+    public String getStratumName() {
+        return stratumName;
+    }
+
+    /**
+     * Returns the given stratum as a String:  a StratumSection,
+     * followed by at least one FileSection and at least one LineSection.
+     */
+    public String getString() {
+        // check state and initialize buffer
+        if (fileNameList.size() == 0 || lineData.size() == 0)
+            return null;
+
+        StringBuilder out = new StringBuilder();
+
+        // print StratumSection
+        out.append("*S " + stratumName + "\n");
+
+        // print FileSection
+        out.append("*F\n");
+        int bound = fileNameList.size();
+        for (int i = 0; i < bound; i++) {
+            if (filePathList.get(i) != null) {
+                out.append("+ " + i + " " + fileNameList.get(i) + "\n");
+                // Source paths must be relative, not absolute, so we
+                // remove the leading "/", if one exists.
+                String filePath = filePathList.get(i);
+                if (filePath.startsWith("/")) {
+                    filePath = filePath.substring(1);
+                }
+                out.append(filePath + "\n");
+            } else {
+                out.append(i + " " + fileNameList.get(i) + "\n");
+            }
+        }
+
+        // print LineSection
+        out.append("*L\n");
+        bound = lineData.size();
+        for (int i = 0; i < bound; i++) {
+            LineInfo li = lineData.get(i);
+            out.append(li.getString());
+        }
+
+        return out.toString();
+    }
+
+    @Override
+    public String toString() {
+        return getString();
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/SmapUtil.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/SmapUtil.java
new file mode 100644
index 0000000..615d468
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/SmapUtil.java
@@ -0,0 +1,700 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+
+/**
+ * Contains static utilities for generating SMAP data based on the
+ * current version of Jasper.
+ * 
+ * @author Jayson Falkner
+ * @author Shawn Bayern
+ * @author Robert Field (inner SDEInstaller class)
+ * @author Mark Roth
+ * @author Kin-man Chung
+ */
+public class SmapUtil {
+
+    //*********************************************************************
+    // Constants
+
+    public static final String SMAP_ENCODING = "UTF-8";
+
+    //*********************************************************************
+    // Public entry points
+
+    /**
+     * Generates an appropriate SMAP representing the current compilation
+     * context.  (JSR-045.)
+     *
+     * @param ctxt Current compilation context
+     * @param pageNodes The current JSP page
+     * @return a SMAP for the page
+     */
+    public static String[] generateSmap(
+        JspCompilationContext ctxt,
+        Node.Nodes pageNodes)
+        throws IOException {
+
+        // Scan the nodes for presence of Jasper generated inner classes
+        PreScanVisitor psVisitor = new PreScanVisitor();
+        try {
+            pageNodes.visit(psVisitor);
+        } catch (JasperException ex) {
+        }
+        HashMap<String, SmapStratum> map = psVisitor.getMap();
+
+        // set up our SMAP generator
+        SmapGenerator g = new SmapGenerator();
+        
+        /** Disable reading of input SMAP because:
+            1. There is a bug here: getRealPath() is null if .jsp is in a jar
+               Bugzilla 14660.
+            2. Mappings from other sources into .jsp files are not supported.
+            TODO: fix 1. if 2. is not true.
+        // determine if we have an input SMAP
+        String smapPath = inputSmapPath(ctxt.getRealPath(ctxt.getJspFile()));
+            File inputSmap = new File(smapPath);
+            if (inputSmap.exists()) {
+                byte[] embeddedSmap = null;
+            byte[] subSmap = SDEInstaller.readWhole(inputSmap);
+            String subSmapString = new String(subSmap, SMAP_ENCODING);
+            g.addSmap(subSmapString, "JSP");
+        }
+        **/
+
+        // now, assemble info about our own stratum (JSP) using JspLineMap
+        SmapStratum s = new SmapStratum("JSP");
+
+        g.setOutputFileName(unqualify(ctxt.getServletJavaFileName()));
+
+        // Map out Node.Nodes
+        evaluateNodes(pageNodes, s, map, ctxt.getOptions().getMappedFile());
+        s.optimizeLineSection();
+        g.addStratum(s, true);
+
+        if (ctxt.getOptions().isSmapDumped()) {
+            File outSmap = new File(ctxt.getClassFileName() + ".smap");
+            PrintWriter so =
+                new PrintWriter(
+                    new OutputStreamWriter(
+                        new FileOutputStream(outSmap),
+                        SMAP_ENCODING));
+            so.print(g.getString());
+            so.close();
+        }
+
+        String classFileName = ctxt.getClassFileName();
+        int innerClassCount = map.size();
+        String [] smapInfo = new String[2 + innerClassCount*2];
+        smapInfo[0] = classFileName;
+        smapInfo[1] = g.getString();
+
+        int count = 2;
+        Iterator<Map.Entry<String,SmapStratum>> iter = map.entrySet().iterator();
+        while (iter.hasNext()) {
+            Map.Entry<String,SmapStratum> entry = iter.next();
+            String innerClass = entry.getKey();
+            s = entry.getValue();
+            s.optimizeLineSection();
+            g = new SmapGenerator();
+            g.setOutputFileName(unqualify(ctxt.getServletJavaFileName()));
+            g.addStratum(s, true);
+
+            String innerClassFileName =
+                classFileName.substring(0, classFileName.indexOf(".class")) +
+                '$' + innerClass + ".class";
+            if (ctxt.getOptions().isSmapDumped()) {
+                File outSmap = new File(innerClassFileName + ".smap");
+                PrintWriter so =
+                    new PrintWriter(
+                        new OutputStreamWriter(
+                            new FileOutputStream(outSmap),
+                            SMAP_ENCODING));
+                so.print(g.getString());
+                so.close();
+            }
+            smapInfo[count] = innerClassFileName;
+            smapInfo[count+1] = g.getString();
+            count += 2;
+        }
+
+        return smapInfo;
+    }
+
+    public static void installSmap(String[] smap)
+        throws IOException {
+        if (smap == null) {
+            return;
+        }
+
+        for (int i = 0; i < smap.length; i += 2) {
+            File outServlet = new File(smap[i]);
+            SDEInstaller.install(outServlet, smap[i+1].getBytes());
+        }
+    }
+
+    //*********************************************************************
+    // Private utilities
+
+    /**
+     * Returns an unqualified version of the given file path.
+     */
+    private static String unqualify(String path) {
+        path = path.replace('\\', '/');
+        return path.substring(path.lastIndexOf('/') + 1);
+    }
+
+    //*********************************************************************
+    // Installation logic (from Robert Field, JSR-045 spec lead)
+    private static class SDEInstaller {
+
+        private final org.apache.juli.logging.Log log=
+            org.apache.juli.logging.LogFactory.getLog( SDEInstaller.class );
+
+        static final String nameSDE = "SourceDebugExtension";
+
+        byte[] orig;
+        byte[] sdeAttr;
+        byte[] gen;
+
+        int origPos = 0;
+        int genPos = 0;
+
+        int sdeIndex;
+
+        static void install(File classFile, byte[] smap) throws IOException {
+            File tmpFile = new File(classFile.getPath() + "tmp");
+            new SDEInstaller(classFile, smap, tmpFile);
+            if (!classFile.delete()) {
+                throw new IOException("classFile.delete() failed");
+            }
+            if (!tmpFile.renameTo(classFile)) {
+                throw new IOException("tmpFile.renameTo(classFile) failed");
+            }
+        }
+
+        SDEInstaller(File inClassFile, byte[] sdeAttr, File outClassFile)
+            throws IOException {
+            if (!inClassFile.exists()) {
+                throw new FileNotFoundException("no such file: " + inClassFile);
+            }
+
+            this.sdeAttr = sdeAttr;
+            // get the bytes
+            orig = readWhole(inClassFile);
+            gen = new byte[orig.length + sdeAttr.length + 100];
+
+            // do it
+            addSDE();
+
+            // write result
+            FileOutputStream outStream = new FileOutputStream(outClassFile);
+            outStream.write(gen, 0, genPos);
+            outStream.close();
+        }
+
+        static byte[] readWhole(File input) throws IOException {
+            FileInputStream inStream = new FileInputStream(input);
+            int len = (int)input.length();
+            byte[] bytes = new byte[len];
+            if (inStream.read(bytes, 0, len) != len) {
+                throw new IOException("expected size: " + len);
+            }
+            inStream.close();
+            return bytes;
+        }
+
+        void addSDE() throws UnsupportedEncodingException, IOException {
+            copy(4 + 2 + 2); // magic min/maj version
+            int constantPoolCountPos = genPos;
+            int constantPoolCount = readU2();
+            if (log.isDebugEnabled())
+                log.debug("constant pool count: " + constantPoolCount);
+            writeU2(constantPoolCount);
+
+            // copy old constant pool return index of SDE symbol, if found
+            sdeIndex = copyConstantPool(constantPoolCount);
+            if (sdeIndex < 0) {
+                // if "SourceDebugExtension" symbol not there add it
+                writeUtf8ForSDE();
+
+                // increment the countantPoolCount
+                sdeIndex = constantPoolCount;
+                ++constantPoolCount;
+                randomAccessWriteU2(constantPoolCountPos, constantPoolCount);
+
+                if (log.isDebugEnabled())
+                    log.debug("SourceDebugExtension not found, installed at: " + sdeIndex);
+            } else {
+                if (log.isDebugEnabled())
+                    log.debug("SourceDebugExtension found at: " + sdeIndex);
+            }
+            copy(2 + 2 + 2); // access, this, super
+            int interfaceCount = readU2();
+            writeU2(interfaceCount);
+            if (log.isDebugEnabled())
+                log.debug("interfaceCount: " + interfaceCount);
+            copy(interfaceCount * 2);
+            copyMembers(); // fields
+            copyMembers(); // methods
+            int attrCountPos = genPos;
+            int attrCount = readU2();
+            writeU2(attrCount);
+            if (log.isDebugEnabled())
+                log.debug("class attrCount: " + attrCount);
+            // copy the class attributes, return true if SDE attr found (not copied)
+            if (!copyAttrs(attrCount)) {
+                // we will be adding SDE and it isn't already counted
+                ++attrCount;
+                randomAccessWriteU2(attrCountPos, attrCount);
+                if (log.isDebugEnabled())
+                    log.debug("class attrCount incremented");
+            }
+            writeAttrForSDE(sdeIndex);
+        }
+
+        void copyMembers() {
+            int count = readU2();
+            writeU2(count);
+            if (log.isDebugEnabled())
+                log.debug("members count: " + count);
+            for (int i = 0; i < count; ++i) {
+                copy(6); // access, name, descriptor
+                int attrCount = readU2();
+                writeU2(attrCount);
+                if (log.isDebugEnabled())
+                    log.debug("member attr count: " + attrCount);
+                copyAttrs(attrCount);
+            }
+        }
+
+        boolean copyAttrs(int attrCount) {
+            boolean sdeFound = false;
+            for (int i = 0; i < attrCount; ++i) {
+                int nameIndex = readU2();
+                // don't write old SDE
+                if (nameIndex == sdeIndex) {
+                    sdeFound = true;
+                    if (log.isDebugEnabled())
+                        log.debug("SDE attr found");
+                } else {
+                    writeU2(nameIndex); // name
+                    int len = readU4();
+                    writeU4(len);
+                    copy(len);
+                    if (log.isDebugEnabled())
+                        log.debug("attr len: " + len);
+                }
+            }
+            return sdeFound;
+        }
+
+        void writeAttrForSDE(int index) {
+            writeU2(index);
+            writeU4(sdeAttr.length);
+            for (int i = 0; i < sdeAttr.length; ++i) {
+                writeU1(sdeAttr[i]);
+            }
+        }
+
+        void randomAccessWriteU2(int pos, int val) {
+            int savePos = genPos;
+            genPos = pos;
+            writeU2(val);
+            genPos = savePos;
+        }
+
+        int readU1() {
+            return orig[origPos++] & 0xFF;
+        }
+
+        int readU2() {
+            int res = readU1();
+            return (res << 8) + readU1();
+        }
+
+        int readU4() {
+            int res = readU2();
+            return (res << 16) + readU2();
+        }
+
+        void writeU1(int val) {
+            gen[genPos++] = (byte)val;
+        }
+
+        void writeU2(int val) {
+            writeU1(val >> 8);
+            writeU1(val & 0xFF);
+        }
+
+        void writeU4(int val) {
+            writeU2(val >> 16);
+            writeU2(val & 0xFFFF);
+        }
+
+        void copy(int count) {
+            for (int i = 0; i < count; ++i) {
+                gen[genPos++] = orig[origPos++];
+            }
+        }
+
+        byte[] readBytes(int count) {
+            byte[] bytes = new byte[count];
+            for (int i = 0; i < count; ++i) {
+                bytes[i] = orig[origPos++];
+            }
+            return bytes;
+        }
+
+        void writeBytes(byte[] bytes) {
+            for (int i = 0; i < bytes.length; ++i) {
+                gen[genPos++] = bytes[i];
+            }
+        }
+
+        int copyConstantPool(int constantPoolCount)
+            throws UnsupportedEncodingException, IOException {
+            int sdeIndex = -1;
+            // copy const pool index zero not in class file
+            for (int i = 1; i < constantPoolCount; ++i) {
+                int tag = readU1();
+                writeU1(tag);
+                switch (tag) {
+                    case 7 : // Class
+                    case 8 : // String
+                        if (log.isDebugEnabled())
+                            log.debug(i + " copying 2 bytes");
+                        copy(2);
+                        break;
+                    case 9 : // Field
+                    case 10 : // Method
+                    case 11 : // InterfaceMethod
+                    case 3 : // Integer
+                    case 4 : // Float
+                    case 12 : // NameAndType
+                        if (log.isDebugEnabled())
+                            log.debug(i + " copying 4 bytes");
+                        copy(4);
+                        break;
+                    case 5 : // Long
+                    case 6 : // Double
+                        if (log.isDebugEnabled())
+                            log.debug(i + " copying 8 bytes");
+                        copy(8);
+                        i++;
+                        break;
+                    case 1 : // Utf8
+                        int len = readU2();
+                        writeU2(len);
+                        byte[] utf8 = readBytes(len);
+                        String str = new String(utf8, "UTF-8");
+                        if (log.isDebugEnabled())
+                            log.debug(i + " read class attr -- '" + str + "'");
+                        if (str.equals(nameSDE)) {
+                            sdeIndex = i;
+                        }
+                        writeBytes(utf8);
+                        break;
+                    default :
+                        throw new IOException("unexpected tag: " + tag);
+                }
+            }
+            return sdeIndex;
+        }
+
+        void writeUtf8ForSDE() {
+            int len = nameSDE.length();
+            writeU1(1); // Utf8 tag
+            writeU2(len);
+            for (int i = 0; i < len; ++i) {
+                writeU1(nameSDE.charAt(i));
+            }
+        }
+    }
+
+    public static void evaluateNodes(
+        Node.Nodes nodes,
+        SmapStratum s,
+        HashMap<String, SmapStratum> innerClassMap,
+        boolean breakAtLF) {
+        try {
+            nodes.visit(new SmapGenVisitor(s, breakAtLF, innerClassMap));
+        } catch (JasperException ex) {
+        }
+    }
+
+    static class SmapGenVisitor extends Node.Visitor {
+
+        private SmapStratum smap;
+        private boolean breakAtLF;
+        private HashMap<String, SmapStratum> innerClassMap;
+
+        SmapGenVisitor(SmapStratum s, boolean breakAtLF, HashMap<String, SmapStratum> map) {
+            this.smap = s;
+            this.breakAtLF = breakAtLF;
+            this.innerClassMap = map;
+        }
+
+        @Override
+        public void visitBody(Node n) throws JasperException {
+            SmapStratum smapSave = smap;
+            String innerClass = n.getInnerClassName();
+            if (innerClass != null) {
+                this.smap = innerClassMap.get(innerClass);
+            }
+            super.visitBody(n);
+            smap = smapSave;
+        }
+
+        @Override
+        public void visit(Node.Declaration n) throws JasperException {
+            doSmapText(n);
+        }
+
+        @Override
+        public void visit(Node.Expression n) throws JasperException {
+            doSmapText(n);
+        }
+
+        @Override
+        public void visit(Node.Scriptlet n) throws JasperException {
+            doSmapText(n);
+        }
+
+        @Override
+        public void visit(Node.IncludeAction n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.ForwardAction n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.GetProperty n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.SetProperty n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.UseBean n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.PlugIn n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.CustomTag n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.UninterpretedTag n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.JspElement n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.JspText n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.NamedAttribute n) throws JasperException {
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.JspBody n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.InvokeAction n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.DoBodyAction n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.ELExpression n) throws JasperException {
+            doSmap(n);
+        }
+
+        @Override
+        public void visit(Node.TemplateText n) throws JasperException {
+            Mark mark = n.getStart();
+            if (mark == null) {
+                return;
+            }
+
+            //Add the file information
+            String fileName = mark.getFile();
+            smap.addFile(unqualify(fileName), fileName);
+
+            //Add a LineInfo that corresponds to the beginning of this node
+            int iInputStartLine = mark.getLineNumber();
+            int iOutputStartLine = n.getBeginJavaLine();
+            int iOutputLineIncrement = breakAtLF? 1: 0;
+            smap.addLineData(iInputStartLine, fileName, 1, iOutputStartLine, 
+                             iOutputLineIncrement);
+
+            // Output additional mappings in the text
+            java.util.ArrayList<Integer> extraSmap = n.getExtraSmap();
+
+            if (extraSmap != null) {
+                for (int i = 0; i < extraSmap.size(); i++) {
+                    iOutputStartLine += iOutputLineIncrement;
+                    smap.addLineData(
+                        iInputStartLine+extraSmap.get(i).intValue(),
+                        fileName,
+                        1,
+                        iOutputStartLine,
+                        iOutputLineIncrement);
+                }
+            }
+        }
+
+        private void doSmap(
+            Node n,
+            int inLineCount,
+            int outIncrement,
+            int skippedLines) {
+            Mark mark = n.getStart();
+            if (mark == null) {
+                return;
+            }
+
+            String unqualifiedName = unqualify(mark.getFile());
+            smap.addFile(unqualifiedName, mark.getFile());
+            smap.addLineData(
+                mark.getLineNumber() + skippedLines,
+                mark.getFile(),
+                inLineCount - skippedLines,
+                n.getBeginJavaLine() + skippedLines,
+                outIncrement);
+        }
+
+        private void doSmap(Node n) {
+            doSmap(n, 1, n.getEndJavaLine() - n.getBeginJavaLine(), 0);
+        }
+
+        private void doSmapText(Node n) {
+            String text = n.getText();
+            int index = 0;
+            int next = 0;
+            int lineCount = 1;
+            int skippedLines = 0;
+            boolean slashStarSeen = false;
+            boolean beginning = true;
+
+            // Count lines inside text, but skipping comment lines at the
+            // beginning of the text.
+            while ((next = text.indexOf('\n', index)) > -1) {
+                if (beginning) {
+                    String line = text.substring(index, next).trim();
+                    if (!slashStarSeen && line.startsWith("/*")) {
+                        slashStarSeen = true;
+                    }
+                    if (slashStarSeen) {
+                        skippedLines++;
+                        int endIndex = line.indexOf("*/");
+                        if (endIndex >= 0) {
+                            // End of /* */ comment
+                            slashStarSeen = false;
+                            if (endIndex < line.length() - 2) {
+                                // Some executable code after comment
+                                skippedLines--;
+                                beginning = false;
+                            }
+                        }
+                    } else if (line.length() == 0 || line.startsWith("//")) {
+                        skippedLines++;
+                    } else {
+                        beginning = false;
+                    }
+                }
+                lineCount++;
+                index = next + 1;
+            }
+
+            doSmap(n, lineCount, 1, skippedLines);
+        }
+    }
+
+    private static class PreScanVisitor extends Node.Visitor {
+
+        HashMap<String, SmapStratum> map = new HashMap<String, SmapStratum>();
+
+        @Override
+        public void doVisit(Node n) {
+            String inner = n.getInnerClassName();
+            if (inner != null && !map.containsKey(inner)) {
+                map.put(inner, new SmapStratum("JSP"));
+            }
+        }
+
+        HashMap<String, SmapStratum> getMap() {
+            return map;
+        }
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/TagConstants.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/TagConstants.java
new file mode 100644
index 0000000..373eb0e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/TagConstants.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.compiler;
+
+public interface TagConstants {
+
+    public static final String JSP_URI = "http://java.sun.com/JSP/Page";
+
+    public static final String DIRECTIVE_ACTION = "directive.";
+
+    public static final String ROOT_ACTION = "root";
+    public static final String JSP_ROOT_ACTION = "jsp:root";
+
+    public static final String PAGE_DIRECTIVE_ACTION = "directive.page";
+    public static final String JSP_PAGE_DIRECTIVE_ACTION = "jsp:directive.page";
+
+    public static final String INCLUDE_DIRECTIVE_ACTION = "directive.include";
+    public static final String JSP_INCLUDE_DIRECTIVE_ACTION = "jsp:directive.include";
+
+    public static final String DECLARATION_ACTION = "declaration";
+    public static final String JSP_DECLARATION_ACTION = "jsp:declaration";
+
+    public static final String SCRIPTLET_ACTION = "scriptlet";
+    public static final String JSP_SCRIPTLET_ACTION = "jsp:scriptlet";
+
+    public static final String EXPRESSION_ACTION = "expression";
+    public static final String JSP_EXPRESSION_ACTION = "jsp:expression";
+
+    public static final String USE_BEAN_ACTION = "useBean";
+    public static final String JSP_USE_BEAN_ACTION = "jsp:useBean";
+
+    public static final String SET_PROPERTY_ACTION = "setProperty";
+    public static final String JSP_SET_PROPERTY_ACTION = "jsp:setProperty";
+
+    public static final String GET_PROPERTY_ACTION = "getProperty";
+    public static final String JSP_GET_PROPERTY_ACTION = "jsp:getProperty";
+
+    public static final String INCLUDE_ACTION = "include";
+    public static final String JSP_INCLUDE_ACTION = "jsp:include";
+
+    public static final String FORWARD_ACTION = "forward";
+    public static final String JSP_FORWARD_ACTION = "jsp:forward";
+
+    public static final String PARAM_ACTION = "param";
+    public static final String JSP_PARAM_ACTION = "jsp:param";
+
+    public static final String PARAMS_ACTION = "params";
+    public static final String JSP_PARAMS_ACTION = "jsp:params";
+
+    public static final String PLUGIN_ACTION = "plugin";
+    public static final String JSP_PLUGIN_ACTION = "jsp:plugin";
+
+    public static final String FALLBACK_ACTION = "fallback";
+    public static final String JSP_FALLBACK_ACTION = "jsp:fallback";
+
+    public static final String TEXT_ACTION = "text";
+    public static final String JSP_TEXT_ACTION = "jsp:text";
+    public static final String JSP_TEXT_ACTION_END = "</jsp:text>";
+
+    public static final String ATTRIBUTE_ACTION = "attribute";
+    public static final String JSP_ATTRIBUTE_ACTION = "jsp:attribute";
+
+    public static final String BODY_ACTION = "body";
+    public static final String JSP_BODY_ACTION = "jsp:body";
+
+    public static final String ELEMENT_ACTION = "element";
+    public static final String JSP_ELEMENT_ACTION = "jsp:element";
+
+    public static final String OUTPUT_ACTION = "output";
+    public static final String JSP_OUTPUT_ACTION = "jsp:output";
+
+    public static final String TAGLIB_DIRECTIVE_ACTION = "taglib";
+    public static final String JSP_TAGLIB_DIRECTIVE_ACTION = "jsp:taglib";
+
+    /*
+     * Tag Files
+     */
+    public static final String INVOKE_ACTION = "invoke";
+    public static final String JSP_INVOKE_ACTION = "jsp:invoke";
+
+    public static final String DOBODY_ACTION = "doBody";
+    public static final String JSP_DOBODY_ACTION = "jsp:doBody";
+
+    /*
+     * Tag File Directives
+     */
+    public static final String TAG_DIRECTIVE_ACTION = "directive.tag";
+    public static final String JSP_TAG_DIRECTIVE_ACTION = "jsp:directive.tag";
+
+    public static final String ATTRIBUTE_DIRECTIVE_ACTION = "directive.attribute";
+    public static final String JSP_ATTRIBUTE_DIRECTIVE_ACTION = "jsp:directive.attribute";
+
+    public static final String VARIABLE_DIRECTIVE_ACTION = "directive.variable";
+    public static final String JSP_VARIABLE_DIRECTIVE_ACTION = "jsp:directive.variable";
+
+    /*
+     * Directive attributes
+     */
+    public static final String URN_JSPTAGDIR = "urn:jsptagdir:";
+    public static final String URN_JSPTLD = "urn:jsptld:";
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/TagFileProcessor.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/TagFileProcessor.java
new file mode 100644
index 0000000..bc459b0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/TagFileProcessor.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.jasper.compiler;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Vector;
+
+import javax.el.MethodExpression;
+import javax.el.ValueExpression;
+import javax.servlet.jsp.tagext.TagAttributeInfo;
+import javax.servlet.jsp.tagext.TagExtraInfo;
+import javax.servlet.jsp.tagext.TagFileInfo;
+import javax.servlet.jsp.tagext.TagInfo;
+import javax.servlet.jsp.tagext.TagLibraryInfo;
+import javax.servlet.jsp.tagext.TagVariableInfo;
+import javax.servlet.jsp.tagext.VariableInfo;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.apache.jasper.runtime.JspSourceDependent;
+import org.apache.jasper.servlet.JspServletWrapper;
+
+/**
+ * 1. Processes and extracts the directive info in a tag file. 2. Compiles and
+ * loads tag files used in a JSP file.
+ * 
+ * @author Kin-man Chung
+ */
+
+class TagFileProcessor {
+
+    private Vector<Compiler> tempVector;
+
+    /**
+     * A visitor the tag file
+     */
+    private static class TagFileDirectiveVisitor extends Node.Visitor {
+
+        private static final JspUtil.ValidAttribute[] tagDirectiveAttrs = {
+                new JspUtil.ValidAttribute("display-name"),
+                new JspUtil.ValidAttribute("body-content"),
+                new JspUtil.ValidAttribute("dynamic-attributes"),
+                new JspUtil.ValidAttribute("small-icon"),
+                new JspUtil.ValidAttribute("large-icon"),
+                new JspUtil.ValidAttribute("description"),
+                new JspUtil.ValidAttribute("example"),
+                new JspUtil.ValidAttribute("pageEncoding"),
+                new JspUtil.ValidAttribute("language"),
+                new JspUtil.ValidAttribute("import"),
+                new JspUtil.ValidAttribute("deferredSyntaxAllowedAsLiteral"), // JSP 2.1
+                new JspUtil.ValidAttribute("trimDirectiveWhitespaces"), // JSP 2.1
+                new JspUtil.ValidAttribute("isELIgnored") };
+
+        private static final JspUtil.ValidAttribute[] attributeDirectiveAttrs = {
+                new JspUtil.ValidAttribute("name", true),
+                new JspUtil.ValidAttribute("required"),
+                new JspUtil.ValidAttribute("fragment"),
+                new JspUtil.ValidAttribute("rtexprvalue"),
+                new JspUtil.ValidAttribute("type"),
+                new JspUtil.ValidAttribute("deferredValue"),            // JSP 2.1
+                new JspUtil.ValidAttribute("deferredValueType"),        // JSP 2.1
+                new JspUtil.ValidAttribute("deferredMethod"),           // JSP 2
+                new JspUtil.ValidAttribute("deferredMethodSignature"),  // JSP 21
+                new JspUtil.ValidAttribute("description") };
+
+        private static final JspUtil.ValidAttribute[] variableDirectiveAttrs = {
+                new JspUtil.ValidAttribute("name-given"),
+                new JspUtil.ValidAttribute("name-from-attribute"),
+                new JspUtil.ValidAttribute("alias"),
+                new JspUtil.ValidAttribute("variable-class"),
+                new JspUtil.ValidAttribute("scope"),
+                new JspUtil.ValidAttribute("declare"),
+                new JspUtil.ValidAttribute("description") };
+
+        private ErrorDispatcher err;
+
+        private TagLibraryInfo tagLibInfo;
+
+        private String name = null;
+
+        private String path = null;
+
+        private TagExtraInfo tei = null;
+
+        private String bodycontent = null;
+
+        private String description = null;
+
+        private String displayName = null;
+
+        private String smallIcon = null;
+
+        private String largeIcon = null;
+
+        private String dynamicAttrsMapName;
+
+        private String example = null;
+
+        private Vector<TagAttributeInfo> attributeVector;
+
+        private Vector<TagVariableInfo> variableVector;
+
+        private static final String ATTR_NAME = "the name attribute of the attribute directive";
+
+        private static final String VAR_NAME_GIVEN = "the name-given attribute of the variable directive";
+
+        private static final String VAR_NAME_FROM = "the name-from-attribute attribute of the variable directive";
+
+        private static final String VAR_ALIAS = "the alias attribute of the variable directive";
+
+        private static final String TAG_DYNAMIC = "the dynamic-attributes attribute of the tag directive";
+
+        private HashMap<String,NameEntry> nameTable =
+            new HashMap<String,NameEntry>();
+
+        private HashMap<String,NameEntry> nameFromTable =
+            new HashMap<String,NameEntry>();
+
+        public TagFileDirectiveVisitor(Compiler compiler,
+                TagLibraryInfo tagLibInfo, String name, String path) {
+            err = compiler.getErrorDispatcher();
+            this.tagLibInfo = tagLibInfo;
+            this.name = name;
+            this.path = path;
+            attributeVector = new Vector<TagAttributeInfo>();
+            variableVector = new Vector<TagVariableInfo>();
+        }
+
+        @Override
+        public void visit(Node.TagDirective n) throws JasperException {
+
+            JspUtil.checkAttributes("Tag directive", n, tagDirectiveAttrs, err);
+
+            bodycontent = checkConflict(n, bodycontent, "body-content");
+            if (bodycontent != null
+                    && !bodycontent
+                            .equalsIgnoreCase(TagInfo.BODY_CONTENT_EMPTY)
+                    && !bodycontent
+                            .equalsIgnoreCase(TagInfo.BODY_CONTENT_TAG_DEPENDENT)
+                    && !bodycontent
+                            .equalsIgnoreCase(TagInfo.BODY_CONTENT_SCRIPTLESS)) {
+                err.jspError(n, "jsp.error.tagdirective.badbodycontent",
+                        bodycontent);
+            }
+            dynamicAttrsMapName = checkConflict(n, dynamicAttrsMapName,
+                    "dynamic-attributes");
+            if (dynamicAttrsMapName != null) {
+                checkUniqueName(dynamicAttrsMapName, TAG_DYNAMIC, n);
+            }
+            smallIcon = checkConflict(n, smallIcon, "small-icon");
+            largeIcon = checkConflict(n, largeIcon, "large-icon");
+            description = checkConflict(n, description, "description");
+            displayName = checkConflict(n, displayName, "display-name");
+            example = checkConflict(n, example, "example");
+        }
+
+        private String checkConflict(Node n, String oldAttrValue, String attr)
+                throws JasperException {
+
+            String result = oldAttrValue;
+            String attrValue = n.getAttributeValue(attr);
+            if (attrValue != null) {
+                if (oldAttrValue != null && !oldAttrValue.equals(attrValue)) {
+                    err.jspError(n, "jsp.error.tag.conflict.attr", attr,
+                            oldAttrValue, attrValue);
+                }
+                result = attrValue;
+            }
+            return result;
+        }
+
+        @Override
+        public void visit(Node.AttributeDirective n) throws JasperException {
+
+            JspUtil.checkAttributes("Attribute directive", n,
+                    attributeDirectiveAttrs, err);
+
+            // JSP 2.1 Table JSP.8-3
+            // handle deferredValue and deferredValueType
+            boolean deferredValue = false;
+            boolean deferredValueSpecified = false;
+            String deferredValueString = n.getAttributeValue("deferredValue");
+            if (deferredValueString != null) {
+                deferredValueSpecified = true;
+                deferredValue = JspUtil.booleanValue(deferredValueString);
+            }
+            String deferredValueType = n.getAttributeValue("deferredValueType");
+            if (deferredValueType != null) {
+                if (deferredValueSpecified && !deferredValue) {
+                    err.jspError(n, "jsp.error.deferredvaluetypewithoutdeferredvalue");
+                } else {
+                    deferredValue = true;
+                }
+            } else if (deferredValue) {
+                deferredValueType = "java.lang.Object";
+            } else {
+                deferredValueType = "java.lang.String";
+            }
+
+            // JSP 2.1 Table JSP.8-3
+            // handle deferredMethod and deferredMethodSignature
+            boolean deferredMethod = false;
+            boolean deferredMethodSpecified = false;
+            String deferredMethodString = n.getAttributeValue("deferredMethod");
+            if (deferredMethodString != null) {
+                deferredMethodSpecified = true;
+                deferredMethod = JspUtil.booleanValue(deferredMethodString);
+            }
+            String deferredMethodSignature = n
+                    .getAttributeValue("deferredMethodSignature");
+            if (deferredMethodSignature != null) {
+                if (deferredMethodSpecified && !deferredMethod) {
+                    err.jspError(n, "jsp.error.deferredmethodsignaturewithoutdeferredmethod");
+                } else {
+                    deferredMethod = true;
+                }
+            } else if (deferredMethod) {
+                deferredMethodSignature = "void methodname()";
+            }
+
+            if (deferredMethod && deferredValue) {
+                err.jspError(n, "jsp.error.deferredmethodandvalue");
+            }
+            
+            String attrName = n.getAttributeValue("name");
+            boolean required = JspUtil.booleanValue(n
+                    .getAttributeValue("required"));
+            boolean rtexprvalue = true;
+            String rtexprvalueString = n.getAttributeValue("rtexprvalue");
+            if (rtexprvalueString != null) {
+                rtexprvalue = JspUtil.booleanValue(rtexprvalueString);
+            }
+            boolean fragment = JspUtil.booleanValue(n
+                    .getAttributeValue("fragment"));
+            String type = n.getAttributeValue("type");
+            if (fragment) {
+                // type is fixed to "JspFragment" and a translation error
+                // must occur if specified.
+                if (type != null) {
+                    err.jspError(n, "jsp.error.fragmentwithtype");
+                }
+                // rtexprvalue is fixed to "true" and a translation error
+                // must occur if specified.
+                rtexprvalue = true;
+                if (rtexprvalueString != null) {
+                    err.jspError(n, "jsp.error.frgmentwithrtexprvalue");
+                }
+            } else {
+                if (type == null)
+                    type = "java.lang.String";
+                
+                if (deferredValue) {
+                    type = ValueExpression.class.getName();
+                } else if (deferredMethod) {
+                    type = MethodExpression.class.getName();
+                }
+            }
+
+            if (("2.0".equals(tagLibInfo.getRequiredVersion()) || ("1.2".equals(tagLibInfo.getRequiredVersion())))
+                    && (deferredMethodSpecified || deferredMethod
+                            || deferredValueSpecified || deferredValue)) {
+                err.jspError("jsp.error.invalid.version", path);
+            }
+            
+            TagAttributeInfo tagAttributeInfo = new TagAttributeInfo(attrName,
+                    required, type, rtexprvalue, fragment, null, deferredValue,
+                    deferredMethod, deferredValueType, deferredMethodSignature);
+            attributeVector.addElement(tagAttributeInfo);
+            checkUniqueName(attrName, ATTR_NAME, n, tagAttributeInfo);
+        }
+
+        @Override
+        public void visit(Node.VariableDirective n) throws JasperException {
+
+            JspUtil.checkAttributes("Variable directive", n,
+                    variableDirectiveAttrs, err);
+
+            String nameGiven = n.getAttributeValue("name-given");
+            String nameFromAttribute = n
+                    .getAttributeValue("name-from-attribute");
+            if (nameGiven == null && nameFromAttribute == null) {
+                err.jspError("jsp.error.variable.either.name");
+            }
+
+            if (nameGiven != null && nameFromAttribute != null) {
+                err.jspError("jsp.error.variable.both.name");
+            }
+
+            String alias = n.getAttributeValue("alias");
+            if (nameFromAttribute != null && alias == null
+                    || nameFromAttribute == null && alias != null) {
+                err.jspError("jsp.error.variable.alias");
+            }
+
+            String className = n.getAttributeValue("variable-class");
+            if (className == null)
+                className = "java.lang.String";
+
+            String declareStr = n.getAttributeValue("declare");
+            boolean declare = true;
+            if (declareStr != null)
+                declare = JspUtil.booleanValue(declareStr);
+
+            int scope = VariableInfo.NESTED;
+            String scopeStr = n.getAttributeValue("scope");
+            if (scopeStr != null) {
+                if ("NESTED".equals(scopeStr)) {
+                    // Already the default
+                } else if ("AT_BEGIN".equals(scopeStr)) {
+                    scope = VariableInfo.AT_BEGIN;
+                } else if ("AT_END".equals(scopeStr)) {
+                    scope = VariableInfo.AT_END;
+                }
+            }
+
+            if (nameFromAttribute != null) {
+                /*
+                 * An alias has been specified. We use 'nameGiven' to hold the
+                 * value of the alias, and 'nameFromAttribute' to hold the name
+                 * of the attribute whose value (at invocation-time) denotes the
+                 * name of the variable that is being aliased
+                 */
+                nameGiven = alias;
+                checkUniqueName(nameFromAttribute, VAR_NAME_FROM, n);
+                checkUniqueName(alias, VAR_ALIAS, n);
+            } else {
+                // name-given specified
+                checkUniqueName(nameGiven, VAR_NAME_GIVEN, n);
+            }
+
+            variableVector.addElement(new TagVariableInfo(nameGiven,
+                    nameFromAttribute, className, declare, scope));
+        }
+
+        public TagInfo getTagInfo() throws JasperException {
+
+            if (name == null) {
+                // XXX Get it from tag file name
+            }
+
+            if (bodycontent == null) {
+                bodycontent = TagInfo.BODY_CONTENT_SCRIPTLESS;
+            }
+
+            String tagClassName = JspUtil.getTagHandlerClassName(
+                    path, tagLibInfo.getReliableURN(), err);
+
+            TagVariableInfo[] tagVariableInfos = new TagVariableInfo[variableVector
+                    .size()];
+            variableVector.copyInto(tagVariableInfos);
+
+            TagAttributeInfo[] tagAttributeInfo = new TagAttributeInfo[attributeVector
+                    .size()];
+            attributeVector.copyInto(tagAttributeInfo);
+
+            return new JasperTagInfo(name, tagClassName, bodycontent,
+                    description, tagLibInfo, tei, tagAttributeInfo,
+                    displayName, smallIcon, largeIcon, tagVariableInfos,
+                    dynamicAttrsMapName);
+        }
+
+        static class NameEntry {
+            private String type;
+
+            private Node node;
+
+            private TagAttributeInfo attr;
+
+            NameEntry(String type, Node node, TagAttributeInfo attr) {
+                this.type = type;
+                this.node = node;
+                this.attr = attr;
+            }
+
+            String getType() {
+                return type;
+            }
+
+            Node getNode() {
+                return node;
+            }
+
+            TagAttributeInfo getTagAttributeInfo() {
+                return attr;
+            }
+        }
+
+        /**
+         * Reports a translation error if names specified in attributes of
+         * directives are not unique in this translation unit.
+         * 
+         * The value of the following attributes must be unique. 1. 'name'
+         * attribute of an attribute directive 2. 'name-given' attribute of a
+         * variable directive 3. 'alias' attribute of variable directive 4.
+         * 'dynamic-attributes' of a tag directive except that
+         * 'dynamic-attributes' can (and must) have the same value when it
+         * appears in multiple tag directives.
+         * 
+         * Also, 'name-from' attribute of a variable directive cannot have the
+         * same value as that from another variable directive.
+         */
+        private void checkUniqueName(String name, String type, Node n)
+                throws JasperException {
+            checkUniqueName(name, type, n, null);
+        }
+
+        private void checkUniqueName(String name, String type, Node n,
+                TagAttributeInfo attr) throws JasperException {
+
+            HashMap<String, NameEntry> table = (type == VAR_NAME_FROM) ? nameFromTable : nameTable;
+            NameEntry nameEntry = table.get(name);
+            if (nameEntry != null) {
+                if (!TAG_DYNAMIC.equals(type) ||
+                        !TAG_DYNAMIC.equals(nameEntry.getType())) {
+                    int line = nameEntry.getNode().getStart().getLineNumber();
+                    err.jspError(n, "jsp.error.tagfile.nameNotUnique", type,
+                            nameEntry.getType(), Integer.toString(line));
+                }
+            } else {
+                table.put(name, new NameEntry(type, n, attr));
+            }
+        }
+
+        /**
+         * Perform miscellaneous checks after the nodes are visited.
+         */
+        void postCheck() throws JasperException {
+            // Check that var.name-from-attributes has valid values.
+            Iterator<String> iter = nameFromTable.keySet().iterator();
+            while (iter.hasNext()) {
+                String nameFrom = iter.next();
+                NameEntry nameEntry = nameTable.get(nameFrom);
+                NameEntry nameFromEntry = nameFromTable.get(nameFrom);
+                Node nameFromNode = nameFromEntry.getNode();
+                if (nameEntry == null) {
+                    err.jspError(nameFromNode,
+                            "jsp.error.tagfile.nameFrom.noAttribute", nameFrom);
+                } else {
+                    Node node = nameEntry.getNode();
+                    TagAttributeInfo tagAttr = nameEntry.getTagAttributeInfo();
+                    if (!"java.lang.String".equals(tagAttr.getTypeName())
+                            || !tagAttr.isRequired()
+                            || tagAttr.canBeRequestTime()) {
+                        err.jspError(nameFromNode,
+                                "jsp.error.tagfile.nameFrom.badAttribute",
+                                nameFrom, Integer.toString(node.getStart()
+                                        .getLineNumber()));
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Parses the tag file, and collects information on the directives included
+     * in it. The method is used to obtain the info on the tag file, when the
+     * handler that it represents is referenced. The tag file is not compiled
+     * here.
+     * 
+     * @param pc
+     *            the current ParserController used in this compilation
+     * @param name
+     *            the tag name as specified in the TLD
+     * @param path
+     *            the path for the tagfile
+     * @param jarResource
+     *            the Jar resource containing the tag file 
+     * @param tagLibInfo
+     *            the TagLibraryInfo object associated with this TagInfo
+     * @return a TagInfo object assembled from the directives in the tag file.
+     */
+    public static TagInfo parseTagFileDirectives(ParserController pc,
+            String name, String path, JarResource jarResource, TagLibraryInfo tagLibInfo)
+            throws JasperException {
+
+
+        ErrorDispatcher err = pc.getCompiler().getErrorDispatcher();
+
+        Node.Nodes page = null;
+        try {
+            page = pc.parseTagFileDirectives(path, jarResource);
+        } catch (FileNotFoundException e) {
+            err.jspError("jsp.error.file.not.found", path);
+        } catch (IOException e) {
+            err.jspError("jsp.error.file.not.found", path);
+        }
+
+        TagFileDirectiveVisitor tagFileVisitor = new TagFileDirectiveVisitor(pc
+                .getCompiler(), tagLibInfo, name, path);
+        page.visit(tagFileVisitor);
+        tagFileVisitor.postCheck();
+
+        return tagFileVisitor.getTagInfo();
+    }
+
+    /**
+     * Compiles and loads a tagfile.
+     */
+    private Class<?> loadTagFile(Compiler compiler, String tagFilePath,
+            TagInfo tagInfo, PageInfo parentPageInfo) throws JasperException {
+
+        JarResource tagJarResouce = null;
+        if (tagFilePath.startsWith("/META-INF/")) {
+            tagJarResouce = 
+                compiler.getCompilationContext().getTldLocation(
+                        tagInfo.getTagLibrary().getURI()).getJarResource();
+        }
+        String wrapperUri;
+        if (tagJarResouce == null) {
+            wrapperUri = tagFilePath;
+        } else {
+            wrapperUri = tagJarResouce.getEntry(tagFilePath).toString();
+        }
+
+        JspCompilationContext ctxt = compiler.getCompilationContext();
+        JspRuntimeContext rctxt = ctxt.getRuntimeContext();
+        JspServletWrapper wrapper = rctxt.getWrapper(wrapperUri);
+
+        synchronized (rctxt) {
+            if (wrapper == null) {
+                wrapper = new JspServletWrapper(ctxt.getServletContext(), ctxt
+                        .getOptions(), tagFilePath, tagInfo, ctxt
+                        .getRuntimeContext(), tagJarResouce);
+                rctxt.addWrapper(wrapperUri, wrapper);
+
+                // Use same classloader and classpath for compiling tag files
+                wrapper.getJspEngineContext().setClassLoader(
+                        ctxt.getClassLoader());
+                wrapper.getJspEngineContext().setClassPath(ctxt.getClassPath());
+            } else {
+                // Make sure that JspCompilationContext gets the latest TagInfo
+                // for the tag file. TagInfo instance was created the last
+                // time the tag file was scanned for directives, and the tag
+                // file may have been modified since then.
+                wrapper.getJspEngineContext().setTagInfo(tagInfo);
+            }
+
+            Class<?> tagClazz;
+            int tripCount = wrapper.incTripCount();
+            try {
+                if (tripCount > 0) {
+                    // When tripCount is greater than zero, a circular
+                    // dependency exists. The circularly dependent tag
+                    // file is compiled in prototype mode, to avoid infinite
+                    // recursion.
+
+                    JspServletWrapper tempWrapper = new JspServletWrapper(ctxt
+                            .getServletContext(), ctxt.getOptions(),
+                            tagFilePath, tagInfo, ctxt.getRuntimeContext(),
+                            ctxt.getTagFileJarResource(tagFilePath));
+                    // Use same classloader and classpath for compiling tag files
+                    tempWrapper.getJspEngineContext().setClassLoader(
+                            ctxt.getClassLoader());
+                    tempWrapper.getJspEngineContext().setClassPath(ctxt.getClassPath());
+                    tagClazz = tempWrapper.loadTagFilePrototype();
+                    tempVector.add(tempWrapper.getJspEngineContext()
+                            .getCompiler());
+                } else {
+                    tagClazz = wrapper.loadTagFile();
+                }
+            } finally {
+                wrapper.decTripCount();
+            }
+
+            // Add the dependents for this tag file to its parent's
+            // Dependent list. The only reliable dependency information
+            // can only be obtained from the tag instance.
+            try {
+                Object tagIns = tagClazz.newInstance();
+                if (tagIns instanceof JspSourceDependent) {
+                    Iterator<String> iter = ((JspSourceDependent) tagIns)
+                            .getDependants().iterator();
+                    while (iter.hasNext()) {
+                        parentPageInfo.addDependant(iter.next());
+                    }
+                }
+            } catch (Exception e) {
+                // ignore errors
+            }
+
+            return tagClazz;
+        }
+    }
+
+    /*
+     * Visitor which scans the page and looks for tag handlers that are tag
+     * files, compiling (if necessary) and loading them.
+     */
+    private class TagFileLoaderVisitor extends Node.Visitor {
+
+        private Compiler compiler;
+
+        private PageInfo pageInfo;
+
+        TagFileLoaderVisitor(Compiler compiler) {
+
+            this.compiler = compiler;
+            this.pageInfo = compiler.getPageInfo();
+        }
+
+        @Override
+        public void visit(Node.CustomTag n) throws JasperException {
+            TagFileInfo tagFileInfo = n.getTagFileInfo();
+            if (tagFileInfo != null) {
+                String tagFilePath = tagFileInfo.getPath();
+                if (tagFilePath.startsWith("/META-INF/")) {
+                    // For tags in JARs, add the TLD and the tag as a dependency
+                    TldLocation location =
+                        compiler.getCompilationContext().getTldLocation(
+                            tagFileInfo.getTagInfo().getTagLibrary().getURI());
+                    JarResource jarResource = location.getJarResource();
+                    if (jarResource != null) {
+                        // Add TLD
+                        pageInfo.addDependant(jarResource.getEntry(location.getName()).toString());
+                        // Add Tag
+                        pageInfo.addDependant(jarResource.getEntry(tagFilePath.substring(1)).toString());
+                    }
+                    else {
+                        pageInfo.addDependant(tagFilePath);
+                    }
+                } else {
+                    pageInfo.addDependant(tagFilePath);
+                }
+                Class<?> c = loadTagFile(compiler, tagFilePath, n.getTagInfo(),
+                        pageInfo);
+                n.setTagHandlerClass(c);
+            }
+            visitBody(n);
+        }
+    }
+
+    /**
+     * Implements a phase of the translation that compiles (if necessary) the
+     * tag files used in a JSP files. The directives in the tag files are
+     * assumed to have been processed and encapsulated as TagFileInfo in the
+     * CustomTag nodes.
+     */
+    public void loadTagFiles(Compiler compiler, Node.Nodes page)
+            throws JasperException {
+
+        tempVector = new Vector<Compiler>();
+        page.visit(new TagFileLoaderVisitor(compiler));
+    }
+
+    /**
+     * Removed the java and class files for the tag prototype generated from the
+     * current compilation.
+     * 
+     * @param classFileName
+     *            If non-null, remove only the class file with with this name.
+     */
+    public void removeProtoTypeFiles(String classFileName) {
+        Iterator<Compiler> iter = tempVector.iterator();
+        while (iter.hasNext()) {
+            Compiler c = iter.next();
+            if (classFileName == null) {
+                c.removeGeneratedClassFiles();
+            } else if (classFileName.equals(c.getCompilationContext()
+                    .getClassFileName())) {
+                c.removeGeneratedClassFiles();
+                tempVector.remove(c);
+                return;
+            }
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/TagLibraryInfoImpl.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/TagLibraryInfoImpl.java
new file mode 100644
index 0000000..da8fdd3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/TagLibraryInfoImpl.java
@@ -0,0 +1,760 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Vector;
+
+import javax.servlet.jsp.tagext.FunctionInfo;
+import javax.servlet.jsp.tagext.PageData;
+import javax.servlet.jsp.tagext.TagAttributeInfo;
+import javax.servlet.jsp.tagext.TagExtraInfo;
+import javax.servlet.jsp.tagext.TagFileInfo;
+import javax.servlet.jsp.tagext.TagInfo;
+import javax.servlet.jsp.tagext.TagLibraryInfo;
+import javax.servlet.jsp.tagext.TagLibraryValidator;
+import javax.servlet.jsp.tagext.TagVariableInfo;
+import javax.servlet.jsp.tagext.ValidationMessage;
+import javax.servlet.jsp.tagext.VariableInfo;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.apache.jasper.util.ExceptionUtils;
+import org.apache.jasper.xmlparser.ParserUtils;
+import org.apache.jasper.xmlparser.TreeNode;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/**
+ * Implementation of the TagLibraryInfo class from the JSP spec.
+ * 
+ * @author Anil K. Vijendran
+ * @author Mandar Raje
+ * @author Pierre Delisle
+ * @author Kin-man Chung
+ * @author Jan Luehe
+ */
+class TagLibraryInfoImpl extends TagLibraryInfo implements TagConstants {
+
+    // Logger
+    private final Log log = LogFactory.getLog(TagLibraryInfoImpl.class);
+
+    private JspCompilationContext ctxt;
+    
+    private PageInfo pi;
+
+    private ErrorDispatcher err;
+
+    private ParserController parserController;
+
+    private final void print(String name, String value, PrintWriter w) {
+        if (value != null) {
+            w.print(name + " = {\n\t");
+            w.print(value);
+            w.print("\n}\n");
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringWriter sw = new StringWriter();
+        PrintWriter out = new PrintWriter(sw);
+        print("tlibversion", tlibversion, out);
+        print("jspversion", jspversion, out);
+        print("shortname", shortname, out);
+        print("urn", urn, out);
+        print("info", info, out);
+        print("uri", uri, out);
+        print("tagLibraryValidator", "" + tagLibraryValidator, out);
+
+        for (int i = 0; i < tags.length; i++)
+            out.println(tags[i].toString());
+
+        for (int i = 0; i < tagFiles.length; i++)
+            out.println(tagFiles[i].toString());
+
+        for (int i = 0; i < functions.length; i++)
+            out.println(functions[i].toString());
+
+        return sw.toString();
+    }
+
+    // XXX FIXME
+    // resolveRelativeUri and/or getResourceAsStream don't seem to properly
+    // handle relative paths when dealing when home and getDocBase are set
+    // the following is a workaround until these problems are resolved.
+    private InputStream getResourceAsStream(String uri)
+            throws FileNotFoundException {
+        // Is uri absolute?
+        if (uri.startsWith("file:")) {
+            return new FileInputStream(new File(uri.substring(5)));
+        } else {
+            try {
+                // see if file exists on the filesystem
+                String real = ctxt.getRealPath(uri);
+                if (real == null) {
+                    return ctxt.getResourceAsStream(uri);
+                } else {
+                    return new FileInputStream(real);
+                }
+            } catch (FileNotFoundException ex) {
+                // if file not found on filesystem, get the resource through
+                // the context
+                return ctxt.getResourceAsStream(uri);
+            }
+        }
+    }
+
+    /**
+     * Constructor.
+     */
+    public TagLibraryInfoImpl(JspCompilationContext ctxt, ParserController pc,
+            PageInfo pi, String prefix, String uriIn, TldLocation location,
+            ErrorDispatcher err, Mark mark)
+            throws JasperException {
+        super(prefix, uriIn);
+
+        this.ctxt = ctxt;
+        this.parserController = pc;
+        this.pi = pi;
+        this.err = err;
+        InputStream in = null;
+
+        if (location == null) {
+            // The URI points to the TLD itself or to a JAR file in which the
+            // TLD is stored
+            location = generateTLDLocation(uri, ctxt);
+        }
+
+        String tldName = location.getName();
+        JarResource jarResource = location.getJarResource();
+        try {
+            if (jarResource == null) {
+                // Location points directly to TLD file
+                try {
+                    in = getResourceAsStream(tldName);
+                    if (in == null) {
+                        throw new FileNotFoundException(tldName);
+                    }
+                } catch (FileNotFoundException ex) {
+                    err.jspError(mark, "jsp.error.file.not.found", tldName);
+                }
+
+                parseTLD(tldName, in, null);
+                // Add TLD to dependency list
+                PageInfo pageInfo = ctxt.createCompiler().getPageInfo();
+                if (pageInfo != null) {
+                    pageInfo.addDependant(tldName);
+                }
+            } else {
+                // Tag library is packaged in JAR file
+                try {
+                    in = jarResource.getEntry(tldName).openStream();
+                    parseTLD(jarResource.getUrl(), in, jarResource);
+                } catch (Exception ex) {
+                    err.jspError(mark, "jsp.error.tld.unable_to_read", jarResource.getUrl(),
+                            tldName, ex.toString());
+                }
+            }
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                }
+            }
+        }
+
+    }
+
+    @Override
+    public TagLibraryInfo[] getTagLibraryInfos() {
+        Collection<TagLibraryInfo> coll = pi.getTaglibs();
+        return coll.toArray(new TagLibraryInfo[0]);
+    }
+    
+    /*
+     * @param ctxt The JSP compilation context @param uri The TLD's uri @param
+     * in The TLD's input stream @param jarFileUrl The JAR file containing the
+     * TLD, or null if the tag library is not packaged in a JAR
+     */
+    private void parseTLD(String uri, InputStream in, JarResource jarResource)
+            throws JasperException {
+        Vector<TagInfo> tagVector = new Vector<TagInfo>();
+        Vector<TagFileInfo> tagFileVector = new Vector<TagFileInfo>();
+        Hashtable<String, FunctionInfo> functionTable = new Hashtable<String, FunctionInfo>();
+
+        // Create an iterator over the child elements of our <taglib> element
+        ParserUtils pu = new ParserUtils();
+        TreeNode tld = pu.parseXMLDocument(uri, in);
+
+        // Check to see if the <taglib> root element contains a 'version'
+        // attribute, which was added in JSP 2.0 to replace the <jsp-version>
+        // subelement
+        this.jspversion = tld.findAttribute("version");
+
+        // Process each child element of our <taglib> element
+        Iterator<TreeNode> list = tld.findChildren();
+
+        while (list.hasNext()) {
+            TreeNode element = list.next();
+            String tname = element.getName();
+
+            if ("tlibversion".equals(tname) // JSP 1.1
+                    || "tlib-version".equals(tname)) { // JSP 1.2
+                this.tlibversion = element.getBody();
+            } else if ("jspversion".equals(tname)
+                    || "jsp-version".equals(tname)) {
+                this.jspversion = element.getBody();
+            } else if ("shortname".equals(tname) || "short-name".equals(tname))
+                this.shortname = element.getBody();
+            else if ("uri".equals(tname))
+                this.urn = element.getBody();
+            else if ("info".equals(tname) || "description".equals(tname))
+                this.info = element.getBody();
+            else if ("validator".equals(tname))
+                this.tagLibraryValidator = createValidator(element);
+            else if ("tag".equals(tname))
+                tagVector.addElement(createTagInfo(element, jspversion));
+            else if ("tag-file".equals(tname)) {
+                TagFileInfo tagFileInfo = createTagFileInfo(element,
+                        jarResource);
+                tagFileVector.addElement(tagFileInfo);
+            } else if ("function".equals(tname)) { // JSP2.0
+                FunctionInfo funcInfo = createFunctionInfo(element);
+                String funcName = funcInfo.getName();
+                if (functionTable.containsKey(funcName)) {
+                    err.jspError("jsp.error.tld.fn.duplicate.name", funcName,
+                            uri);
+
+                }
+                functionTable.put(funcName, funcInfo);
+            } else if ("display-name".equals(tname) ||
+                    "small-icon".equals(tname) || "large-icon".equals(tname)
+                    || "listener".equals(tname)) {
+                // Ignored elements
+            } else if ("taglib-extension".equals(tname)) {
+                // Recognized but ignored
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage(
+                            "jsp.warning.unknown.element.in.taglib", tname));
+                }
+            }
+
+        }
+
+        if (tlibversion == null) {
+            err.jspError("jsp.error.tld.mandatory.element.missing",
+                    "tlib-version", uri);
+        }
+        if (jspversion == null) {
+            err.jspError("jsp.error.tld.mandatory.element.missing",
+                    "jsp-version", uri);
+        }
+
+        this.tags = new TagInfo[tagVector.size()];
+        tagVector.copyInto(this.tags);
+
+        this.tagFiles = new TagFileInfo[tagFileVector.size()];
+        tagFileVector.copyInto(this.tagFiles);
+
+        this.functions = new FunctionInfo[functionTable.size()];
+        int i = 0;
+        Enumeration<FunctionInfo> enumeration = functionTable.elements();
+        while (enumeration.hasMoreElements()) {
+            this.functions[i++] = enumeration.nextElement();
+        }
+    }
+
+    /*
+     * @param uri The uri of the TLD @param ctxt The compilation context
+     * 
+     * @return String array whose first element denotes the 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, which is hardcoded to
+     * META-INF/taglib.tld.
+     */
+    private TldLocation generateTLDLocation(String uri, JspCompilationContext ctxt)
+            throws JasperException {
+
+        int uriType = TldLocationsCache.uriType(uri);
+        if (uriType == TldLocationsCache.ABS_URI) {
+            err.jspError("jsp.error.taglibDirective.absUriCannotBeResolved",
+                    uri);
+        } else if (uriType == TldLocationsCache.NOROOT_REL_URI) {
+            uri = ctxt.resolveRelativeUri(uri);
+        }
+
+        if (uri.endsWith(".jar")) {
+            URL url = null;
+            try {
+                url = ctxt.getResource(uri);
+            } catch (Exception ex) {
+                err.jspError("jsp.error.tld.unable_to_get_jar", uri, ex
+                        .toString());
+            }
+            if (url == null) {
+                err.jspError("jsp.error.tld.missing_jar", uri);
+            }
+            return new TldLocation("META-INF/taglib.tld", url.toString());
+        } else {
+            return new TldLocation(uri);
+        }
+    }
+
+    private TagInfo createTagInfo(TreeNode elem, String jspVersion)
+            throws JasperException {
+
+        String tagName = null;
+        String tagClassName = null;
+        String teiClassName = null;
+
+        /*
+         * Default body content for JSP 1.2 tag handlers (<body-content> has
+         * become mandatory in JSP 2.0, because the default would be invalid for
+         * simple tag handlers)
+         */
+        String bodycontent = "JSP";
+
+        String info = null;
+        String displayName = null;
+        String smallIcon = null;
+        String largeIcon = null;
+        boolean dynamicAttributes = false;
+
+        Vector<TagAttributeInfo> attributeVector = new Vector<TagAttributeInfo>();
+        Vector<TagVariableInfo> variableVector = new Vector<TagVariableInfo>();
+        Iterator<TreeNode> list = elem.findChildren();
+        while (list.hasNext()) {
+            TreeNode element = list.next();
+            String tname = element.getName();
+
+            if ("name".equals(tname)) {
+                tagName = element.getBody();
+            } else if ("tagclass".equals(tname) || "tag-class".equals(tname)) {
+                tagClassName = element.getBody();
+            } else if ("teiclass".equals(tname) || "tei-class".equals(tname)) {
+                teiClassName = element.getBody();
+            } else if ("bodycontent".equals(tname)
+                    || "body-content".equals(tname)) {
+                bodycontent = element.getBody();
+            } else if ("display-name".equals(tname)) {
+                displayName = element.getBody();
+            } else if ("small-icon".equals(tname)) {
+                smallIcon = element.getBody();
+            } else if ("large-icon".equals(tname)) {
+                largeIcon = element.getBody();
+            } else if ("icon".equals(tname)) {
+                TreeNode icon = element.findChild("small-icon");
+                if (icon != null) {
+                    smallIcon = icon.getBody();
+                }
+                icon = element.findChild("large-icon");
+                if (icon != null) {
+                    largeIcon = icon.getBody();
+                }
+            } else if ("info".equals(tname) || "description".equals(tname)) {
+                info = element.getBody();
+            } else if ("variable".equals(tname)) {
+                variableVector.addElement(createVariable(element));
+            } else if ("attribute".equals(tname)) {
+                attributeVector
+                        .addElement(createAttribute(element, jspVersion));
+            } else if ("dynamic-attributes".equals(tname)) {
+                dynamicAttributes = JspUtil.booleanValue(element.getBody());
+            } else if ("example".equals(tname)) {
+                // Ignored elements
+            } else if ("tag-extension".equals(tname)) {
+                // Ignored
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage(
+                            "jsp.warning.unknown.element.in.tag", tname));
+                }
+            }
+        }
+
+        TagExtraInfo tei = null;
+        if (teiClassName != null && !teiClassName.equals("")) {
+            try {
+                Class<?> teiClass =
+                    ctxt.getClassLoader().loadClass(teiClassName);
+                tei = (TagExtraInfo) teiClass.newInstance();
+            } catch (Exception e) {
+                err.jspError("jsp.error.teiclass.instantiation", teiClassName,
+                        e);
+            }
+        }
+
+        TagAttributeInfo[] tagAttributeInfo = new TagAttributeInfo[attributeVector
+                .size()];
+        attributeVector.copyInto(tagAttributeInfo);
+
+        TagVariableInfo[] tagVariableInfos = new TagVariableInfo[variableVector
+                .size()];
+        variableVector.copyInto(tagVariableInfos);
+
+        TagInfo taginfo = new TagInfo(tagName, tagClassName, bodycontent, info,
+                this, tei, tagAttributeInfo, displayName, smallIcon, largeIcon,
+                tagVariableInfos, dynamicAttributes);
+        return taginfo;
+    }
+
+    /*
+     * Parses the tag file directives of the given TagFile and turns them into a
+     * TagInfo.
+     * 
+     * @param elem The <tag-file> element in the TLD @param uri The location of
+     * the TLD, in case the tag file is specified relative to it @param jarFile
+     * The JAR file, in case the tag file is packaged in a JAR
+     * 
+     * @return TagInfo corresponding to tag file directives
+     */
+    private TagFileInfo createTagFileInfo(TreeNode elem, JarResource jarResource)
+            throws JasperException {
+
+        String name = null;
+        String path = null;
+
+        Iterator<TreeNode> list = elem.findChildren();
+        while (list.hasNext()) {
+            TreeNode child = list.next();
+            String tname = child.getName();
+            if ("name".equals(tname)) {
+                name = child.getBody();
+            } else if ("path".equals(tname)) {
+                path = child.getBody();
+            } else if ("example".equals(tname)) {
+                // Ignore <example> element: Bugzilla 33538
+            } else if ("tag-extension".equals(tname)) {
+                // Ignore <tag-extension> element: Bugzilla 33538
+            } else if ("icon".equals(tname) 
+                    || "display-name".equals(tname) 
+                    || "description".equals(tname)) {
+                // Ignore these elements: Bugzilla 38015
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage(
+                            "jsp.warning.unknown.element.in.tagfile", tname));
+                }
+            }
+        }
+
+        if (path.startsWith("/META-INF/tags")) {
+            // Tag file packaged in JAR
+            // See https://issues.apache.org/bugzilla/show_bug.cgi?id=46471
+            // This needs to be removed once all the broken code that depends on
+            // it has been removed
+            ctxt.setTagFileJarResource(path, jarResource);
+        } else if (!path.startsWith("/WEB-INF/tags")) {
+            err.jspError("jsp.error.tagfile.illegalPath", path);
+        }
+
+        TagInfo tagInfo = TagFileProcessor.parseTagFileDirectives(
+                parserController, name, path, jarResource, this);
+        return new TagFileInfo(name, path, tagInfo);
+    }
+
+    TagAttributeInfo createAttribute(TreeNode elem, String jspVersion) {
+        String name = null;
+        String type = null;
+        String expectedType = null;
+        String methodSignature = null;
+        boolean required = false, rtexprvalue = false, isFragment = false, deferredValue = false, deferredMethod = false;
+
+        Iterator<TreeNode> list = elem.findChildren();
+        while (list.hasNext()) {
+            TreeNode element = list.next();
+            String tname = element.getName();
+
+            if ("name".equals(tname)) {
+                name = element.getBody();
+            } else if ("required".equals(tname)) {
+                String s = element.getBody();
+                if (s != null)
+                    required = JspUtil.booleanValue(s);
+            } else if ("rtexprvalue".equals(tname)) {
+                String s = element.getBody();
+                if (s != null)
+                    rtexprvalue = JspUtil.booleanValue(s);
+            } else if ("type".equals(tname)) {
+                type = element.getBody();
+                if ("1.2".equals(jspVersion)
+                        && (type.equals("Boolean") || type.equals("Byte")
+                                || type.equals("Character")
+                                || type.equals("Double")
+                                || type.equals("Float")
+                                || type.equals("Integer")
+                                || type.equals("Long") || type.equals("Object")
+                                || type.equals("Short") || type
+                                .equals("String"))) {
+                    type = "java.lang." + type;
+                }
+            } else if ("fragment".equals(tname)) {
+                String s = element.getBody();
+                if (s != null) {
+                    isFragment = JspUtil.booleanValue(s);
+                }
+            } else if ("deferred-value".equals(tname)) {
+                deferredValue = true;
+                type = "javax.el.ValueExpression";
+                TreeNode child = element.findChild("type");
+                if (child != null) {
+                    expectedType = child.getBody();
+                    if (expectedType != null) {
+                        expectedType = expectedType.trim();
+                    }
+                } else {
+                    expectedType = "java.lang.Object";
+                }
+            } else if ("deferred-method".equals(tname)) {
+                deferredMethod = true;
+                type = "javax.el.MethodExpression";
+                TreeNode child = element.findChild("method-signature");
+                if (child != null) {
+                    methodSignature = child.getBody();
+                    if (methodSignature != null) {
+                        methodSignature = methodSignature.trim();
+                    }
+                } else {
+                    methodSignature = "java.lang.Object method()";
+                }
+            } else if ("description".equals(tname) || false) {
+                // Ignored elements
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage(
+                            "jsp.warning.unknown.element.in.attribute", tname));
+                }
+            }
+        }
+
+        if (isFragment) {
+            /*
+             * According to JSP.C-3 ("TLD Schema Element Structure - tag"),
+             * 'type' and 'rtexprvalue' must not be specified if 'fragment' has
+             * been specified (this will be enforced by validating parser).
+             * Also, if 'fragment' is TRUE, 'type' is fixed at
+             * javax.servlet.jsp.tagext.JspFragment, and 'rtexprvalue' is fixed
+             * at true. See also JSP.8.5.2.
+             */
+            type = "javax.servlet.jsp.tagext.JspFragment";
+            rtexprvalue = true;
+        }
+
+        if (!rtexprvalue && type == null) {
+            // According to JSP spec, for static values (those determined at
+            // translation time) the type is fixed at java.lang.String.
+            type = "java.lang.String";
+        }
+        
+        return new TagAttributeInfo(name, required, type, rtexprvalue,
+                isFragment, null, deferredValue, deferredMethod, expectedType,
+                methodSignature);
+    }
+
+    TagVariableInfo createVariable(TreeNode elem) {
+        String nameGiven = null;
+        String nameFromAttribute = null;
+        String className = "java.lang.String";
+        boolean declare = true;
+        int scope = VariableInfo.NESTED;
+
+        Iterator<TreeNode> list = elem.findChildren();
+        while (list.hasNext()) {
+            TreeNode element = list.next();
+            String tname = element.getName();
+            if ("name-given".equals(tname))
+                nameGiven = element.getBody();
+            else if ("name-from-attribute".equals(tname))
+                nameFromAttribute = element.getBody();
+            else if ("variable-class".equals(tname))
+                className = element.getBody();
+            else if ("declare".equals(tname)) {
+                String s = element.getBody();
+                if (s != null)
+                    declare = JspUtil.booleanValue(s);
+            } else if ("scope".equals(tname)) {
+                String s = element.getBody();
+                if (s != null) {
+                    if ("NESTED".equals(s)) {
+                        scope = VariableInfo.NESTED;
+                    } else if ("AT_BEGIN".equals(s)) {
+                        scope = VariableInfo.AT_BEGIN;
+                    } else if ("AT_END".equals(s)) {
+                        scope = VariableInfo.AT_END;
+                    }
+                }
+            } else if ("description".equals(tname) || // Ignored elements
+            false) {
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage(
+                            "jsp.warning.unknown.element.in.variable", tname));
+                }
+            }
+        }
+        return new TagVariableInfo(nameGiven, nameFromAttribute, className,
+                declare, scope);
+    }
+
+    private TagLibraryValidator createValidator(TreeNode elem)
+            throws JasperException {
+
+        String validatorClass = null;
+        Map<String,Object> initParams = new Hashtable<String,Object>();
+
+        Iterator<TreeNode> list = elem.findChildren();
+        while (list.hasNext()) {
+            TreeNode element = list.next();
+            String tname = element.getName();
+            if ("validator-class".equals(tname))
+                validatorClass = element.getBody();
+            else if ("init-param".equals(tname)) {
+                String[] initParam = createInitParam(element);
+                initParams.put(initParam[0], initParam[1]);
+            } else if ("description".equals(tname) || // Ignored elements
+            false) {
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage(
+                            "jsp.warning.unknown.element.in.validator", tname));
+                }
+            }
+        }
+
+        TagLibraryValidator tlv = null;
+        if (validatorClass != null && !validatorClass.equals("")) {
+            try {
+                Class<?> tlvClass = ctxt.getClassLoader()
+                        .loadClass(validatorClass);
+                tlv = (TagLibraryValidator) tlvClass.newInstance();
+            } catch (Exception e) {
+                err.jspError("jsp.error.tlvclass.instantiation",
+                        validatorClass, e);
+            }
+        }
+        if (tlv != null) {
+            tlv.setInitParameters(initParams);
+        }
+        return tlv;
+    }
+
+    String[] createInitParam(TreeNode elem) {
+        String[] initParam = new String[2];
+
+        Iterator<TreeNode> list = elem.findChildren();
+        while (list.hasNext()) {
+            TreeNode element = list.next();
+            String tname = element.getName();
+            if ("param-name".equals(tname)) {
+                initParam[0] = element.getBody();
+            } else if ("param-value".equals(tname)) {
+                initParam[1] = element.getBody();
+            } else if ("description".equals(tname)) {
+                 // Do nothing
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage(
+                            "jsp.warning.unknown.element.in.initParam", tname));
+                }
+            }
+        }
+        return initParam;
+    }
+
+    FunctionInfo createFunctionInfo(TreeNode elem) {
+
+        String name = null;
+        String klass = null;
+        String signature = null;
+
+        Iterator<TreeNode> list = elem.findChildren();
+        while (list.hasNext()) {
+            TreeNode element = list.next();
+            String tname = element.getName();
+
+            if ("name".equals(tname)) {
+                name = element.getBody();
+            } else if ("function-class".equals(tname)) {
+                klass = element.getBody();
+            } else if ("function-signature".equals(tname)) {
+                signature = element.getBody();
+            } else if ("display-name".equals(tname) || // Ignored elements
+                    "small-icon".equals(tname) || "large-icon".equals(tname)
+                    || "description".equals(tname) || "example".equals(tname)) {
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn(Localizer.getMessage(
+                            "jsp.warning.unknown.element.in.function", tname));
+                }
+            }
+        }
+
+        return new FunctionInfo(name, klass, signature);
+    }
+
+    // *********************************************************************
+    // Until javax.servlet.jsp.tagext.TagLibraryInfo is fixed
+
+    /**
+     * The instance (if any) for the TagLibraryValidator class.
+     * 
+     * @return The TagLibraryValidator instance, if any.
+     */
+    public TagLibraryValidator getTagLibraryValidator() {
+        return tagLibraryValidator;
+    }
+
+    /**
+     * Translation-time validation of the XML document associated with the JSP
+     * page. This is a convenience method on the associated TagLibraryValidator
+     * class.
+     * 
+     * @param thePage
+     *            The JSP page object
+     * @return A string indicating whether the page is valid or not.
+     */
+    public ValidationMessage[] validate(PageData thePage) {
+        TagLibraryValidator tlv = getTagLibraryValidator();
+        if (tlv == null)
+            return null;
+
+        String uri = getURI();
+        if (uri.startsWith("/")) {
+            uri = URN_JSPTLD + uri;
+        }
+
+        return tlv.validate(getPrefixString(), uri, thePage);
+    }
+
+    protected TagLibraryValidator tagLibraryValidator;
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/TagPluginManager.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/TagPluginManager.java
new file mode 100644
index 0000000..63d324d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/TagPluginManager.java
@@ -0,0 +1,256 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import javax.servlet.ServletContext;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.compiler.tagplugin.TagPlugin;
+import org.apache.jasper.compiler.tagplugin.TagPluginContext;
+import org.apache.jasper.xmlparser.ParserUtils;
+import org.apache.jasper.xmlparser.TreeNode;
+
+/**
+ * Manages tag plugin optimizations.
+ * @author Kin-man Chung
+ */
+
+public class TagPluginManager {
+
+    private static final String TAG_PLUGINS_XML = "/WEB-INF/tagPlugins.xml";
+    private static final String TAG_PLUGINS_ROOT_ELEM = "tag-plugins";
+
+    private boolean initialized = false;
+    private HashMap<String, TagPlugin> tagPlugins = null;
+    private ServletContext ctxt;
+    private PageInfo pageInfo;
+
+    public TagPluginManager(ServletContext ctxt) {
+        this.ctxt = ctxt;
+    }
+
+    public void apply(Node.Nodes page, ErrorDispatcher err, PageInfo pageInfo)
+            throws JasperException {
+
+        init(err);
+        if (tagPlugins == null || tagPlugins.size() == 0) {
+            return;
+        }
+
+        this.pageInfo = pageInfo;
+
+        page.visit(new Node.Visitor() {
+            @Override
+            public void visit(Node.CustomTag n)
+                    throws JasperException {
+                invokePlugin(n);
+                visitBody(n);
+            }
+        });
+
+    }
+ 
+    private void init(ErrorDispatcher err) throws JasperException {
+        if (initialized)
+            return;
+
+        InputStream is = ctxt.getResourceAsStream(TAG_PLUGINS_XML);
+        if (is == null)
+            return;
+
+        TreeNode root = (new ParserUtils()).parseXMLDocument(TAG_PLUGINS_XML,
+                                                             is);
+        if (root == null) {
+            return;
+        }
+
+        if (!TAG_PLUGINS_ROOT_ELEM.equals(root.getName())) {
+            err.jspError("jsp.error.plugin.wrongRootElement", TAG_PLUGINS_XML,
+                         TAG_PLUGINS_ROOT_ELEM);
+        }
+
+        tagPlugins = new HashMap<String, TagPlugin>();
+        Iterator<TreeNode> pluginList = root.findChildren("tag-plugin");
+        while (pluginList.hasNext()) {
+            TreeNode pluginNode = pluginList.next();
+            TreeNode tagClassNode = pluginNode.findChild("tag-class");
+            if (tagClassNode == null) {
+                // Error
+                return;
+            }
+            String tagClass = tagClassNode.getBody().trim();
+            TreeNode pluginClassNode = pluginNode.findChild("plugin-class");
+            if (pluginClassNode == null) {
+                // Error
+                return;
+            }
+
+            String pluginClassStr = pluginClassNode.getBody();
+            TagPlugin tagPlugin = null;
+            try {
+                Class<?> pluginClass = Class.forName(pluginClassStr);
+                tagPlugin = (TagPlugin) pluginClass.newInstance();
+            } catch (Exception e) {
+                throw new JasperException(e);
+            }
+            if (tagPlugin == null) {
+                return;
+            }
+            tagPlugins.put(tagClass, tagPlugin);
+        }
+        initialized = true;
+    }
+
+    /**
+     * Invoke tag plugin for the given custom tag, if a plugin exists for 
+     * the custom tag's tag handler.
+     *
+     * The given custom tag node will be manipulated by the plugin.
+     */
+    private void invokePlugin(Node.CustomTag n) {
+        TagPlugin tagPlugin = tagPlugins.get(n.getTagHandlerClass().getName());
+        if (tagPlugin == null) {
+            return;
+        }
+
+        TagPluginContext tagPluginContext = new TagPluginContextImpl(n, pageInfo);
+        n.setTagPluginContext(tagPluginContext);
+        tagPlugin.doTag(tagPluginContext);
+    }
+
+    static class TagPluginContextImpl implements TagPluginContext {
+        private Node.CustomTag node;
+        private Node.Nodes curNodes;
+        private PageInfo pageInfo;
+        private HashMap<String, Object> pluginAttributes;
+
+        TagPluginContextImpl(Node.CustomTag n, PageInfo pageInfo) {
+            this.node = n;
+            this.pageInfo = pageInfo;
+            curNodes = new Node.Nodes();
+            n.setAtETag(curNodes);
+            curNodes = new Node.Nodes();
+            n.setAtSTag(curNodes);
+            n.setUseTagPlugin(true);
+            pluginAttributes = new HashMap<String, Object>();
+        }
+
+        @Override
+        public TagPluginContext getParentContext() {
+            Node parent = node.getParent();
+            if (! (parent instanceof Node.CustomTag)) {
+                return null;
+            }
+            return ((Node.CustomTag) parent).getTagPluginContext();
+        }
+
+        @Override
+        public void setPluginAttribute(String key, Object value) {
+            pluginAttributes.put(key, value);
+        }
+
+        @Override
+        public Object getPluginAttribute(String key) {
+            return pluginAttributes.get(key);
+        }
+
+        @Override
+        public boolean isScriptless() {
+            return node.getChildInfo().isScriptless();
+        }
+
+        @Override
+        public boolean isConstantAttribute(String attribute) {
+            Node.JspAttribute attr = getNodeAttribute(attribute);
+            if (attr == null)
+                return false;
+            return attr.isLiteral();
+        }
+
+        @Override
+        public String getConstantAttribute(String attribute) {
+            Node.JspAttribute attr = getNodeAttribute(attribute);
+            if (attr == null)
+                return null;
+            return attr.getValue();
+        }
+
+        @Override
+        public boolean isAttributeSpecified(String attribute) {
+            return getNodeAttribute(attribute) != null;
+        }
+
+        @Override
+        public String getTemporaryVariableName() {
+            return node.getRoot().nextTemporaryVariableName();
+        }
+
+        @Override
+        public void generateImport(String imp) {
+            pageInfo.addImport(imp);
+        }
+
+        @Override
+        public void generateDeclaration(String id, String text) {
+            if (pageInfo.isPluginDeclared(id)) {
+                return;
+            }
+            curNodes.add(new Node.Declaration(text, node.getStart(), null));
+        }
+
+        @Override
+        public void generateJavaSource(String sourceCode) {
+            curNodes.add(new Node.Scriptlet(sourceCode, node.getStart(),
+                                            null));
+        }
+
+        @Override
+        public void generateAttribute(String attributeName) {
+            curNodes.add(new Node.AttributeGenerator(node.getStart(),
+                                                     attributeName,
+                                                     node));
+        }
+
+        @Override
+        public void dontUseTagPlugin() {
+            node.setUseTagPlugin(false);
+        }
+
+        @Override
+        public void generateBody() {
+            // Since we'll generate the body anyway, this is really a nop, 
+            // except for the fact that it lets us put the Java sources the
+            // plugins produce in the correct order (w.r.t the body).
+            curNodes = node.getAtETag();
+        }
+
+        private Node.JspAttribute getNodeAttribute(String attribute) {
+            Node.JspAttribute[] attrs = node.getJspAttributes();
+            for (int i=0; attrs != null && i < attrs.length; i++) {
+                if (attrs[i].getName().equals(attribute)) {
+                    return attrs[i];
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/TextOptimizer.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/TextOptimizer.java
new file mode 100644
index 0000000..e6af9ab
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/TextOptimizer.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.jasper.compiler;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.Options;
+
+/**
+ */
+public class TextOptimizer {
+
+    /**
+     * A visitor to concatenate contiguous template texts.
+     */
+    static class TextCatVisitor extends Node.Visitor {
+
+        private Options options;
+        private PageInfo pageInfo;
+        private int textNodeCount = 0;
+        private Node.TemplateText firstTextNode = null;
+        private StringBuilder textBuffer;
+        private final String emptyText = "";
+
+        public TextCatVisitor(Compiler compiler) {
+            options = compiler.getCompilationContext().getOptions();
+            pageInfo = compiler.getPageInfo();
+        }
+
+        @Override
+        public void doVisit(Node n) throws JasperException {
+            collectText();
+        }
+
+        /*
+         * The following directives are ignored in text concatenation
+         */
+
+        @Override
+        public void visit(Node.PageDirective n) throws JasperException {
+        }
+
+        @Override
+        public void visit(Node.TagDirective n) throws JasperException {
+        }
+
+        @Override
+        public void visit(Node.TaglibDirective n) throws JasperException {
+        }
+
+        @Override
+        public void visit(Node.AttributeDirective n) throws JasperException {
+        }
+
+        @Override
+        public void visit(Node.VariableDirective n) throws JasperException {
+        }
+
+        /*
+         * Don't concatenate text across body boundaries
+         */
+        @Override
+        public void visitBody(Node n) throws JasperException {
+            super.visitBody(n);
+            collectText();
+        }
+
+        @Override
+        public void visit(Node.TemplateText n) throws JasperException {
+            if ((options.getTrimSpaces() || pageInfo.isTrimDirectiveWhitespaces()) 
+                    && n.isAllSpace()) {
+                n.setText(emptyText);
+                return;
+            }
+
+            if (textNodeCount++ == 0) {
+                firstTextNode = n;
+                textBuffer = new StringBuilder(n.getText());
+            } else {
+                // Append text to text buffer
+                textBuffer.append(n.getText());
+                n.setText(emptyText);
+            }
+        }
+
+        /**
+         * This method breaks concatenation mode.  As a side effect it copies
+         * the concatenated string to the first text node 
+         */
+        private void collectText() {
+
+            if (textNodeCount > 1) {
+                // Copy the text in buffer into the first template text node.
+                firstTextNode.setText(textBuffer.toString());
+            }
+            textNodeCount = 0;
+        }
+
+    }
+
+    public static void concatenate(Compiler compiler, Node.Nodes page)
+            throws JasperException {
+
+        TextCatVisitor v = new TextCatVisitor(compiler);
+        page.visit(v);
+
+        // Cleanup, in case the page ends with a template text
+        v.collectText();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/TldLocation.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/TldLocation.java
new file mode 100644
index 0000000..5e3f815
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/TldLocation.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.jasper.compiler;
+
+public class TldLocation {
+    
+    private String entryName;
+    private JarResource jar;
+    
+    public TldLocation(String entryName) {
+        this(entryName, (JarResource)null);
+    }
+    
+    public TldLocation(String entryName, String resourceUrl) {
+        this(entryName, getJarResource(resourceUrl));
+    }
+    
+    public TldLocation(String entryName, JarResource jarResource) {
+        if (entryName == null) {
+            throw new IllegalArgumentException("Tld name is required");
+        }
+        this.entryName = entryName;
+        this.jar = jarResource;
+    }
+        
+    private static JarResource getJarResource(String resourceUrl) {
+        return (resourceUrl != null) ? new JarURLResource(resourceUrl) : null;
+    }
+    
+    /**
+     * @return The name of the tag library.
+     */
+    public String getName() {
+        return entryName;
+    }
+    
+    /**
+     * 
+     * @return The jar resource the tag library is contained in. 
+     *         Might return null if the tag library is not contained in jar resource.
+     */
+    public JarResource getJarResource() {
+        return jar;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/TldLocationsCache.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/TldLocationsCache.java
new file mode 100644
index 0000000..580d567
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/TldLocationsCache.java
@@ -0,0 +1,452 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.JarURLConnection;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import javax.servlet.ServletContext;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.util.ExceptionUtils;
+import org.apache.jasper.xmlparser.ParserUtils;
+import org.apache.jasper.xmlparser.TreeNode;
+import org.apache.tomcat.JarScanner;
+import org.apache.tomcat.JarScannerCallback;
+
+/**
+ * A container for all tag libraries that are defined "globally"
+ * for the web application.
+ * 
+ * Tag Libraries can be defined globally in one of two ways:
+ *   1. Via <taglib> elements in web.xml:
+ *      the uri and location of the tag-library are specified in
+ *      the <taglib> element.
+ *   2. Via packaged jar files that contain .tld files
+ *      within the META-INF directory, or some subdirectory
+ *      of it. The taglib is 'global' if it has the <uri>
+ *      element defined.
+ *
+ * A mapping between the taglib URI and its associated TaglibraryInfoImpl
+ * is maintained in this container.
+ * Actually, that's what we'd like to do. However, because of the
+ * way the classes TagLibraryInfo and TagInfo have been defined,
+ * it is not currently possible to share an instance of TagLibraryInfo
+ * across page invocations. A bug has been submitted to the spec lead.
+ * In the mean time, all we do is save the 'location' where the
+ * TLD associated with a taglib URI can be found.
+ *
+ * When a JSP page has a taglib directive, the mappings in this container
+ * are first searched (see method getLocation()).
+ * If a mapping is found, then the location of the TLD is returned.
+ * If no mapping is found, then the uri specified
+ * in the taglib directive is to be interpreted as the location for
+ * the TLD of this tag library.
+ *
+ * @author Pierre Delisle
+ * @author Jan Luehe
+ */
+
+public class TldLocationsCache {
+
+    /**
+     * The types of URI one may specify for a tag library
+     */
+    public static final int ABS_URI = 0;
+    public static final int ROOT_REL_URI = 1;
+    public static final int NOROOT_REL_URI = 2;
+
+    private static final String WEB_INF = "/WEB-INF/";
+    private static final String WEB_INF_LIB = "/WEB-INF/lib/";
+    private static final String JAR_EXT = ".jar";
+    private static final String TLD_EXT = ".tld";
+
+    // Names of JARs that are known not to contain any TLDs
+    private static Set<String> noTldJars = null;
+
+    /**
+     * The mapping of the 'global' tag library URI to the location (resource
+     * path) of the TLD associated with that tag library. The location is
+     * returned as a String array:
+     *    [0] The location
+     *    [1] If the location is a jar file, this is the location of the tld.
+     */
+    private Hashtable<String, TldLocation> mappings;
+
+    private boolean initialized;
+    private ServletContext ctxt;
+
+    /** Constructor. 
+     *
+     * @param ctxt the servlet context of the web application in which Jasper 
+     * is running
+     */
+    public TldLocationsCache(ServletContext ctxt) {
+        this.ctxt = ctxt;
+        mappings = new Hashtable<String, TldLocation>();
+        initialized = false;
+    }
+
+    /**
+     * Sets the list of JARs that are known not to contain any TLDs.
+     *
+     * @param jarNames List of comma-separated names of JAR files that are 
+     * known not to contain any TLDs 
+     */
+    public static void setNoTldJars(String jarNames) {
+        if (jarNames == null) {
+            noTldJars = null;
+        } else {
+            if (noTldJars == null) {
+                noTldJars = new HashSet<String>();
+            } else {
+                noTldJars.clear();
+            }
+            StringTokenizer tokenizer = new StringTokenizer(jarNames, ",");
+            while (tokenizer.hasMoreElements()) {
+                noTldJars.add(tokenizer.nextToken());
+            }
+        }
+    }
+
+    /**
+     * Gets the 'location' of the TLD associated with the given taglib 'uri'.
+     *
+     * Returns null if the uri is not associated with any tag library 'exposed'
+     * in 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).
+     * 
+     * @param uri The 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 uri is not associated with any tag library 'exposed'
+     * in the web application.
+     */
+    public TldLocation getLocation(String uri) throws JasperException {
+        if (!initialized) {
+            init();
+        }
+        return mappings.get(uri);
+    }
+
+    /** 
+     * Returns the type of a URI:
+     *     ABS_URI
+     *     ROOT_REL_URI
+     *     NOROOT_REL_URI
+     */
+    public static int uriType(String uri) {
+        if (uri.indexOf(':') != -1) {
+            return ABS_URI;
+        } else if (uri.startsWith("/")) {
+            return ROOT_REL_URI;
+        } else {
+            return NOROOT_REL_URI;
+        }
+    }
+
+    /*
+     * Keep processing order in sync with o.a.c.startup.TldConfig
+     *
+     * This supports a Tomcat-specific extension to the TLD search
+     * order defined in the JSP spec. It allows tag libraries packaged as JAR
+     * files to be shared by web applications by simply dropping them in a 
+     * location that all web applications have access to (e.g.,
+     * <CATALINA_HOME>/lib). It also supports some of the weird and
+     * wonderful arrangements present when Tomcat gets embedded.
+     *
+     */
+    private void init() throws JasperException {
+        if (initialized) return;
+        try {
+            tldScanWebXml();
+            tldScanResourcePaths(WEB_INF);
+            
+            JarScanner jarScanner = JarScannerFactory.getJarScanner(ctxt);
+            if (jarScanner != null) {
+                jarScanner.scan(ctxt,
+                        Thread.currentThread().getContextClassLoader(),
+                        new TldJarScannerCallback(), noTldJars);
+            }
+
+            initialized = true;
+        } catch (Exception ex) {
+            throw new JasperException(Localizer.getMessage(
+                    "jsp.error.internal.tldinit", ex.getMessage()), ex);
+        }
+    }
+
+    private class TldJarScannerCallback implements JarScannerCallback {
+
+        @Override
+        public void scan(JarURLConnection urlConn) throws IOException {
+            tldScanJar(urlConn);
+        }
+
+        @Override
+        public void scan(File file) throws IOException {
+            File metaInf = new File(file, "META-INF");
+            if (metaInf.isDirectory()) {
+                tldScanDir(metaInf);
+            }
+        }
+    }
+
+    /*
+     * Populates taglib map described in web.xml.
+     * 
+     * This is not kept in sync with o.a.c.startup.TldConfig as the Jasper only
+     * needs the URI to TLD mappings from scan web.xml whereas TldConfig needs
+     * to scan the actual TLD files.
+     */    
+    private void tldScanWebXml() throws Exception {
+
+        WebXml webXml = null;
+        try {
+            webXml = new WebXml(ctxt);
+            if (webXml.getInputSource() == null) {
+                return;
+            }
+
+            // Parse the web application deployment descriptor
+            TreeNode webtld = null;
+            webtld = new ParserUtils().parseXMLDocument(webXml.getSystemId(),
+                    webXml.getInputSource());
+
+            // Allow taglib to be an element of the root or jsp-config (JSP2.0)
+            TreeNode jspConfig = webtld.findChild("jsp-config");
+            if (jspConfig != null) {
+                webtld = jspConfig;
+            }
+            Iterator<TreeNode> taglibs = webtld.findChildren("taglib");
+            while (taglibs.hasNext()) {
+
+                // Parse the next <taglib> element
+                TreeNode taglib = taglibs.next();
+                String tagUri = null;
+                String tagLoc = null;
+                TreeNode child = taglib.findChild("taglib-uri");
+                if (child != null)
+                    tagUri = child.getBody();
+                child = taglib.findChild("taglib-location");
+                if (child != null)
+                    tagLoc = child.getBody();
+
+                // Save this location if appropriate
+                if (tagLoc == null)
+                    continue;
+                if (uriType(tagLoc) == NOROOT_REL_URI)
+                    tagLoc = "/WEB-INF/" + tagLoc;
+                TldLocation location;
+                if (tagLoc.endsWith(JAR_EXT)) {
+                    location = new TldLocation("META-INF/taglib.tld", ctxt.getResource(tagLoc).toString());
+                } else {
+                    location = new TldLocation(tagLoc);
+                }
+                mappings.put(tagUri, location);
+            }
+        } finally {
+            if (webXml != null) {
+                webXml.close();
+            }
+        }
+    }
+
+    /*
+     * Scans the web application's sub-directory identified by startPath,
+     * along with its sub-directories, for TLDs and adds an implicit map entry
+     * to the taglib map for any TLD that has a <uri> element.
+     *
+     * Initially, rootPath equals /WEB-INF/. The /WEB-INF/classes and
+     * /WEB-INF/lib sub-directories are excluded from the search, as per the
+     * JSP 2.0 spec.
+     * 
+     * Keep code in sync with o.a.c.startup.TldConfig
+     */
+    private void tldScanResourcePaths(String startPath)
+            throws Exception {
+
+        Set<String> dirList = ctxt.getResourcePaths(startPath);
+        if (dirList != null) {
+            Iterator<String> it = dirList.iterator();
+            while (it.hasNext()) {
+                String path = it.next();
+                if (!path.endsWith(TLD_EXT)
+                        && (path.startsWith(WEB_INF_LIB)
+                                || path.startsWith("/WEB-INF/classes/"))) {
+                    continue;
+                }
+                if (path.endsWith(TLD_EXT)) {
+                    if (path.startsWith("/WEB-INF/tags/") &&
+                            !path.endsWith("implicit.tld")) {
+                        continue;
+                    }
+                    InputStream stream = ctxt.getResourceAsStream(path);
+                    try {
+                        tldScanStream(path, null, stream);
+                    } finally {
+                        if (stream != null) {
+                            try {
+                                stream.close();
+                            } catch (Throwable t) {
+                                ExceptionUtils.handleThrowable(t);
+                            }
+                        }
+                    }
+                } else {
+                    tldScanResourcePaths(path);
+                }
+            }
+        }
+    }
+
+    /*
+     * Scans the directory identified by startPath, along with its
+     * sub-directories, for TLDs.
+     *
+     * Keep in sync with o.a.c.startup.TldConfig
+     */
+    private void tldScanDir(File start) throws IOException {
+
+        File[] fileList = start.listFiles();
+        if (fileList != null) {
+            for (int i = 0; i < fileList.length; i++) {
+                // Scan recursively
+                if (fileList[i].isDirectory()) {
+                    tldScanDir(fileList[i]);
+                } else if (fileList[i].getAbsolutePath().endsWith(TLD_EXT)) {
+                    InputStream stream = null;
+                    try {
+                        stream = new FileInputStream(fileList[i]);
+                        tldScanStream(
+                                fileList[i].toURI().toString(), null, stream);
+                    } finally {
+                        if (stream != null) {
+                            try {
+                                stream.close();
+                            } catch (Throwable t) {
+                                ExceptionUtils.handleThrowable(t);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /*
+     * Scans the given JarURLConnection for TLD files located in META-INF
+     * (or a subdirectory of it), adding an implicit map entry to the taglib
+     * map for any TLD that has a <uri> element.
+     *
+     * @param conn The JarURLConnection to the JAR file to scan
+     * 
+     * Keep in sync with o.a.c.startup.TldConfig
+     */
+    private void tldScanJar(JarURLConnection conn) throws IOException {
+
+        JarFile jarFile = null;
+        String resourcePath = conn.getJarFileURL().toString();
+        try {
+            conn.setUseCaches(false);
+            jarFile = conn.getJarFile();
+            Enumeration<JarEntry> entries = jarFile.entries();
+            while (entries.hasMoreElements()) {
+                JarEntry entry = entries.nextElement();
+                String name = entry.getName();
+                if (!name.startsWith("META-INF/")) continue;
+                if (!name.endsWith(".tld")) continue;
+                InputStream stream = jarFile.getInputStream(entry);
+                tldScanStream(resourcePath, name, stream);
+            }
+        } finally {
+            if (jarFile != null) {
+                try {
+                    jarFile.close();
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                }
+            }
+        }
+    }
+
+    /*
+     * Scan the TLD contents in the specified input stream and add any new URIs
+     * to the map.
+     * 
+     * @param resourcePath  Path of the resource
+     * @param entryName     If the resource is a JAR file, the name of the entry
+     *                      in the JAR file
+     * @param stream        The input stream for the resource
+     * @throws IOException
+     */
+    private void tldScanStream(String resourcePath, String entryName,
+            InputStream stream) throws IOException {
+        try {
+            // Parse the tag library descriptor at the specified resource path
+            String uri = null;
+
+            TreeNode tld =
+                new ParserUtils().parseXMLDocument(resourcePath, stream);
+            TreeNode uriNode = tld.findChild("uri");
+            if (uriNode != null) {
+                String body = uriNode.getBody();
+                if (body != null)
+                    uri = body;
+            }
+
+            // Add implicit map entry only if its uri is not already
+            // present in the map
+            if (uri != null && mappings.get(uri) == null) {
+                TldLocation location;
+                if (entryName == null) {
+                    location = new TldLocation(resourcePath);
+                } else {
+                    location = new TldLocation(entryName, resourcePath);
+                }
+                mappings.put(uri, location);
+            }
+        } catch (JasperException e) {
+            // Hack - makes exception handling simpler
+            throw new IOException(e);
+        } finally {
+            if (stream != null) {
+                try {
+                    stream.close();
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                }
+            }
+        }
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Validator.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Validator.java
new file mode 100644
index 0000000..6d68d3b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/Validator.java
@@ -0,0 +1,1852 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Locale;
+
+import javax.el.ELException;
+import javax.el.ExpressionFactory;
+import javax.el.FunctionMapper;
+import javax.servlet.jsp.tagext.FunctionInfo;
+import javax.servlet.jsp.tagext.PageData;
+import javax.servlet.jsp.tagext.TagAttributeInfo;
+import javax.servlet.jsp.tagext.TagData;
+import javax.servlet.jsp.tagext.TagExtraInfo;
+import javax.servlet.jsp.tagext.TagInfo;
+import javax.servlet.jsp.tagext.TagLibraryInfo;
+import javax.servlet.jsp.tagext.ValidationMessage;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.el.ELContextImpl;
+import org.xml.sax.Attributes;
+
+/**
+ * Performs validation on the page elements. Attributes are checked for
+ * mandatory presence, entry value validity, and consistency. As a side effect,
+ * some page global value (such as those from page directives) are stored, for
+ * later use.
+ * 
+ * @author Kin-man Chung
+ * @author Jan Luehe
+ * @author Shawn Bayern
+ * @author Mark Roth
+ */
+class Validator {
+
+    /**
+     * A visitor to validate and extract page directive info
+     */
+    static class DirectiveVisitor extends Node.Visitor {
+
+        private PageInfo pageInfo;
+
+        private ErrorDispatcher err;
+
+        private static final JspUtil.ValidAttribute[] pageDirectiveAttrs = {
+            new JspUtil.ValidAttribute("language"),
+            new JspUtil.ValidAttribute("extends"),
+            new JspUtil.ValidAttribute("import"),
+            new JspUtil.ValidAttribute("session"),
+            new JspUtil.ValidAttribute("buffer"),
+            new JspUtil.ValidAttribute("autoFlush"),
+            new JspUtil.ValidAttribute("isThreadSafe"),
+            new JspUtil.ValidAttribute("info"),
+            new JspUtil.ValidAttribute("errorPage"),
+            new JspUtil.ValidAttribute("isErrorPage"),
+            new JspUtil.ValidAttribute("contentType"),
+            new JspUtil.ValidAttribute("pageEncoding"),
+            new JspUtil.ValidAttribute("isELIgnored"),
+            new JspUtil.ValidAttribute("deferredSyntaxAllowedAsLiteral"),
+            new JspUtil.ValidAttribute("trimDirectiveWhitespaces")
+        };
+
+        private boolean pageEncodingSeen = false;
+
+        /*
+         * Constructor
+         */
+        DirectiveVisitor(Compiler compiler) {
+            this.pageInfo = compiler.getPageInfo();
+            this.err = compiler.getErrorDispatcher();
+        }
+
+        @Override
+        public void visit(Node.IncludeDirective n) throws JasperException {
+            // Since pageDirectiveSeen flag only applies to the Current page
+            // save it here and restore it after the file is included.
+            boolean pageEncodingSeenSave = pageEncodingSeen;
+            pageEncodingSeen = false;
+            visitBody(n);
+            pageEncodingSeen = pageEncodingSeenSave;
+        }
+
+        @Override
+        public void visit(Node.PageDirective n) throws JasperException {
+
+            JspUtil.checkAttributes("Page directive", n, pageDirectiveAttrs,
+                    err);
+
+            // JSP.2.10.1
+            Attributes attrs = n.getAttributes();
+            for (int i = 0; attrs != null && i < attrs.getLength(); i++) {
+                String attr = attrs.getQName(i);
+                String value = attrs.getValue(i);
+
+                if ("language".equals(attr)) {
+                    if (pageInfo.getLanguage(false) == null) {
+                        pageInfo.setLanguage(value, n, err, true);
+                    } else if (!pageInfo.getLanguage(false).equals(value)) {
+                        err.jspError(n, "jsp.error.page.conflict.language",
+                                pageInfo.getLanguage(false), value);
+                    }
+                } else if ("extends".equals(attr)) {
+                    if (pageInfo.getExtends(false) == null) {
+                        pageInfo.setExtends(value, n);
+                    } else if (!pageInfo.getExtends(false).equals(value)) {
+                        err.jspError(n, "jsp.error.page.conflict.extends",
+                                pageInfo.getExtends(false), value);
+                    }
+                } else if ("contentType".equals(attr)) {
+                    if (pageInfo.getContentType() == null) {
+                        pageInfo.setContentType(value);
+                    } else if (!pageInfo.getContentType().equals(value)) {
+                        err.jspError(n, "jsp.error.page.conflict.contenttype",
+                                pageInfo.getContentType(), value);
+                    }
+                } else if ("session".equals(attr)) {
+                    if (pageInfo.getSession() == null) {
+                        pageInfo.setSession(value, n, err);
+                    } else if (!pageInfo.getSession().equals(value)) {
+                        err.jspError(n, "jsp.error.page.conflict.session",
+                                pageInfo.getSession(), value);
+                    }
+                } else if ("buffer".equals(attr)) {
+                    if (pageInfo.getBufferValue() == null) {
+                        pageInfo.setBufferValue(value, n, err);
+                    } else if (!pageInfo.getBufferValue().equals(value)) {
+                        err.jspError(n, "jsp.error.page.conflict.buffer",
+                                pageInfo.getBufferValue(), value);
+                    }
+                } else if ("autoFlush".equals(attr)) {
+                    if (pageInfo.getAutoFlush() == null) {
+                        pageInfo.setAutoFlush(value, n, err);
+                    } else if (!pageInfo.getAutoFlush().equals(value)) {
+                        err.jspError(n, "jsp.error.page.conflict.autoflush",
+                                pageInfo.getAutoFlush(), value);
+                    }
+                } else if ("isThreadSafe".equals(attr)) {
+                    if (pageInfo.getIsThreadSafe() == null) {
+                        pageInfo.setIsThreadSafe(value, n, err);
+                    } else if (!pageInfo.getIsThreadSafe().equals(value)) {
+                        err.jspError(n, "jsp.error.page.conflict.isthreadsafe",
+                                pageInfo.getIsThreadSafe(), value);
+                    }
+                } else if ("isELIgnored".equals(attr)) {
+                    if (pageInfo.getIsELIgnored() == null) {
+                        pageInfo.setIsELIgnored(value, n, err, true);
+                    } else if (!pageInfo.getIsELIgnored().equals(value)) {
+                        err.jspError(n, "jsp.error.page.conflict.iselignored",
+                                pageInfo.getIsELIgnored(), value);
+                    }
+                } else if ("isErrorPage".equals(attr)) {
+                    if (pageInfo.getIsErrorPage() == null) {
+                        pageInfo.setIsErrorPage(value, n, err);
+                    } else if (!pageInfo.getIsErrorPage().equals(value)) {
+                        err.jspError(n, "jsp.error.page.conflict.iserrorpage",
+                                pageInfo.getIsErrorPage(), value);
+                    }
+                } else if ("errorPage".equals(attr)) {
+                    if (pageInfo.getErrorPage() == null) {
+                        pageInfo.setErrorPage(value);
+                    } else if (!pageInfo.getErrorPage().equals(value)) {
+                        err.jspError(n, "jsp.error.page.conflict.errorpage",
+                                pageInfo.getErrorPage(), value);
+                    }
+                } else if ("info".equals(attr)) {
+                    if (pageInfo.getInfo() == null) {
+                        pageInfo.setInfo(value);
+                    } else if (!pageInfo.getInfo().equals(value)) {
+                        err.jspError(n, "jsp.error.page.conflict.info",
+                                pageInfo.getInfo(), value);
+                    }
+                } else if ("pageEncoding".equals(attr)) {
+                    if (pageEncodingSeen)
+                        err.jspError(n, "jsp.error.page.multi.pageencoding");
+                    // 'pageEncoding' can occur at most once per file
+                    pageEncodingSeen = true;
+                    String actual = comparePageEncodings(value, n);
+                    n.getRoot().setPageEncoding(actual);
+                } else if ("deferredSyntaxAllowedAsLiteral".equals(attr)) {
+                    if (pageInfo.getDeferredSyntaxAllowedAsLiteral() == null) {
+                        pageInfo.setDeferredSyntaxAllowedAsLiteral(value, n,
+                                err, true);
+                    } else if (!pageInfo.getDeferredSyntaxAllowedAsLiteral()
+                            .equals(value)) {
+                        err
+                                .jspError(
+                                        n,
+                                        "jsp.error.page.conflict.deferredsyntaxallowedasliteral",
+                                        pageInfo
+                                                .getDeferredSyntaxAllowedAsLiteral(),
+                                        value);
+                    }
+                } else if ("trimDirectiveWhitespaces".equals(attr)) {
+                    if (pageInfo.getTrimDirectiveWhitespaces() == null) {
+                        pageInfo.setTrimDirectiveWhitespaces(value, n, err,
+                                true);
+                    } else if (!pageInfo.getTrimDirectiveWhitespaces().equals(
+                            value)) {
+                        err
+                                .jspError(
+                                        n,
+                                        "jsp.error.page.conflict.trimdirectivewhitespaces",
+                                        pageInfo.getTrimDirectiveWhitespaces(),
+                                        value);
+                    }
+                }
+            }
+
+            // Check for bad combinations
+            if (pageInfo.getBuffer() == 0 && !pageInfo.isAutoFlush())
+                err.jspError(n, "jsp.error.page.badCombo");
+
+            // Attributes for imports for this node have been processed by
+            // the parsers, just add them to pageInfo.
+            pageInfo.addImports(n.getImports());
+        }
+
+        @Override
+        public void visit(Node.TagDirective n) throws JasperException {
+            // Note: Most of the validation is done in TagFileProcessor
+            // when it created a TagInfo object from the
+            // tag file in which the directive appeared.
+
+            // This method does additional processing to collect page info
+
+            Attributes attrs = n.getAttributes();
+            for (int i = 0; attrs != null && i < attrs.getLength(); i++) {
+                String attr = attrs.getQName(i);
+                String value = attrs.getValue(i);
+
+                if ("language".equals(attr)) {
+                    if (pageInfo.getLanguage(false) == null) {
+                        pageInfo.setLanguage(value, n, err, false);
+                    } else if (!pageInfo.getLanguage(false).equals(value)) {
+                        err.jspError(n, "jsp.error.tag.conflict.language",
+                                pageInfo.getLanguage(false), value);
+                    }
+                } else if ("isELIgnored".equals(attr)) {
+                    if (pageInfo.getIsELIgnored() == null) {
+                        pageInfo.setIsELIgnored(value, n, err, false);
+                    } else if (!pageInfo.getIsELIgnored().equals(value)) {
+                        err.jspError(n, "jsp.error.tag.conflict.iselignored",
+                                pageInfo.getIsELIgnored(), value);
+                    }
+                } else if ("pageEncoding".equals(attr)) {
+                    if (pageEncodingSeen)
+                        err.jspError(n, "jsp.error.tag.multi.pageencoding");
+                    pageEncodingSeen = true;
+                    compareTagEncodings(value, n);
+                    n.getRoot().setPageEncoding(value);
+                } else if ("deferredSyntaxAllowedAsLiteral".equals(attr)) {
+                    if (pageInfo.getDeferredSyntaxAllowedAsLiteral() == null) {
+                        pageInfo.setDeferredSyntaxAllowedAsLiteral(value, n,
+                                err, false);
+                    } else if (!pageInfo.getDeferredSyntaxAllowedAsLiteral()
+                            .equals(value)) {
+                        err
+                                .jspError(
+                                        n,
+                                        "jsp.error.tag.conflict.deferredsyntaxallowedasliteral",
+                                        pageInfo
+                                                .getDeferredSyntaxAllowedAsLiteral(),
+                                        value);
+                    }
+                } else if ("trimDirectiveWhitespaces".equals(attr)) {
+                    if (pageInfo.getTrimDirectiveWhitespaces() == null) {
+                        pageInfo.setTrimDirectiveWhitespaces(value, n, err,
+                                false);
+                    } else if (!pageInfo.getTrimDirectiveWhitespaces().equals(
+                            value)) {
+                        err
+                                .jspError(
+                                        n,
+                                        "jsp.error.tag.conflict.trimdirectivewhitespaces",
+                                        pageInfo.getTrimDirectiveWhitespaces(),
+                                        value);
+                    }
+                }
+            }
+
+            // Attributes for imports for this node have been processed by
+            // the parsers, just add them to pageInfo.
+            pageInfo.addImports(n.getImports());
+        }
+
+        @Override
+        public void visit(Node.AttributeDirective n) throws JasperException {
+            // Do nothing, since this attribute directive has already been
+            // validated by TagFileProcessor when it created a TagInfo object
+            // from the tag file in which the directive appeared
+        }
+
+        @Override
+        public void visit(Node.VariableDirective n) throws JasperException {
+            // Do nothing, since this variable directive has already been
+            // validated by TagFileProcessor when it created a TagInfo object
+            // from the tag file in which the directive appeared
+        }
+
+        /*
+         * Compares page encodings specified in various places, and throws
+         * exception in case of page encoding mismatch.
+         * 
+         * @param pageDirEnc The value of the pageEncoding attribute of the page
+         * directive @param pageDir The page directive node
+         * 
+         * @throws JasperException in case of page encoding mismatch
+         */
+        private String comparePageEncodings(String thePageDirEnc,
+                Node.PageDirective pageDir) throws JasperException {
+
+            Node.Root root = pageDir.getRoot();
+            String configEnc = root.getJspConfigPageEncoding();
+            String pageDirEnc = thePageDirEnc.toUpperCase(Locale.ENGLISH);
+
+            /*
+             * Compare the 'pageEncoding' attribute of the page directive with
+             * the encoding specified in the JSP config element whose URL
+             * pattern matches this page. Treat "UTF-16", "UTF-16BE", and
+             * "UTF-16LE" as identical.
+             */
+            if (configEnc != null) {
+                configEnc = configEnc.toUpperCase(Locale.ENGLISH);
+                if (!pageDirEnc.equals(configEnc)
+                        && (!pageDirEnc.startsWith("UTF-16") || !configEnc
+                                .startsWith("UTF-16"))) {
+                    err.jspError(pageDir,
+                            "jsp.error.config_pagedir_encoding_mismatch",
+                            configEnc, pageDirEnc);
+                } else {
+                    return configEnc;
+                }
+            }
+
+            /*
+             * Compare the 'pageEncoding' attribute of the page directive with
+             * the encoding specified in the XML prolog (only for XML syntax,
+             * and only if JSP document contains XML prolog with encoding
+             * declaration). Treat "UTF-16", "UTF-16BE", and "UTF-16LE" as
+             * identical.
+             */
+            if ((root.isXmlSyntax() && root.isEncodingSpecifiedInProlog()) || root.isBomPresent()) {
+                String pageEnc = root.getPageEncoding().toUpperCase(Locale.ENGLISH);
+                if (!pageDirEnc.equals(pageEnc)
+                        && (!pageDirEnc.startsWith("UTF-16") || !pageEnc
+                                .startsWith("UTF-16"))) {
+                    err.jspError(pageDir,
+                            "jsp.error.prolog_pagedir_encoding_mismatch",
+                            pageEnc, pageDirEnc);
+                } else {
+                    return pageEnc;
+                }
+            }
+            
+            return pageDirEnc;
+        }
+        
+        /*
+         * Compares page encodings specified in various places, and throws
+         * exception in case of page encoding mismatch.
+         * 
+         * @param thePageDirEnc The value of the pageEncoding attribute of the page
+         * directive @param pageDir The page directive node
+         * 
+         * @throws JasperException in case of page encoding mismatch
+         */
+        private void compareTagEncodings(String thePageDirEnc,
+                Node.TagDirective pageDir) throws JasperException {
+
+            Node.Root root = pageDir.getRoot();
+            String pageDirEnc = thePageDirEnc.toUpperCase(Locale.ENGLISH);
+            /*
+             * Compare the 'pageEncoding' attribute of the page directive with
+             * the encoding specified in the XML prolog (only for XML syntax,
+             * and only if JSP document contains XML prolog with encoding
+             * declaration). Treat "UTF-16", "UTF-16BE", and "UTF-16LE" as
+             * identical.
+             */
+            if ((root.isXmlSyntax() && root.isEncodingSpecifiedInProlog()) || root.isBomPresent()) {
+                String pageEnc = root.getPageEncoding().toUpperCase(Locale.ENGLISH);
+                if (!pageDirEnc.equals(pageEnc)
+                        && (!pageDirEnc.startsWith("UTF-16") || !pageEnc
+                                .startsWith("UTF-16"))) {
+                    err.jspError(pageDir,
+                            "jsp.error.prolog_pagedir_encoding_mismatch",
+                            pageEnc, pageDirEnc);
+                }
+            }
+        }
+
+    }
+
+    /**
+     * A visitor for validating nodes other than page directives
+     */
+    static class ValidateVisitor extends Node.Visitor {
+
+        private PageInfo pageInfo;
+
+        private ErrorDispatcher err;
+
+        private ClassLoader loader;
+
+        private final StringBuilder buf = new StringBuilder(32);
+
+        private static final JspUtil.ValidAttribute[] jspRootAttrs = {
+                new JspUtil.ValidAttribute("xsi:schemaLocation"),
+                new JspUtil.ValidAttribute("version", true) };
+
+        private static final JspUtil.ValidAttribute[] includeDirectiveAttrs = { new JspUtil.ValidAttribute(
+                "file", true) };
+
+        private static final JspUtil.ValidAttribute[] taglibDirectiveAttrs = {
+                new JspUtil.ValidAttribute("uri"),
+                new JspUtil.ValidAttribute("tagdir"),
+                new JspUtil.ValidAttribute("prefix", true) };
+
+        private static final JspUtil.ValidAttribute[] includeActionAttrs = {
+                new JspUtil.ValidAttribute("page", true),
+                new JspUtil.ValidAttribute("flush") };
+
+        private static final JspUtil.ValidAttribute[] paramActionAttrs = {
+                new JspUtil.ValidAttribute("name", true),
+                new JspUtil.ValidAttribute("value", true) };
+
+        private static final JspUtil.ValidAttribute[] forwardActionAttrs = {
+                new JspUtil.ValidAttribute("page", true) };
+
+        private static final JspUtil.ValidAttribute[] getPropertyAttrs = {
+                new JspUtil.ValidAttribute("name", true),
+                new JspUtil.ValidAttribute("property", true) };
+
+        private static final JspUtil.ValidAttribute[] setPropertyAttrs = {
+                new JspUtil.ValidAttribute("name", true),
+                new JspUtil.ValidAttribute("property", true),
+                new JspUtil.ValidAttribute("value", false),
+                new JspUtil.ValidAttribute("param") };
+
+        private static final JspUtil.ValidAttribute[] useBeanAttrs = {
+                new JspUtil.ValidAttribute("id", true),
+                new JspUtil.ValidAttribute("scope"),
+                new JspUtil.ValidAttribute("class"),
+                new JspUtil.ValidAttribute("type"),
+                new JspUtil.ValidAttribute("beanName", false) };
+
+        private static final JspUtil.ValidAttribute[] plugInAttrs = {
+                new JspUtil.ValidAttribute("type", true),
+                new JspUtil.ValidAttribute("code", true),
+                new JspUtil.ValidAttribute("codebase"),
+                new JspUtil.ValidAttribute("align"),
+                new JspUtil.ValidAttribute("archive"),
+                new JspUtil.ValidAttribute("height", false),
+                new JspUtil.ValidAttribute("hspace"),
+                new JspUtil.ValidAttribute("jreversion"),
+                new JspUtil.ValidAttribute("name"),
+                new JspUtil.ValidAttribute("vspace"),
+                new JspUtil.ValidAttribute("width", false),
+                new JspUtil.ValidAttribute("nspluginurl"),
+                new JspUtil.ValidAttribute("iepluginurl") };
+
+        private static final JspUtil.ValidAttribute[] attributeAttrs = {
+                new JspUtil.ValidAttribute("name", true),
+                new JspUtil.ValidAttribute("trim"),
+                new JspUtil.ValidAttribute("omit")};
+
+        private static final JspUtil.ValidAttribute[] invokeAttrs = {
+                new JspUtil.ValidAttribute("fragment", true),
+                new JspUtil.ValidAttribute("var"),
+                new JspUtil.ValidAttribute("varReader"),
+                new JspUtil.ValidAttribute("scope") };
+
+        private static final JspUtil.ValidAttribute[] doBodyAttrs = {
+                new JspUtil.ValidAttribute("var"),
+                new JspUtil.ValidAttribute("varReader"),
+                new JspUtil.ValidAttribute("scope") };
+
+        private static final JspUtil.ValidAttribute[] jspOutputAttrs = {
+                new JspUtil.ValidAttribute("omit-xml-declaration"),
+                new JspUtil.ValidAttribute("doctype-root-element"),
+                new JspUtil.ValidAttribute("doctype-public"),
+                new JspUtil.ValidAttribute("doctype-system") };
+
+        private static final ExpressionFactory EXPRESSION_FACTORY =
+            ExpressionFactory.newInstance();
+
+        /*
+         * Constructor
+         */
+        ValidateVisitor(Compiler compiler) {
+            this.pageInfo = compiler.getPageInfo();
+            this.err = compiler.getErrorDispatcher();
+            this.loader = compiler.getCompilationContext().getClassLoader();
+        }
+
+        @Override
+        public void visit(Node.JspRoot n) throws JasperException {
+            JspUtil.checkAttributes("Jsp:root", n, jspRootAttrs, err);
+            String version = n.getTextAttribute("version");
+            if (!version.equals("1.2") && !version.equals("2.0") &&
+                    !version.equals("2.1") && !version.equals("2.2")) {
+                err.jspError(n, "jsp.error.jsproot.version.invalid", version);
+            }
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.IncludeDirective n) throws JasperException {
+            JspUtil.checkAttributes("Include directive", n,
+                    includeDirectiveAttrs, err);
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.TaglibDirective n) throws JasperException {
+            JspUtil.checkAttributes("Taglib directive", n,
+                    taglibDirectiveAttrs, err);
+            // Either 'uri' or 'tagdir' attribute must be specified
+            String uri = n.getAttributeValue("uri");
+            String tagdir = n.getAttributeValue("tagdir");
+            if (uri == null && tagdir == null) {
+                err.jspError(n, "jsp.error.taglibDirective.missing.location");
+            }
+            if (uri != null && tagdir != null) {
+                err
+                        .jspError(n,
+                                "jsp.error.taglibDirective.both_uri_and_tagdir");
+            }
+        }
+
+        @Override
+        public void visit(Node.ParamAction n) throws JasperException {
+            JspUtil.checkAttributes("Param action", n, paramActionAttrs, err);
+            // make sure the value of the 'name' attribute is not a
+            // request-time expression
+            throwErrorIfExpression(n, "name", "jsp:param");
+            n.setValue(getJspAttribute(null, "value", null, null, n
+                    .getAttributeValue("value"), n, false));
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.ParamsAction n) throws JasperException {
+            // Make sure we've got at least one nested jsp:param
+            Node.Nodes subElems = n.getBody();
+            if (subElems == null) {
+                err.jspError(n, "jsp.error.params.emptyBody");
+            }
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.IncludeAction n) throws JasperException {
+            JspUtil.checkAttributes("Include action", n, includeActionAttrs,
+                    err);
+            n.setPage(getJspAttribute(null, "page", null, null, n
+                    .getAttributeValue("page"), n, false));
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.ForwardAction n) throws JasperException {
+            JspUtil.checkAttributes("Forward", n, forwardActionAttrs, err);
+            n.setPage(getJspAttribute(null, "page", null, null, n
+                    .getAttributeValue("page"), n, false));
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.GetProperty n) throws JasperException {
+            JspUtil.checkAttributes("GetProperty", n, getPropertyAttrs, err);
+        }
+
+        @Override
+        public void visit(Node.SetProperty n) throws JasperException {
+            JspUtil.checkAttributes("SetProperty", n, setPropertyAttrs, err);
+            String property = n.getTextAttribute("property");
+            String param = n.getTextAttribute("param");
+            String value = n.getAttributeValue("value");
+
+            n.setValue(getJspAttribute(null, "value", null, null, value,
+                    n, false));
+
+            boolean valueSpecified = n.getValue() != null;
+
+            if ("*".equals(property)) {
+                if (param != null || valueSpecified)
+                    err.jspError(n, "jsp.error.setProperty.invalid");
+
+            } else if (param != null && valueSpecified) {
+                err.jspError(n, "jsp.error.setProperty.invalid");
+            }
+
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.UseBean n) throws JasperException {
+            JspUtil.checkAttributes("UseBean", n, useBeanAttrs, err);
+
+            String name = n.getTextAttribute("id");
+            String scope = n.getTextAttribute("scope");
+            JspUtil.checkScope(scope, n, err);
+            String className = n.getTextAttribute("class");
+            String type = n.getTextAttribute("type");
+            BeanRepository beanInfo = pageInfo.getBeanRepository();
+
+            if (className == null && type == null)
+                err.jspError(n, "jsp.error.usebean.missingType");
+
+            if (beanInfo.checkVariable(name))
+                err.jspError(n, "jsp.error.usebean.duplicate");
+
+            if ("session".equals(scope) && !pageInfo.isSession())
+                err.jspError(n, "jsp.error.usebean.noSession");
+
+            Node.JspAttribute jattr = getJspAttribute(null, "beanName", null,
+                    null, n.getAttributeValue("beanName"), n, false);
+            n.setBeanName(jattr);
+            if (className != null && jattr != null)
+                err.jspError(n, "jsp.error.usebean.notBoth");
+
+            if (className == null)
+                className = type;
+
+            beanInfo.addBean(n, name, className, scope);
+
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.PlugIn n) throws JasperException {
+            JspUtil.checkAttributes("Plugin", n, plugInAttrs, err);
+
+            throwErrorIfExpression(n, "type", "jsp:plugin");
+            throwErrorIfExpression(n, "code", "jsp:plugin");
+            throwErrorIfExpression(n, "codebase", "jsp:plugin");
+            throwErrorIfExpression(n, "align", "jsp:plugin");
+            throwErrorIfExpression(n, "archive", "jsp:plugin");
+            throwErrorIfExpression(n, "hspace", "jsp:plugin");
+            throwErrorIfExpression(n, "jreversion", "jsp:plugin");
+            throwErrorIfExpression(n, "name", "jsp:plugin");
+            throwErrorIfExpression(n, "vspace", "jsp:plugin");
+            throwErrorIfExpression(n, "nspluginurl", "jsp:plugin");
+            throwErrorIfExpression(n, "iepluginurl", "jsp:plugin");
+
+            String type = n.getTextAttribute("type");
+            if (type == null)
+                err.jspError(n, "jsp.error.plugin.notype");
+            if (!type.equals("bean") && !type.equals("applet"))
+                err.jspError(n, "jsp.error.plugin.badtype");
+            if (n.getTextAttribute("code") == null)
+                err.jspError(n, "jsp.error.plugin.nocode");
+
+            Node.JspAttribute width = getJspAttribute(null, "width", null,
+                    null, n.getAttributeValue("width"), n, false);
+            n.setWidth(width);
+
+            Node.JspAttribute height = getJspAttribute(null, "height", null,
+                    null, n.getAttributeValue("height"), n, false);
+            n.setHeight(height);
+
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.NamedAttribute n) throws JasperException {
+            JspUtil.checkAttributes("Attribute", n, attributeAttrs, err);
+            n.setOmit(getJspAttribute(null, "omit", null, null, n
+                    .getAttributeValue("omit"), n, true));
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.JspBody n) throws JasperException {
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.Declaration n) throws JasperException {
+            if (pageInfo.isScriptingInvalid()) {
+                err.jspError(n.getStart(), "jsp.error.no.scriptlets");
+            }
+        }
+
+        @Override
+        public void visit(Node.Expression n) throws JasperException {
+            if (pageInfo.isScriptingInvalid()) {
+                err.jspError(n.getStart(), "jsp.error.no.scriptlets");
+            }
+        }
+
+        @Override
+        public void visit(Node.Scriptlet n) throws JasperException {
+            if (pageInfo.isScriptingInvalid()) {
+                err.jspError(n.getStart(), "jsp.error.no.scriptlets");
+            }
+        }
+
+        @Override
+        public void visit(Node.ELExpression n) throws JasperException {
+            // exit if we are ignoring EL all together
+            if (pageInfo.isELIgnored())
+                return;
+
+            // JSP.2.2 - '#{' not allowed in template text
+            if (n.getType() == '#') {
+                if (!pageInfo.isDeferredSyntaxAllowedAsLiteral()) {
+                    err.jspError(n, "jsp.error.el.template.deferred");
+                } else {
+                    return;
+                }
+            }
+
+            // build expression
+            StringBuilder expr = this.getBuffer();
+            expr.append(n.getType()).append('{').append(n.getText())
+                    .append('}');
+            ELNode.Nodes el = ELParser.parse(expr.toString(), pageInfo
+                    .isDeferredSyntaxAllowedAsLiteral());
+
+            // validate/prepare expression
+            prepareExpression(el, n, expr.toString());
+
+            // store it
+            n.setEL(el);
+        }
+
+        @Override
+        public void visit(Node.UninterpretedTag n) throws JasperException {
+            if (n.getNamedAttributeNodes().size() != 0) {
+                err.jspError(n, "jsp.error.namedAttribute.invalidUse");
+            }
+
+            Attributes attrs = n.getAttributes();
+            if (attrs != null) {
+                int attrSize = attrs.getLength();
+                Node.JspAttribute[] jspAttrs = new Node.JspAttribute[attrSize];
+                for (int i = 0; i < attrSize; i++) {
+                    // JSP.2.2 - '#{' not allowed in template text
+                    String value = attrs.getValue(i);
+                    if (!pageInfo.isDeferredSyntaxAllowedAsLiteral()) {
+                        if (containsDeferredSyntax(value)) {
+                            err.jspError(n, "jsp.error.el.template.deferred");
+                        }
+                    }
+                    jspAttrs[i] = getJspAttribute(null, attrs.getQName(i),
+                            attrs.getURI(i), attrs.getLocalName(i), value, n,
+                            false);
+                }
+                n.setJspAttributes(jspAttrs);
+            }
+
+            visitBody(n);
+        }
+
+        /*
+         * Look for a #{ sequence that isn't preceded by \.
+         */
+        private boolean containsDeferredSyntax(String value) {
+            if (value == null) {
+                return false;
+            }
+            
+            int i = 0;
+            int len = value.length();
+            boolean prevCharIsEscape = false;
+            while (i < value.length()) {
+                char c = value.charAt(i);
+                if (c == '#' && (i+1) < len && value.charAt(i+1) == '{' && !prevCharIsEscape) {
+                    return true;
+                } else if (c == '\\') {
+                    prevCharIsEscape = true;
+                } else {
+                    prevCharIsEscape = false;
+                }
+                i++;
+            }
+            return false;
+        }
+
+        @Override
+        public void visit(Node.CustomTag n) throws JasperException {
+
+            TagInfo tagInfo = n.getTagInfo();
+            if (tagInfo == null) {
+                err.jspError(n, "jsp.error.missing.tagInfo", n.getQName());
+            }
+
+            /*
+             * The bodycontent of a SimpleTag cannot be JSP.
+             */
+            if (n.implementsSimpleTag()
+                    && tagInfo.getBodyContent().equalsIgnoreCase(
+                            TagInfo.BODY_CONTENT_JSP)) {
+                err.jspError(n, "jsp.error.simpletag.badbodycontent", tagInfo
+                        .getTagClassName());
+            }
+
+            /*
+             * If the tag handler declares in the TLD that it supports dynamic
+             * attributes, it also must implement the DynamicAttributes
+             * interface.
+             */
+            if (tagInfo.hasDynamicAttributes()
+                    && !n.implementsDynamicAttributes()) {
+                err.jspError(n, "jsp.error.dynamic.attributes.not.implemented",
+                        n.getQName());
+            }
+
+            /*
+             * Make sure all required attributes are present, either as
+             * attributes or named attributes (<jsp:attribute>). Also make sure
+             * that the same attribute is not specified in both attributes or
+             * named attributes.
+             */
+            TagAttributeInfo[] tldAttrs = tagInfo.getAttributes();
+            String customActionUri = n.getURI();
+            Attributes attrs = n.getAttributes();
+            int attrsSize = (attrs == null) ? 0 : attrs.getLength();
+            for (int i = 0; i < tldAttrs.length; i++) {
+                String attr = null;
+                if (attrs != null) {
+                    attr = attrs.getValue(tldAttrs[i].getName());
+                    if (attr == null) {
+                        attr = attrs.getValue(customActionUri, tldAttrs[i]
+                                .getName());
+                    }
+                }
+                Node.NamedAttribute na = n.getNamedAttributeNode(tldAttrs[i]
+                        .getName());
+
+                if (tldAttrs[i].isRequired() && attr == null && na == null) {
+                    err.jspError(n, "jsp.error.missing_attribute", tldAttrs[i]
+                            .getName(), n.getLocalName());
+                }
+                if (attr != null && na != null) {
+                    err.jspError(n, "jsp.error.duplicate.name.jspattribute",
+                            tldAttrs[i].getName());
+                }
+            }
+
+            Node.Nodes naNodes = n.getNamedAttributeNodes();
+            int jspAttrsSize = naNodes.size() + attrsSize;
+            Node.JspAttribute[] jspAttrs = null;
+            if (jspAttrsSize > 0) {
+                jspAttrs = new Node.JspAttribute[jspAttrsSize];
+            }
+            Hashtable<String, Object> tagDataAttrs = new Hashtable<String, Object>(attrsSize);
+
+            checkXmlAttributes(n, jspAttrs, tagDataAttrs);
+            checkNamedAttributes(n, jspAttrs, attrsSize, tagDataAttrs);
+
+            TagData tagData = new TagData(tagDataAttrs);
+
+            // JSP.C1: It is a (translation time) error for an action that
+            // has one or more variable subelements to have a TagExtraInfo
+            // class that returns a non-null object.
+            TagExtraInfo tei = tagInfo.getTagExtraInfo();
+            if (tei != null && tei.getVariableInfo(tagData) != null
+                    && tei.getVariableInfo(tagData).length > 0
+                    && tagInfo.getTagVariableInfos().length > 0) {
+                err.jspError("jsp.error.non_null_tei_and_var_subelems", n
+                        .getQName());
+            }
+
+            n.setTagData(tagData);
+            n.setJspAttributes(jspAttrs);
+
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.JspElement n) throws JasperException {
+
+            Attributes attrs = n.getAttributes();
+            if (attrs == null) {
+                err.jspError(n, "jsp.error.jspelement.missing.name");
+            }
+            int xmlAttrLen = attrs.getLength();
+
+            Node.Nodes namedAttrs = n.getNamedAttributeNodes();
+
+            // XML-style 'name' attribute, which is mandatory, must not be
+            // included in JspAttribute array
+            int jspAttrSize = xmlAttrLen - 1 + namedAttrs.size();
+
+            Node.JspAttribute[] jspAttrs = new Node.JspAttribute[jspAttrSize];
+            int jspAttrIndex = 0;
+
+            // Process XML-style attributes
+            for (int i = 0; i < xmlAttrLen; i++) {
+                if ("name".equals(attrs.getLocalName(i))) {
+                    n.setNameAttribute(getJspAttribute(null, attrs.getQName(i),
+                            attrs.getURI(i), attrs.getLocalName(i), attrs
+                                    .getValue(i), n, false));
+                } else {
+                    if (jspAttrIndex < jspAttrSize) {
+                        jspAttrs[jspAttrIndex++] = getJspAttribute(null, attrs
+                                .getQName(i), attrs.getURI(i), attrs
+                                .getLocalName(i), attrs.getValue(i), n, false);
+                    }
+                }
+            }
+            if (n.getNameAttribute() == null) {
+                err.jspError(n, "jsp.error.jspelement.missing.name");
+            }
+
+            // Process named attributes
+            for (int i = 0; i < namedAttrs.size(); i++) {
+                Node.NamedAttribute na = (Node.NamedAttribute) namedAttrs
+                        .getNode(i);
+                jspAttrs[jspAttrIndex++] = new Node.JspAttribute(na, null,
+                        false);
+            }
+
+            n.setJspAttributes(jspAttrs);
+
+            visitBody(n);
+        }
+
+        @Override
+        public void visit(Node.JspOutput n) throws JasperException {
+            JspUtil.checkAttributes("jsp:output", n, jspOutputAttrs, err);
+
+            if (n.getBody() != null) {
+                err.jspError(n, "jsp.error.jspoutput.nonemptybody");
+            }
+
+            String omitXmlDecl = n.getAttributeValue("omit-xml-declaration");
+            String doctypeName = n.getAttributeValue("doctype-root-element");
+            String doctypePublic = n.getAttributeValue("doctype-public");
+            String doctypeSystem = n.getAttributeValue("doctype-system");
+
+            String omitXmlDeclOld = pageInfo.getOmitXmlDecl();
+            String doctypeNameOld = pageInfo.getDoctypeName();
+            String doctypePublicOld = pageInfo.getDoctypePublic();
+            String doctypeSystemOld = pageInfo.getDoctypeSystem();
+
+            if (omitXmlDecl != null && omitXmlDeclOld != null
+                    && !omitXmlDecl.equals(omitXmlDeclOld)) {
+                err.jspError(n, "jsp.error.jspoutput.conflict",
+                        "omit-xml-declaration", omitXmlDeclOld, omitXmlDecl);
+            }
+
+            if (doctypeName != null && doctypeNameOld != null
+                    && !doctypeName.equals(doctypeNameOld)) {
+                err.jspError(n, "jsp.error.jspoutput.conflict",
+                        "doctype-root-element", doctypeNameOld, doctypeName);
+            }
+
+            if (doctypePublic != null && doctypePublicOld != null
+                    && !doctypePublic.equals(doctypePublicOld)) {
+                err.jspError(n, "jsp.error.jspoutput.conflict",
+                        "doctype-public", doctypePublicOld, doctypePublic);
+            }
+
+            if (doctypeSystem != null && doctypeSystemOld != null
+                    && !doctypeSystem.equals(doctypeSystemOld)) {
+                err.jspError(n, "jsp.error.jspoutput.conflict",
+                        "doctype-system", doctypeSystemOld, doctypeSystem);
+            }
+
+            if (doctypeName == null && doctypeSystem != null
+                    || doctypeName != null && doctypeSystem == null) {
+                err.jspError(n, "jsp.error.jspoutput.doctypenamesystem");
+            }
+
+            if (doctypePublic != null && doctypeSystem == null) {
+                err.jspError(n, "jsp.error.jspoutput.doctypepulicsystem");
+            }
+
+            if (omitXmlDecl != null) {
+                pageInfo.setOmitXmlDecl(omitXmlDecl);
+            }
+            if (doctypeName != null) {
+                pageInfo.setDoctypeName(doctypeName);
+            }
+            if (doctypeSystem != null) {
+                pageInfo.setDoctypeSystem(doctypeSystem);
+            }
+            if (doctypePublic != null) {
+                pageInfo.setDoctypePublic(doctypePublic);
+            }
+        }
+
+        @Override
+        public void visit(Node.InvokeAction n) throws JasperException {
+
+            JspUtil.checkAttributes("Invoke", n, invokeAttrs, err);
+
+            String scope = n.getTextAttribute("scope");
+            JspUtil.checkScope(scope, n, err);
+
+            String var = n.getTextAttribute("var");
+            String varReader = n.getTextAttribute("varReader");
+            if (scope != null && var == null && varReader == null) {
+                err.jspError(n, "jsp.error.missing_var_or_varReader");
+            }
+            if (var != null && varReader != null) {
+                err.jspError(n, "jsp.error.var_and_varReader");
+            }
+        }
+
+        @Override
+        public void visit(Node.DoBodyAction n) throws JasperException {
+
+            JspUtil.checkAttributes("DoBody", n, doBodyAttrs, err);
+
+            String scope = n.getTextAttribute("scope");
+            JspUtil.checkScope(scope, n, err);
+
+            String var = n.getTextAttribute("var");
+            String varReader = n.getTextAttribute("varReader");
+            if (scope != null && var == null && varReader == null) {
+                err.jspError(n, "jsp.error.missing_var_or_varReader");
+            }
+            if (var != null && varReader != null) {
+                err.jspError(n, "jsp.error.var_and_varReader");
+            }
+        }
+
+        /*
+         * Make sure the given custom action does not have any invalid
+         * attributes.
+         * 
+         * A custom action and its declared attributes always belong to the same
+         * namespace, which is identified by the prefix name of the custom tag
+         * invocation. For example, in this invocation:
+         * 
+         * <my:test a="1" b="2" c="3"/>, the action
+         * 
+         * "test" and its attributes "a", "b", and "c" all belong to the
+         * namespace identified by the prefix "my". The above invocation would
+         * be equivalent to:
+         * 
+         * <my:test my:a="1" my:b="2" my:c="3"/>
+         * 
+         * An action attribute may have a prefix different from that of the
+         * action invocation only if the underlying tag handler supports dynamic
+         * attributes, in which case the attribute with the different prefix is
+         * considered a dynamic attribute.
+         */
+        private void checkXmlAttributes(Node.CustomTag n,
+                Node.JspAttribute[] jspAttrs, Hashtable<String, Object> tagDataAttrs)
+                throws JasperException {
+
+            TagInfo tagInfo = n.getTagInfo();
+            if (tagInfo == null) {
+                err.jspError(n, "jsp.error.missing.tagInfo", n.getQName());
+            }
+            TagAttributeInfo[] tldAttrs = tagInfo.getAttributes();
+            Attributes attrs = n.getAttributes();
+
+            for (int i = 0; attrs != null && i < attrs.getLength(); i++) {
+                boolean found = false;
+                
+                boolean runtimeExpression = ((n.getRoot().isXmlSyntax() && attrs.getValue(i).startsWith("%="))
+                        || (!n.getRoot().isXmlSyntax() && attrs.getValue(i).startsWith("<%=")));
+                boolean elExpression = false;
+                boolean deferred = false;
+                double libraryVersion = Double.parseDouble(
+                        tagInfo.getTagLibrary().getRequiredVersion());
+                boolean deferredSyntaxAllowedAsLiteral =
+                    pageInfo.isDeferredSyntaxAllowedAsLiteral() ||
+                    libraryVersion < 2.1;
+
+                ELNode.Nodes el = null;
+                if (!runtimeExpression && !pageInfo.isELIgnored()) {
+                    el = ELParser.parse(attrs.getValue(i),
+                            deferredSyntaxAllowedAsLiteral);
+                    Iterator<ELNode> nodes = el.iterator();
+                    while (nodes.hasNext()) {
+                        ELNode node = nodes.next();
+                        if (node instanceof ELNode.Root) {
+                            if (((ELNode.Root) node).getType() == '$') {
+                                if (elExpression && deferred) {
+                                    err.jspError(n,
+                                            "jsp.error.attribute.deferredmix");
+                                }
+                                elExpression = true;
+                            } else if (((ELNode.Root) node).getType() == '#') {
+                                if (elExpression && !deferred) {
+                                    err.jspError(n,
+                                            "jsp.error.attribute.deferredmix");
+                                }
+                                elExpression = true;
+                                deferred = true;
+                            }
+                        }
+                    }
+                }
+
+                boolean expression = runtimeExpression || elExpression;
+
+                for (int j = 0; tldAttrs != null && j < tldAttrs.length; j++) {
+                    if (attrs.getLocalName(i).equals(tldAttrs[j].getName())
+                            && (attrs.getURI(i) == null
+                                    || attrs.getURI(i).length() == 0 || attrs
+                                    .getURI(i).equals(n.getURI()))) {
+
+                        TagAttributeInfo tldAttr = tldAttrs[j];
+                        if (tldAttr.canBeRequestTime()
+                                || tldAttr.isDeferredMethod() || tldAttr.isDeferredValue()) { // JSP 2.1
+                            
+                            if (!expression) {
+
+                                String expectedType = null;
+                                if (tldAttr.isDeferredMethod()) {
+                                    // The String literal must be castable to what is declared as type
+                                    // for the attribute
+                                    String m = tldAttr.getMethodSignature();
+                                    if (m != null) {
+                                        m = m.trim();
+                                        int rti = m.indexOf(' ');
+                                        if (rti > 0) {
+                                            expectedType = m.substring(0, rti).trim();
+                                        }
+                                    } else {
+                                        expectedType = "java.lang.Object";
+                                    }
+                                    if ("void".equals(expectedType)) {
+                                        // Can't specify a literal for a
+                                        // deferred method with an expected type
+                                        // of void - JSP.2.3.4
+                                        err.jspError(n,
+                                                "jsp.error.literal_with_void",
+                                                tldAttr.getName());
+                                    }
+                                }
+                                if (tldAttr.isDeferredValue()) {
+                                    // The String literal must be castable to what is declared as type
+                                    // for the attribute
+                                    expectedType = tldAttr.getExpectedTypeName();
+                                }
+                                if (expectedType != null) {
+                                    Class<?> expectedClass = String.class;
+                                    try {
+                                        expectedClass = JspUtil.toClass(expectedType, loader);
+                                    } catch (ClassNotFoundException e) {
+                                        err.jspError
+                                            (n, "jsp.error.unknown_attribute_type",
+                                             tldAttr.getName(), expectedType);
+                                    }
+                                    // Check casting - not possible for all types
+                                    if (String.class.equals(expectedClass) ||
+                                            expectedClass == Long.TYPE ||
+                                            expectedClass == Double.TYPE ||
+                                            expectedClass == Byte.TYPE ||
+                                            expectedClass == Short.TYPE ||
+                                            expectedClass == Integer.TYPE ||
+                                            expectedClass == Float.TYPE ||
+                                            Number.class.isAssignableFrom(expectedClass) ||
+                                            Character.class.equals(expectedClass) ||
+                                            Character.TYPE == expectedClass ||
+                                            Boolean.class.equals(expectedClass) ||
+                                            Boolean.TYPE == expectedClass ||
+                                            expectedClass.isEnum()) {
+                                        try {
+                                            EXPRESSION_FACTORY.coerceToType(attrs.getValue(i), expectedClass);
+                                        } catch (Exception e) {
+                                            err.jspError
+                                                (n, "jsp.error.coerce_to_type",
+                                                 tldAttr.getName(), expectedType, attrs.getValue(i));
+                                        }
+                                    }
+                                }
+
+                                jspAttrs[i] = new Node.JspAttribute(tldAttr,
+                                        attrs.getQName(i), attrs.getURI(i), attrs
+                                                .getLocalName(i),
+                                        attrs.getValue(i), false, null, false);
+                            } else {
+
+                                if (deferred && !tldAttr.isDeferredMethod() && !tldAttr.isDeferredValue()) {
+                                    // No deferred expressions allowed for this attribute
+                                    err.jspError(n, "jsp.error.attribute.custom.non_rt_with_expr",
+                                            tldAttr.getName());
+                                }
+                                if (!deferred && !tldAttr.canBeRequestTime()) {
+                                    // Only deferred expressions are allowed for this attribute
+                                    err.jspError(n, "jsp.error.attribute.custom.non_rt_with_expr",
+                                            tldAttr.getName());
+                                }
+                                
+                                if (elExpression) {
+                                    // El expression
+                                    validateFunctions(el, n);
+                                    jspAttrs[i] = new Node.JspAttribute(tldAttr,
+                                            attrs.getQName(i), attrs.getURI(i), 
+                                            attrs.getLocalName(i),
+                                            attrs.getValue(i), false, el, false);
+                                    ELContextImpl ctx = new ELContextImpl();
+                                    ctx.setFunctionMapper(getFunctionMapper(el));
+                                    try {
+                                        jspAttrs[i].validateEL(this.pageInfo.getExpressionFactory(), ctx);
+                                    } catch (ELException e) {
+                                        this.err.jspError(n.getStart(),
+                                                "jsp.error.invalid.expression", 
+                                                attrs.getValue(i), e.toString());
+                                    }
+                                } else {
+                                    // Runtime expression
+                                    jspAttrs[i] = getJspAttribute(tldAttr,
+                                            attrs.getQName(i), attrs.getURI(i),
+                                            attrs.getLocalName(i), attrs
+                                            .getValue(i), n, false);
+                                }
+                            }
+                            
+                        } else {
+                            // Attribute does not accept any expressions.
+                            // Make sure its value does not contain any.
+                            if (expression) {
+                                err.jspError(n, "jsp.error.attribute.custom.non_rt_with_expr",
+                                                tldAttr.getName());
+                            }
+                            jspAttrs[i] = new Node.JspAttribute(tldAttr,
+                                    attrs.getQName(i), attrs.getURI(i), attrs
+                                            .getLocalName(i),
+                                    attrs.getValue(i), false, null, false);
+                        }
+                        if (expression) {
+                            tagDataAttrs.put(attrs.getQName(i),
+                                    TagData.REQUEST_TIME_VALUE);
+                        } else {
+                            tagDataAttrs.put(attrs.getQName(i), attrs
+                                    .getValue(i));
+                        }
+                        found = true;
+                        break;
+                    }
+                }
+                if (!found) {
+                    if (tagInfo.hasDynamicAttributes()) {
+                        jspAttrs[i] = getJspAttribute(null, attrs.getQName(i),
+                                attrs.getURI(i), attrs.getLocalName(i), attrs
+                                        .getValue(i), n, true);
+                    } else {
+                        err.jspError(n, "jsp.error.bad_attribute", attrs
+                                .getQName(i), n.getLocalName());
+                    }
+                }
+            }
+        }
+
+        /*
+         * Make sure the given custom action does not have any invalid named
+         * attributes
+         */
+        private void checkNamedAttributes(Node.CustomTag n,
+                Node.JspAttribute[] jspAttrs, int start, 
+                Hashtable<String, Object> tagDataAttrs)
+                throws JasperException {
+
+            TagInfo tagInfo = n.getTagInfo();
+            if (tagInfo == null) {
+                err.jspError(n, "jsp.error.missing.tagInfo", n.getQName());
+            }
+            TagAttributeInfo[] tldAttrs = tagInfo.getAttributes();
+            Node.Nodes naNodes = n.getNamedAttributeNodes();
+
+            for (int i = 0; i < naNodes.size(); i++) {
+                Node.NamedAttribute na = (Node.NamedAttribute) naNodes
+                        .getNode(i);
+                boolean found = false;
+                for (int j = 0; j < tldAttrs.length; j++) {
+                    /*
+                     * See above comment about namespace matches. For named
+                     * attributes, we use the prefix instead of URI as the match
+                     * criterion, because in the case of a JSP document, we'd
+                     * have to keep track of which namespaces are in scope when
+                     * parsing a named attribute, in order to determine the URI
+                     * that the prefix of the named attribute's name matches to.
+                     */
+                    String attrPrefix = na.getPrefix();
+                    if (na.getLocalName().equals(tldAttrs[j].getName())
+                            && (attrPrefix == null || attrPrefix.length() == 0 || attrPrefix
+                                    .equals(n.getPrefix()))) {
+                        jspAttrs[start + i] = new Node.JspAttribute(na,
+                                tldAttrs[j], false);
+                        NamedAttributeVisitor nav = null;
+                        if (na.getBody() != null) {
+                            nav = new NamedAttributeVisitor();
+                            na.getBody().visit(nav);
+                        }
+                        if (nav != null && nav.hasDynamicContent()) {
+                            tagDataAttrs.put(na.getName(),
+                                    TagData.REQUEST_TIME_VALUE);
+                        } else {
+                            tagDataAttrs.put(na.getName(), na.getText());
+                        }
+                        found = true;
+                        break;
+                    }
+                }
+                if (!found) {
+                    if (tagInfo.hasDynamicAttributes()) {
+                        jspAttrs[start + i] = new Node.JspAttribute(na, null,
+                                true);
+                    } else {
+                        err.jspError(n, "jsp.error.bad_attribute",
+                                na.getName(), n.getLocalName());
+                    }
+                }
+            }
+        }
+
+        /**
+         * Preprocess attributes that can be expressions. Expression delimiters
+         * are stripped.
+         * <p>
+         * If value is null, checks if there are any NamedAttribute subelements
+         * in the tree node, and if so, constructs a JspAttribute out of a child
+         * NamedAttribute node.
+         */
+        private Node.JspAttribute getJspAttribute(TagAttributeInfo tai,
+                String qName, String uri, String localName, String value,
+                Node n, boolean dynamic)
+                throws JasperException {
+
+            Node.JspAttribute result = null;
+
+            // XXX Is it an error to see "%=foo%" in non-Xml page?
+            // (We won't see "<%=foo%> in xml page because '<' is not a
+            // valid attribute value in xml).
+
+            if (value != null) {
+                if (n.getRoot().isXmlSyntax() && value.startsWith("%=")) {
+                    result = new Node.JspAttribute(tai, qName, uri, localName,
+                            value.substring(2, value.length() - 1), true, null,
+                            dynamic);
+                } else if (!n.getRoot().isXmlSyntax()
+                        && value.startsWith("<%=")) {
+                    result = new Node.JspAttribute(tai, qName, uri, localName,
+                            value.substring(3, value.length() - 2), true, null,
+                            dynamic);
+                } else if (pageInfo.isELIgnored()) {
+                    result = new Node.JspAttribute(tai, qName, uri, localName,
+                            value, false, null, dynamic);
+                } else {
+                    // The attribute can contain expressions but is not a
+                    // scriptlet expression; thus, we want to run it through
+                    // the expression interpreter
+
+                    // validate expression syntax if string contains
+                    // expression(s)
+                    ELNode.Nodes el = ELParser.parse(value, pageInfo
+                            .isDeferredSyntaxAllowedAsLiteral());
+
+                    if (el.containsEL()) {
+
+                        validateFunctions(el, n);
+
+                        result = new Node.JspAttribute(tai, qName, uri,
+                                localName, value, false, el, dynamic);
+
+                        ELContextImpl ctx = new ELContextImpl();
+                        ctx.setFunctionMapper(getFunctionMapper(el));
+
+                        try {
+                            result.validateEL(this.pageInfo
+                                    .getExpressionFactory(), ctx);
+                        } catch (ELException e) {
+                            this.err.jspError(n.getStart(),
+                                    "jsp.error.invalid.expression", value, e
+                                            .toString());
+                        }
+
+                    } else {
+                        result = new Node.JspAttribute(tai, qName, uri,
+                                localName, value, false, null, dynamic);
+                    }
+                }
+            } else {
+                // Value is null. Check for any NamedAttribute subnodes
+                // that might contain the value for this attribute.
+                // Otherwise, the attribute wasn't found so we return null.
+
+                Node.NamedAttribute namedAttributeNode = n
+                        .getNamedAttributeNode(qName);
+                if (namedAttributeNode != null) {
+                    result = new Node.JspAttribute(namedAttributeNode, tai,
+                            dynamic);
+                }
+            }
+
+            return result;
+        }
+
+        /*
+         * Return an empty StringBuilder [not thread-safe]
+         */
+        private StringBuilder getBuffer() {
+            this.buf.setLength(0);
+            return this.buf;
+        }
+
+        /*
+         * Checks to see if the given attribute value represents a runtime or EL
+         * expression.
+         */
+        private boolean isExpression(Node n, String value, boolean checkDeferred) {
+            
+            boolean runtimeExpression = ((n.getRoot().isXmlSyntax() && value.startsWith("%="))
+                    || (!n.getRoot().isXmlSyntax() && value.startsWith("<%=")));
+            boolean elExpression = false;
+
+            if (!runtimeExpression && !pageInfo.isELIgnored()) {
+                Iterator<ELNode> nodes = ELParser.parse(value,
+                        pageInfo.isDeferredSyntaxAllowedAsLiteral()).iterator();
+                while (nodes.hasNext()) {
+                    ELNode node = nodes.next();
+                    if (node instanceof ELNode.Root) {
+                        if (((ELNode.Root) node).getType() == '$') {
+                            elExpression = true;
+                        } else if (checkDeferred && !pageInfo.isDeferredSyntaxAllowedAsLiteral() 
+                                && ((ELNode.Root) node).getType() == '#') {
+                            elExpression = true;
+                        }
+                    }
+                }
+            }
+
+            return runtimeExpression || elExpression;
+
+        }
+
+        /*
+         * Throws exception if the value of the attribute with the given name in
+         * the given node is given as an RT or EL expression, but the spec
+         * requires a static value.
+         */
+        private void throwErrorIfExpression(Node n, String attrName,
+                String actionName) throws JasperException {
+            if (n.getAttributes() != null
+                    && n.getAttributes().getValue(attrName) != null
+                    && isExpression(n, n.getAttributes().getValue(attrName), true)) {
+                err.jspError(n,
+                        "jsp.error.attribute.standard.non_rt_with_expr",
+                        attrName, actionName);
+            }
+        }
+
+        private static class NamedAttributeVisitor extends Node.Visitor {
+            private boolean hasDynamicContent;
+
+            @Override
+            public void doVisit(Node n) throws JasperException {
+                if (!(n instanceof Node.JspText)
+                        && !(n instanceof Node.TemplateText)) {
+                    hasDynamicContent = true;
+                }
+                visitBody(n);
+            }
+
+            public boolean hasDynamicContent() {
+                return hasDynamicContent;
+            }
+        }
+
+        private String findUri(String prefix, Node n) {
+
+            for (Node p = n; p != null; p = p.getParent()) {
+                Attributes attrs = p.getTaglibAttributes();
+                if (attrs == null) {
+                    continue;
+                }
+                for (int i = 0; i < attrs.getLength(); i++) {
+                    String name = attrs.getQName(i);
+                    int k = name.indexOf(':');
+                    if (prefix == null && k < 0) {
+                        // prefix not specified and a default ns found
+                        return attrs.getValue(i);
+                    }
+                    if (prefix != null && k >= 0
+                            && prefix.equals(name.substring(k + 1))) {
+                        return attrs.getValue(i);
+                    }
+                }
+            }
+            return null;
+        }
+
+        /**
+         * Validate functions in EL expressions
+         */
+        private void validateFunctions(ELNode.Nodes el, Node n)
+                throws JasperException {
+
+            class FVVisitor extends ELNode.Visitor {
+
+                Node n;
+
+                FVVisitor(Node n) {
+                    this.n = n;
+                }
+
+                @Override
+                public void visit(ELNode.Function func) throws JasperException {
+                    String prefix = func.getPrefix();
+                    String function = func.getName();
+                    String uri = null;
+
+                    if (n.getRoot().isXmlSyntax()) {
+                        uri = findUri(prefix, n);
+                    } else if (prefix != null) {
+                        uri = pageInfo.getURI(prefix);
+                    }
+
+                    if (uri == null) {
+                        if (prefix == null) {
+                            err.jspError(n, "jsp.error.noFunctionPrefix",
+                                    function);
+                        } else {
+                            err.jspError(n, "jsp.error.attribute.invalidPrefix",
+                                    prefix);
+                        }
+                    }
+                    TagLibraryInfo taglib = pageInfo.getTaglib(uri);
+                    FunctionInfo funcInfo = null;
+                    if (taglib != null) {
+                        funcInfo = taglib.getFunction(function);
+                    }
+                    if (funcInfo == null) {
+                        err.jspError(n, "jsp.error.noFunction", function);
+                    }
+                    // Skip TLD function uniqueness check. Done by Schema ?
+                    func.setUri(uri);
+                    func.setFunctionInfo(funcInfo);
+                    processSignature(func);
+                }
+            }
+
+            el.visit(new FVVisitor(n));
+        }
+
+        private void prepareExpression(ELNode.Nodes el, Node n, String expr)
+                throws JasperException {
+            validateFunctions(el, n);
+
+            // test it out
+            ELContextImpl ctx = new ELContextImpl();
+            ctx.setFunctionMapper(this.getFunctionMapper(el));
+            ExpressionFactory ef = this.pageInfo.getExpressionFactory();
+            try {
+                ef.createValueExpression(ctx, expr, Object.class);
+            } catch (ELException e) {
+
+            }
+        }
+
+        private void processSignature(ELNode.Function func)
+                throws JasperException {
+            func.setMethodName(getMethod(func));
+            func.setParameters(getParameters(func));
+        }
+
+        /**
+         * Get the method name from the signature.
+         */
+        private String getMethod(ELNode.Function func) throws JasperException {
+            FunctionInfo funcInfo = func.getFunctionInfo();
+            String signature = funcInfo.getFunctionSignature();
+
+            int start = signature.indexOf(' ');
+            if (start < 0) {
+                err.jspError("jsp.error.tld.fn.invalid.signature", func
+                        .getPrefix(), func.getName());
+            }
+            int end = signature.indexOf('(');
+            if (end < 0) {
+                err.jspError(
+                        "jsp.error.tld.fn.invalid.signature.parenexpected",
+                        func.getPrefix(), func.getName());
+            }
+            return signature.substring(start + 1, end).trim();
+        }
+
+        /**
+         * Get the parameters types from the function signature.
+         * 
+         * @return An array of parameter class names
+         */
+        private String[] getParameters(ELNode.Function func)
+                throws JasperException {
+            FunctionInfo funcInfo = func.getFunctionInfo();
+            String signature = funcInfo.getFunctionSignature();
+            ArrayList<String> params = new ArrayList<String>();
+            // Signature is of the form
+            // <return-type> S <method-name S? '('
+            // < <arg-type> ( ',' <arg-type> )* )? ')'
+            int start = signature.indexOf('(') + 1;
+            boolean lastArg = false;
+            while (true) {
+                int p = signature.indexOf(',', start);
+                if (p < 0) {
+                    p = signature.indexOf(')', start);
+                    if (p < 0) {
+                        err.jspError("jsp.error.tld.fn.invalid.signature", func
+                                .getPrefix(), func.getName());
+                    }
+                    lastArg = true;
+                }
+                String arg = signature.substring(start, p).trim();
+                if (!"".equals(arg)) {
+                    params.add(arg);
+                }
+                if (lastArg) {
+                    break;
+                }
+                start = p + 1;
+            }
+            return params.toArray(new String[params.size()]);
+        }
+
+        private FunctionMapper getFunctionMapper(ELNode.Nodes el)
+                throws JasperException {
+
+            class ValidateFunctionMapper extends FunctionMapper {
+
+                private HashMap<String, Method> fnmap = new HashMap<String, Method>();
+
+                public void mapFunction(String fnQName, Method method) {
+                    fnmap.put(fnQName, method);
+                }
+
+                @Override
+                public Method resolveFunction(String prefix, String localName) {
+                    return this.fnmap.get(prefix + ":" + localName);
+                }
+            }
+
+            class MapperELVisitor extends ELNode.Visitor {
+                ValidateFunctionMapper fmapper;
+
+                MapperELVisitor(ValidateFunctionMapper fmapper) {
+                    this.fmapper = fmapper;
+                }
+
+                @Override
+                public void visit(ELNode.Function n) throws JasperException {
+
+                    Class<?> c = null;
+                    Method method = null;
+                    try {
+                        c = loader.loadClass(n.getFunctionInfo()
+                                .getFunctionClass());
+                    } catch (ClassNotFoundException e) {
+                        err.jspError("jsp.error.function.classnotfound", n
+                                .getFunctionInfo().getFunctionClass(), n
+                                .getPrefix()
+                                + ':' + n.getName(), e.getMessage());
+                    }
+                    String paramTypes[] = n.getParameters();
+                    int size = paramTypes.length;
+                    Class<?> params[] = new Class[size];
+                    int i = 0;
+                    try {
+                        for (i = 0; i < size; i++) {
+                            params[i] = JspUtil.toClass(paramTypes[i], loader);
+                        }
+                        method = c.getDeclaredMethod(n.getMethodName(), params);
+                    } catch (ClassNotFoundException e) {
+                        err.jspError("jsp.error.signature.classnotfound",
+                                paramTypes[i], n.getPrefix() + ':'
+                                        + n.getName(), e.getMessage());
+                    } catch (NoSuchMethodException e) {
+                        err.jspError("jsp.error.noFunctionMethod", n
+                                .getMethodName(), n.getName(), c.getName());
+                    }
+                    fmapper.mapFunction(n.getPrefix() + ':' + n.getName(),
+                            method);
+                }
+            }
+
+            ValidateFunctionMapper fmapper = new ValidateFunctionMapper();
+            el.visit(new MapperELVisitor(fmapper));
+            return fmapper;
+        }
+    } // End of ValidateVisitor
+
+    /**
+     * A visitor for validating TagExtraInfo classes of all tags
+     */
+    static class TagExtraInfoVisitor extends Node.Visitor {
+
+        private ErrorDispatcher err;
+
+        /*
+         * Constructor
+         */
+        TagExtraInfoVisitor(Compiler compiler) {
+            this.err = compiler.getErrorDispatcher();
+        }
+
+        @Override
+        public void visit(Node.CustomTag n) throws JasperException {
+            TagInfo tagInfo = n.getTagInfo();
+            if (tagInfo == null) {
+                err.jspError(n, "jsp.error.missing.tagInfo", n.getQName());
+            }
+
+            ValidationMessage[] errors = tagInfo.validate(n.getTagData());
+            if (errors != null && errors.length != 0) {
+                StringBuilder errMsg = new StringBuilder();
+                errMsg.append("<h3>");
+                errMsg.append(Localizer.getMessage(
+                        "jsp.error.tei.invalid.attributes", n.getQName()));
+                errMsg.append("</h3>");
+                for (int i = 0; i < errors.length; i++) {
+                    errMsg.append("<p>");
+                    if (errors[i].getId() != null) {
+                        errMsg.append(errors[i].getId());
+                        errMsg.append(": ");
+                    }
+                    errMsg.append(errors[i].getMessage());
+                    errMsg.append("</p>");
+                }
+
+                err.jspError(n, errMsg.toString());
+            }
+
+            visitBody(n);
+        }
+    }
+
+    public static void validateDirectives(Compiler compiler, Node.Nodes page)
+            throws JasperException {
+        page.visit(new DirectiveVisitor(compiler));
+    }
+
+    public static void validateExDirectives(Compiler compiler, Node.Nodes page)
+        throws JasperException {
+        // Determine the default output content type
+        PageInfo pageInfo = compiler.getPageInfo();
+        String contentType = pageInfo.getContentType();
+
+        if (contentType == null || contentType.indexOf("charset=") < 0) {
+            boolean isXml = page.getRoot().isXmlSyntax();
+            String defaultType;
+            if (contentType == null) {
+                defaultType = isXml ? "text/xml" : "text/html";
+            } else {
+                defaultType = contentType;
+            }
+
+            String charset = null;
+            if (isXml) {
+                charset = "UTF-8";
+            } else {
+                if (!page.getRoot().isDefaultPageEncoding()) {
+                    charset = page.getRoot().getPageEncoding();
+                }
+            }
+
+            if (charset != null) {
+                pageInfo.setContentType(defaultType + ";charset=" + charset);
+            } else {
+                pageInfo.setContentType(defaultType);
+            }
+        }
+
+        /*
+         * Validate all other nodes. This validation step includes checking a
+         * custom tag's mandatory and optional attributes against information in
+         * the TLD (first validation step for custom tags according to
+         * JSP.10.5).
+         */
+        page.visit(new ValidateVisitor(compiler));
+
+        /*
+         * Invoke TagLibraryValidator classes of all imported tags (second
+         * validation step for custom tags according to JSP.10.5).
+         */
+        validateXmlView(new PageDataImpl(page, compiler), compiler);
+
+        /*
+         * Invoke TagExtraInfo method isValid() for all imported tags (third
+         * validation step for custom tags according to JSP.10.5).
+         */
+        page.visit(new TagExtraInfoVisitor(compiler));
+
+    }
+
+    // *********************************************************************
+    // Private (utility) methods
+
+    /**
+     * Validate XML view against the TagLibraryValidator classes of all imported
+     * tag libraries.
+     */
+    private static void validateXmlView(PageData xmlView, Compiler compiler)
+            throws JasperException {
+
+        StringBuilder errMsg = null;
+        ErrorDispatcher errDisp = compiler.getErrorDispatcher();
+
+        for (Iterator<TagLibraryInfo> iter =
+            compiler.getPageInfo().getTaglibs().iterator(); iter.hasNext();) {
+
+            Object o = iter.next();
+            if (!(o instanceof TagLibraryInfoImpl))
+                continue;
+            TagLibraryInfoImpl tli = (TagLibraryInfoImpl) o;
+
+            ValidationMessage[] errors = tli.validate(xmlView);
+            if ((errors != null) && (errors.length != 0)) {
+                if (errMsg == null) {
+                    errMsg = new StringBuilder();
+                }
+                errMsg.append("<h3>");
+                errMsg.append(Localizer.getMessage(
+                        "jsp.error.tlv.invalid.page", tli.getShortName(),
+                        compiler.getPageInfo().getJspFile()));
+                errMsg.append("</h3>");
+                for (int i = 0; i < errors.length; i++) {
+                    if (errors[i] != null) {
+                        errMsg.append("<p>");
+                        errMsg.append(errors[i].getId());
+                        errMsg.append(": ");
+                        errMsg.append(errors[i].getMessage());
+                        errMsg.append("</p>");
+                    }
+                }
+            }
+        }
+
+        if (errMsg != null) {
+            errDisp.jspError(errMsg.toString());
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/WebXml.java b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/WebXml.java
new file mode 100644
index 0000000..1414e63
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/compiler/WebXml.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.jasper.compiler;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.servlet.ServletContext;
+
+import org.apache.jasper.Constants;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.xml.sax.InputSource;
+
+/**
+ * Provides Jasper with a standard mechanism for gaining access to the web.xml
+ * file associated with the current application. This isn't as simple as looking
+ * in /WEB-INF/web.xml. In embedded scenarios, an alternative web.xml may be
+ * provided and in Servlet 3.0 / JSP 2.2 environments an application's web.xml
+ * may be the result of merging a number of web-fragment.xml files and/or
+ * annotations with the main web.xml
+ * 
+ * Clients *must* ensure that they call {@link #close()} to clean up resources. 
+ */
+public class WebXml {
+    private static final String FILE_PROTOCOL = "file:";
+    private static final String WEB_XML = "/WEB-INF/web.xml";
+
+    private final Log log = LogFactory.getLog(WebXml.class);
+            
+    private InputStream stream;
+    private InputSource source;
+    private String systemId;
+
+    public WebXml(ServletContext ctxt) throws IOException {
+        // Is a web.xml provided as context attribute?
+        String webXml = (String) ctxt.getAttribute(
+                org.apache.tomcat.util.scan.Constants.MERGED_WEB_XML);
+        if (webXml != null) {
+            source = new InputSource(new StringReader(webXml));
+            systemId = org.apache.tomcat.util.scan.Constants.MERGED_WEB_XML;
+        }
+        
+        // If not available as context attribute, look for an alternative
+        // location
+        if (source == null) {
+            // Acquire input stream to web application deployment descriptor
+            String altDDName = (String)ctxt.getAttribute(
+                                                    Constants.ALT_DD_ATTR);
+            if (altDDName != null) {
+                try {
+                    URL uri =
+                        new URL(FILE_PROTOCOL+altDDName.replace('\\', '/'));
+                    stream = uri.openStream();
+                    source = new InputSource(stream);
+                    systemId = uri.toExternalForm();
+                } catch (MalformedURLException e) {
+                    log.warn(Localizer.getMessage(
+                            "jsp.error.internal.filenotfound",
+                            altDDName));
+                }
+            }
+        }
+        
+        // Finally, try the default /WEB-INF/web.xml
+        if (source == null) {
+            URL uri = ctxt.getResource(WEB_XML);
+            if (uri == null) {
+                log.warn(Localizer.getMessage(
+                        "jsp.error.internal.filenotfound", WEB_XML));
+            } else {
+                stream = uri.openStream();
+                source = new InputSource(stream);
+                systemId = uri.toExternalForm();
+            }
+        }
+
+        if (source == null) {
+            systemId = null;
+        } else {
+            source.setSystemId(systemId);
+        }
+    }
+    
+    public String getSystemId() {
+        return systemId;
+    }
+
+    public InputSource getInputSource() {
+        return source;
+    }
+
+    public void close() {
+        if (stream != null) {
+            try {
+                stream.close();
+            } catch (IOException e) {
+                log.error(Localizer.getMessage(
+                        "jsp.error.stream.close.failed"));
+            }
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/ASCIIReader.java b/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/ASCIIReader.java
new file mode 100644
index 0000000..0d66bfe
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/ASCIIReader.java
@@ -0,0 +1,205 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.xmlparser;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+import org.apache.jasper.compiler.Localizer;
+
+/**
+ * A simple ASCII byte reader. This is an optimized reader for reading
+ * byte streams that only contain 7-bit ASCII characters.
+ *
+ * @author Andy Clark, IBM
+ *
+ * @version $Id: ASCIIReader.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ */
+public class ASCIIReader extends Reader {
+
+    //
+    // Data
+    //
+
+    /** Input stream. */
+    protected InputStream fInputStream;
+
+    /** Byte buffer. */
+    protected byte[] fBuffer;
+
+    //
+    // Constructors
+    //
+
+    /** 
+     * Constructs an ASCII reader from the specified input stream 
+     * and buffer size.
+     *
+     * @param inputStream The input stream.
+     * @param size        The initial buffer size.
+     */
+    public ASCIIReader(InputStream inputStream, int size) {
+        fInputStream = inputStream;
+        fBuffer = new byte[size];
+    }
+
+    //
+    // Reader methods
+    //
+
+    /**
+     * Read a single character.  This method will block until a character is
+     * available, an I/O error occurs, or the end of the stream is reached.
+     *
+     * <p> Subclasses that intend to support efficient single-character input
+     * should override this method.
+     *
+     * @return     The character read, as an integer in the range 0 to 127
+     *             (<tt>0x00-0x7f</tt>), or -1 if the end of the stream has
+     *             been reached
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    @Override
+    public int read() throws IOException {
+        int b0 = fInputStream.read();
+        if (b0 > 0x80) {
+            throw new IOException(Localizer.getMessage("jsp.error.xml.invalidASCII",
+                                                       Integer.toString(b0)));
+        }
+        return b0;
+    } // read():int
+
+    /**
+     * Read characters into a portion of an array.  This method will block
+     * until some input is available, an I/O error occurs, or the end of the
+     * stream is reached.
+     *
+     * @param      ch     Destination buffer
+     * @param      offset Offset at which to start storing characters
+     * @param      length Maximum number of characters to read
+     *
+     * @return     The number of characters read, or -1 if the end of the
+     *             stream has been reached
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    @Override
+    public int read(char ch[], int offset, int length) throws IOException {
+        if (length > fBuffer.length) {
+            length = fBuffer.length;
+        }
+        int count = fInputStream.read(fBuffer, 0, length);
+        for (int i = 0; i < count; i++) {
+            int b0 = (0xff & fBuffer[i]); // Convert to unsigned
+            if (b0 > 0x80) {
+                throw new IOException(Localizer.getMessage("jsp.error.xml.invalidASCII",
+                                                           Integer.toString(b0)));
+            }
+            ch[offset + i] = (char)b0;
+        }
+        return count;
+    } // read(char[],int,int)
+
+    /**
+     * Skip characters.  This method will block until some characters are
+     * available, an I/O error occurs, or the end of the stream is reached.
+     *
+     * @param  n  The number of characters to skip
+     *
+     * @return    The number of characters actually skipped
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    @Override
+    public long skip(long n) throws IOException {
+        return fInputStream.skip(n);
+    } // skip(long):long
+
+    /**
+     * Tell whether this stream is ready to be read.
+     *
+     * @return True if the next read() is guaranteed not to block for input,
+     * false otherwise.  Note that returning false does not guarantee that the
+     * next read will block.
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    @Override
+    public boolean ready() throws IOException {
+        return false;
+    } // ready()
+
+    /**
+     * Tell whether this stream supports the mark() operation.
+     */
+    @Override
+    public boolean markSupported() {
+        return fInputStream.markSupported();
+    } // markSupported()
+
+    /**
+     * Mark the present position in the stream.  Subsequent calls to reset()
+     * will attempt to reposition the stream to this point.  Not all
+     * character-input streams support the mark() operation.
+     *
+     * @param  readAheadLimit  Limit on the number of characters that may be
+     *                         read while still preserving the mark.  After
+     *                         reading this many characters, attempting to
+     *                         reset the stream may fail.
+     *
+     * @exception  IOException  If the stream does not support mark(),
+     *                          or if some other I/O error occurs
+     */
+    @Override
+    public void mark(int readAheadLimit) throws IOException {
+        fInputStream.mark(readAheadLimit);
+    } // mark(int)
+
+    /**
+     * Reset the stream.  If the stream has been marked, then attempt to
+     * reposition it at the mark.  If the stream has not been marked, then
+     * attempt to reset it in some way appropriate to the particular stream,
+     * for example by repositioning it to its starting point.  Not all
+     * character-input streams support the reset() operation, and some support
+     * reset() without supporting mark().
+     *
+     * @exception  IOException  If the stream has not been marked,
+     *                          or if the mark has been invalidated,
+     *                          or if the stream does not support reset(),
+     *                          or if some other I/O error occurs
+     */
+    @Override
+    public void reset() throws IOException {
+        fInputStream.reset();
+    } // reset()
+
+    /**
+     * Close the stream.  Once a stream has been closed, further read(),
+     * ready(), mark(), or reset() invocations will throw an IOException.
+     * Closing a previously-closed stream, however, has no effect.
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+     @Override
+    public void close() throws IOException {
+         fInputStream.close();
+     } // close()
+
+} // class ASCIIReader
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/EncodingMap.java b/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/EncodingMap.java
new file mode 100644
index 0000000..911c848
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/EncodingMap.java
@@ -0,0 +1,1022 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation and was
+ * originally based on software copyright (c) 1999, International
+ * Business Machines, Inc., http://www.apache.org.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.jasper.xmlparser;
+
+import java.util.Hashtable;
+
+/**
+ * EncodingMap is a convenience class which handles conversions between 
+ * IANA encoding names and Java encoding names, and vice versa. The
+ * encoding names used in XML instance documents <strong>must</strong>
+ * be the IANA encoding names specified or one of the aliases for those names
+ * which IANA defines.
+ * <p>
+ * <TABLE BORDER="0" WIDTH="100%">
+ *  <TR>
+ *      <TD WIDTH="33%">
+ *          <P ALIGN="CENTER"><B>Common Name</B>
+ *      </TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER"><B>Use this name in XML files</B>
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER"><B>Name Type</B>
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER"><B>Xerces converts to this Java Encoder Name</B>
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">8 bit Unicode</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">UTF-8
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">UTF8
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin 1</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-1
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-1
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin 2</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-2
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-2
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin 3</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-3
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-3
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin 4</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-4
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-4
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin Cyrillic</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-5
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-5
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin Arabic</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-6
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-6
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin Greek</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-7
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-7
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin Hebrew</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-8
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-8
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">ISO Latin 5</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ISO-8859-9
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">ISO-8859-9
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: US</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-us
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp037
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Canada</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-ca
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp037
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Netherlands</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-nl
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp037
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Denmark</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-dk
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp277
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Norway</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-no
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp277
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Finland</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-fi
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp278
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Sweden</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-se
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp278
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Italy</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-it
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp280
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Spain, Latin America</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-es
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp284
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Great Britain</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-gb
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp285
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: France</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-fr
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp297
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Arabic</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-ar1
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp420
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Hebrew</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-he
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp424
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Switzerland</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-ch
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp500
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Roece</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-roece
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp870
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Yugoslavia</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-yu
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp870
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Iceland</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-is
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp871
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">EBCDIC: Urdu</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">ebcdic-cp-ar2
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">IANA
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">cp918
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">Chinese for PRC, mixed 1/2 byte</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">gb2312
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">GB2312
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">Extended Unix Code, packed for Japanese</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">euc-jp
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">eucjis
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">Japanese: iso-2022-jp</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">iso-2020-jp
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">JIS
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">Japanese: Shift JIS</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">Shift_JIS
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">SJIS
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">Chinese: Big5</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">Big5
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">Big5
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">Extended Unix Code, packed for Korean</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">euc-kr
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">iso2022kr
+ *      </TD>
+ *  </TR>
+ *  <TR>
+ *      <TD WIDTH="33%">Cyrillic</TD>
+ *      <TD WIDTH="15%">
+ *          <P ALIGN="CENTER">koi8-r
+ *      </TD>
+ *      <TD WIDTH="12%">
+ *          <P ALIGN="CENTER">MIME
+ *      </TD>
+ *      <TD WIDTH="31%">
+ *          <P ALIGN="CENTER">koi8-r
+ *      </TD>
+ *  </TR>
+ * </TABLE>
+ * 
+ * @author TAMURA Kent, IBM
+ * @author Andy Clark, IBM
+ *
+ * @version $Id: EncodingMap.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ */
+public class EncodingMap {
+
+    //
+    // Data
+    //
+
+    /** fIANA2JavaMap */
+    protected static final Hashtable<String,String> fIANA2JavaMap =
+        new Hashtable<String,String>();
+
+    /** fJava2IANAMap */
+    protected static final Hashtable<String,String> fJava2IANAMap =
+        new Hashtable<String,String>();
+
+    //
+    // Static initialization
+    //
+
+    static {
+
+        // add IANA to Java encoding mappings.
+        fIANA2JavaMap.put("BIG5",            "Big5");
+        fIANA2JavaMap.put("CSBIG5",            "Big5");
+        fIANA2JavaMap.put("CP037",    "CP037");
+        fIANA2JavaMap.put("IBM037",    "CP037");
+        fIANA2JavaMap.put("CSIBM037",    "CP037");
+        fIANA2JavaMap.put("EBCDIC-CP-US",    "CP037");
+        fIANA2JavaMap.put("EBCDIC-CP-CA",    "CP037");
+        fIANA2JavaMap.put("EBCDIC-CP-NL",    "CP037");
+        fIANA2JavaMap.put("EBCDIC-CP-WT",    "CP037");
+        fIANA2JavaMap.put("IBM273",    "CP273");
+        fIANA2JavaMap.put("CP273",    "CP273");
+        fIANA2JavaMap.put("CSIBM273",    "CP273");
+        fIANA2JavaMap.put("IBM277",    "CP277");
+        fIANA2JavaMap.put("CP277",    "CP277");
+        fIANA2JavaMap.put("CSIBM277",    "CP277");
+        fIANA2JavaMap.put("EBCDIC-CP-DK",    "CP277");
+        fIANA2JavaMap.put("EBCDIC-CP-NO",    "CP277");
+        fIANA2JavaMap.put("IBM278",    "CP278");
+        fIANA2JavaMap.put("CP278",    "CP278");
+        fIANA2JavaMap.put("CSIBM278",    "CP278");
+        fIANA2JavaMap.put("EBCDIC-CP-FI",    "CP278");
+        fIANA2JavaMap.put("EBCDIC-CP-SE",    "CP278");
+        fIANA2JavaMap.put("IBM280",    "CP280");
+        fIANA2JavaMap.put("CP280",    "CP280");
+        fIANA2JavaMap.put("CSIBM280",    "CP280");
+        fIANA2JavaMap.put("EBCDIC-CP-IT",    "CP280");
+        fIANA2JavaMap.put("IBM284",    "CP284");
+        fIANA2JavaMap.put("CP284",    "CP284");
+        fIANA2JavaMap.put("CSIBM284",    "CP284");
+        fIANA2JavaMap.put("EBCDIC-CP-ES",    "CP284");
+        fIANA2JavaMap.put("EBCDIC-CP-GB",    "CP285");
+        fIANA2JavaMap.put("IBM285",    "CP285");
+        fIANA2JavaMap.put("CP285",    "CP285");
+        fIANA2JavaMap.put("CSIBM285",    "CP285");
+        fIANA2JavaMap.put("EBCDIC-JP-KANA",    "CP290");
+        fIANA2JavaMap.put("IBM290",    "CP290");
+        fIANA2JavaMap.put("CP290",    "CP290");
+        fIANA2JavaMap.put("CSIBM290",    "CP290");
+        fIANA2JavaMap.put("EBCDIC-CP-FR",    "CP297");
+        fIANA2JavaMap.put("IBM297",    "CP297");
+        fIANA2JavaMap.put("CP297",    "CP297");
+        fIANA2JavaMap.put("CSIBM297",    "CP297");
+        fIANA2JavaMap.put("EBCDIC-CP-AR1",   "CP420");
+        fIANA2JavaMap.put("IBM420",    "CP420");
+        fIANA2JavaMap.put("CP420",    "CP420");
+        fIANA2JavaMap.put("CSIBM420",    "CP420");
+        fIANA2JavaMap.put("EBCDIC-CP-HE",    "CP424");
+        fIANA2JavaMap.put("IBM424",    "CP424");
+        fIANA2JavaMap.put("CP424",    "CP424");
+        fIANA2JavaMap.put("CSIBM424",    "CP424");
+        fIANA2JavaMap.put("IBM437",    "CP437");
+        fIANA2JavaMap.put("437",    "CP437");
+        fIANA2JavaMap.put("CP437",    "CP437");
+        fIANA2JavaMap.put("CSPC8CODEPAGE437",    "CP437");
+        fIANA2JavaMap.put("EBCDIC-CP-CH",    "CP500");
+        fIANA2JavaMap.put("IBM500",    "CP500");
+        fIANA2JavaMap.put("CP500",    "CP500");
+        fIANA2JavaMap.put("CSIBM500",    "CP500");
+        fIANA2JavaMap.put("EBCDIC-CP-CH",    "CP500");
+        fIANA2JavaMap.put("EBCDIC-CP-BE",    "CP500"); 
+        fIANA2JavaMap.put("IBM775",    "CP775");
+        fIANA2JavaMap.put("CP775",    "CP775");
+        fIANA2JavaMap.put("CSPC775BALTIC",    "CP775");
+        fIANA2JavaMap.put("IBM850",    "CP850");
+        fIANA2JavaMap.put("850",    "CP850");
+        fIANA2JavaMap.put("CP850",    "CP850");
+        fIANA2JavaMap.put("CSPC850MULTILINGUAL",    "CP850");
+        fIANA2JavaMap.put("IBM852",    "CP852");
+        fIANA2JavaMap.put("852",    "CP852");
+        fIANA2JavaMap.put("CP852",    "CP852");
+        fIANA2JavaMap.put("CSPCP852",    "CP852");
+        fIANA2JavaMap.put("IBM855",    "CP855");
+        fIANA2JavaMap.put("855",    "CP855");
+        fIANA2JavaMap.put("CP855",    "CP855");
+        fIANA2JavaMap.put("CSIBM855",    "CP855");
+        fIANA2JavaMap.put("IBM857",    "CP857");
+        fIANA2JavaMap.put("857",    "CP857");
+        fIANA2JavaMap.put("CP857",    "CP857");
+        fIANA2JavaMap.put("CSIBM857",    "CP857");
+        fIANA2JavaMap.put("IBM00858",    "CP858");
+        fIANA2JavaMap.put("CP00858",    "CP858");
+        fIANA2JavaMap.put("CCSID00858",    "CP858");
+        fIANA2JavaMap.put("IBM860",    "CP860");
+        fIANA2JavaMap.put("860",    "CP860");
+        fIANA2JavaMap.put("CP860",    "CP860");
+        fIANA2JavaMap.put("CSIBM860",    "CP860");
+        fIANA2JavaMap.put("IBM861",    "CP861");
+        fIANA2JavaMap.put("861",    "CP861");
+        fIANA2JavaMap.put("CP861",    "CP861");
+        fIANA2JavaMap.put("CP-IS",    "CP861");
+        fIANA2JavaMap.put("CSIBM861",    "CP861");
+        fIANA2JavaMap.put("IBM862",    "CP862");
+        fIANA2JavaMap.put("862",    "CP862");
+        fIANA2JavaMap.put("CP862",    "CP862");
+        fIANA2JavaMap.put("CSPC862LATINHEBREW",    "CP862");
+        fIANA2JavaMap.put("IBM863",    "CP863");
+        fIANA2JavaMap.put("863",    "CP863");
+        fIANA2JavaMap.put("CP863",    "CP863");
+        fIANA2JavaMap.put("CSIBM863",    "CP863");
+        fIANA2JavaMap.put("IBM864",    "CP864");
+        fIANA2JavaMap.put("CP864",    "CP864");
+        fIANA2JavaMap.put("CSIBM864",    "CP864");
+        fIANA2JavaMap.put("IBM865",    "CP865");
+        fIANA2JavaMap.put("865",    "CP865");
+        fIANA2JavaMap.put("CP865",    "CP865");
+        fIANA2JavaMap.put("CSIBM865",    "CP865");
+        fIANA2JavaMap.put("IBM866",    "CP866");
+        fIANA2JavaMap.put("866",    "CP866");
+        fIANA2JavaMap.put("CP866",    "CP866");
+        fIANA2JavaMap.put("CSIBM866",    "CP866");
+        fIANA2JavaMap.put("IBM868",    "CP868");
+        fIANA2JavaMap.put("CP868",    "CP868");
+        fIANA2JavaMap.put("CSIBM868",    "CP868");
+        fIANA2JavaMap.put("CP-AR",        "CP868");
+        fIANA2JavaMap.put("IBM869",    "CP869");
+        fIANA2JavaMap.put("CP869",    "CP869");
+        fIANA2JavaMap.put("CSIBM869",    "CP869");
+        fIANA2JavaMap.put("CP-GR",        "CP869");
+        fIANA2JavaMap.put("IBM870",    "CP870");
+        fIANA2JavaMap.put("CP870",    "CP870");
+        fIANA2JavaMap.put("CSIBM870",    "CP870");
+        fIANA2JavaMap.put("EBCDIC-CP-ROECE", "CP870");
+        fIANA2JavaMap.put("EBCDIC-CP-YU",    "CP870");
+        fIANA2JavaMap.put("IBM871",    "CP871");
+        fIANA2JavaMap.put("CP871",    "CP871");
+        fIANA2JavaMap.put("CSIBM871",    "CP871");
+        fIANA2JavaMap.put("EBCDIC-CP-IS",    "CP871");
+        fIANA2JavaMap.put("IBM918",    "CP918");
+        fIANA2JavaMap.put("CP918",    "CP918");
+        fIANA2JavaMap.put("CSIBM918",    "CP918");
+        fIANA2JavaMap.put("EBCDIC-CP-AR2",   "CP918");
+        fIANA2JavaMap.put("IBM00924",    "CP924");
+        fIANA2JavaMap.put("CP00924",    "CP924");
+        fIANA2JavaMap.put("CCSID00924",    "CP924");
+        // is this an error???
+        fIANA2JavaMap.put("EBCDIC-LATIN9--EURO",    "CP924");
+        fIANA2JavaMap.put("IBM1026",    "CP1026");
+        fIANA2JavaMap.put("CP1026",    "CP1026");
+        fIANA2JavaMap.put("CSIBM1026",    "CP1026");
+        fIANA2JavaMap.put("IBM01140",    "Cp1140");
+        fIANA2JavaMap.put("CP01140",    "Cp1140");
+        fIANA2JavaMap.put("CCSID01140",    "Cp1140");
+        fIANA2JavaMap.put("IBM01141",    "Cp1141");
+        fIANA2JavaMap.put("CP01141",    "Cp1141");
+        fIANA2JavaMap.put("CCSID01141",    "Cp1141");
+        fIANA2JavaMap.put("IBM01142",    "Cp1142");
+        fIANA2JavaMap.put("CP01142",    "Cp1142");
+        fIANA2JavaMap.put("CCSID01142",    "Cp1142");
+        fIANA2JavaMap.put("IBM01143",    "Cp1143");
+        fIANA2JavaMap.put("CP01143",    "Cp1143");
+        fIANA2JavaMap.put("CCSID01143",    "Cp1143");
+        fIANA2JavaMap.put("IBM01144",    "Cp1144");
+        fIANA2JavaMap.put("CP01144",    "Cp1144");
+        fIANA2JavaMap.put("CCSID01144",    "Cp1144");
+        fIANA2JavaMap.put("IBM01145",    "Cp1145");
+        fIANA2JavaMap.put("CP01145",    "Cp1145");
+        fIANA2JavaMap.put("CCSID01145",    "Cp1145");
+        fIANA2JavaMap.put("IBM01146",    "Cp1146");
+        fIANA2JavaMap.put("CP01146",    "Cp1146");
+        fIANA2JavaMap.put("CCSID01146",    "Cp1146");
+        fIANA2JavaMap.put("IBM01147",    "Cp1147");
+        fIANA2JavaMap.put("CP01147",    "Cp1147");
+        fIANA2JavaMap.put("CCSID01147",    "Cp1147");
+        fIANA2JavaMap.put("IBM01148",    "Cp1148");
+        fIANA2JavaMap.put("CP01148",    "Cp1148");
+        fIANA2JavaMap.put("CCSID01148",    "Cp1148");
+        fIANA2JavaMap.put("IBM01149",    "Cp1149");
+        fIANA2JavaMap.put("CP01149",    "Cp1149");
+        fIANA2JavaMap.put("CCSID01149",    "Cp1149");
+        fIANA2JavaMap.put("EUC-JP",          "EUCJIS");
+        fIANA2JavaMap.put("CSEUCPKDFMTJAPANESE",          "EUCJIS");
+        fIANA2JavaMap.put("EXTENDED_UNIX_CODE_PACKED_FORMAT_FOR_JAPANESE",          "EUCJIS");
+        fIANA2JavaMap.put("EUC-KR",          "KSC5601");
+        fIANA2JavaMap.put("CSEUCKR",          "KSC5601");
+        fIANA2JavaMap.put("KS_C_5601-1987",          "KS_C_5601-1987");
+        fIANA2JavaMap.put("ISO-IR-149",          "KS_C_5601-1987");
+        fIANA2JavaMap.put("KS_C_5601-1989",          "KS_C_5601-1987");
+        fIANA2JavaMap.put("KSC_5601",          "KS_C_5601-1987");
+        fIANA2JavaMap.put("KOREAN",          "KS_C_5601-1987");
+        fIANA2JavaMap.put("CSKSC56011987",          "KS_C_5601-1987");
+        fIANA2JavaMap.put("GB2312",          "GB2312");
+        fIANA2JavaMap.put("CSGB2312",          "GB2312");
+        fIANA2JavaMap.put("ISO-2022-JP",     "JIS");
+        fIANA2JavaMap.put("CSISO2022JP",     "JIS");
+        fIANA2JavaMap.put("ISO-2022-KR",     "ISO2022KR");
+        fIANA2JavaMap.put("CSISO2022KR",     "ISO2022KR");
+        fIANA2JavaMap.put("ISO-2022-CN",     "ISO2022CN");
+
+        fIANA2JavaMap.put("X0201",  "JIS0201");
+        fIANA2JavaMap.put("CSISO13JISC6220JP", "JIS0201");
+        fIANA2JavaMap.put("X0208",  "JIS0208");
+        fIANA2JavaMap.put("ISO-IR-87",  "JIS0208");
+        fIANA2JavaMap.put("X0208dbiJIS_X0208-1983",  "JIS0208");
+        fIANA2JavaMap.put("CSISO87JISX0208",  "JIS0208");
+        fIANA2JavaMap.put("X0212",  "JIS0212");
+        fIANA2JavaMap.put("ISO-IR-159",  "JIS0212");
+        fIANA2JavaMap.put("CSISO159JISX02121990",  "JIS0212");
+        fIANA2JavaMap.put("GB18030",       "GB18030");
+        fIANA2JavaMap.put("GBK",       "GBK");
+        fIANA2JavaMap.put("CP936",       "GBK");
+        fIANA2JavaMap.put("MS936",       "GBK");
+        fIANA2JavaMap.put("WINDOWS-936",       "GBK");
+        fIANA2JavaMap.put("SHIFT_JIS",       "SJIS");
+        fIANA2JavaMap.put("CSSHIFTJIS",       "SJIS");
+        fIANA2JavaMap.put("MS_KANJI",       "SJIS");
+        fIANA2JavaMap.put("WINDOWS-31J",       "MS932");
+        fIANA2JavaMap.put("CSWINDOWS31J",       "MS932");
+
+        // Add support for Cp1252 and its friends
+        fIANA2JavaMap.put("WINDOWS-1250",   "Cp1250");
+        fIANA2JavaMap.put("WINDOWS-1251",   "Cp1251");
+        fIANA2JavaMap.put("WINDOWS-1252",   "Cp1252");
+        fIANA2JavaMap.put("WINDOWS-1253",   "Cp1253");
+        fIANA2JavaMap.put("WINDOWS-1254",   "Cp1254");
+        fIANA2JavaMap.put("WINDOWS-1255",   "Cp1255");
+        fIANA2JavaMap.put("WINDOWS-1256",   "Cp1256");
+        fIANA2JavaMap.put("WINDOWS-1257",   "Cp1257");
+        fIANA2JavaMap.put("WINDOWS-1258",   "Cp1258");
+        fIANA2JavaMap.put("TIS-620",   "TIS620");
+
+        fIANA2JavaMap.put("ISO-8859-1",      "ISO8859_1"); 
+        fIANA2JavaMap.put("ISO-IR-100",      "ISO8859_1");
+        fIANA2JavaMap.put("ISO_8859-1",      "ISO8859_1");
+        fIANA2JavaMap.put("LATIN1",      "ISO8859_1");
+        fIANA2JavaMap.put("CSISOLATIN1",      "ISO8859_1");
+        fIANA2JavaMap.put("L1",      "ISO8859_1");
+        fIANA2JavaMap.put("IBM819",      "ISO8859_1");
+        fIANA2JavaMap.put("CP819",      "ISO8859_1");
+
+        fIANA2JavaMap.put("ISO-8859-2",      "ISO8859_2"); 
+        fIANA2JavaMap.put("ISO-IR-101",      "ISO8859_2");
+        fIANA2JavaMap.put("ISO_8859-2",      "ISO8859_2");
+        fIANA2JavaMap.put("LATIN2",      "ISO8859_2");
+        fIANA2JavaMap.put("CSISOLATIN2",      "ISO8859_2");
+        fIANA2JavaMap.put("L2",      "ISO8859_2");
+
+        fIANA2JavaMap.put("ISO-8859-3",      "ISO8859_3"); 
+        fIANA2JavaMap.put("ISO-IR-109",      "ISO8859_3");
+        fIANA2JavaMap.put("ISO_8859-3",      "ISO8859_3");
+        fIANA2JavaMap.put("LATIN3",      "ISO8859_3");
+        fIANA2JavaMap.put("CSISOLATIN3",      "ISO8859_3");
+        fIANA2JavaMap.put("L3",      "ISO8859_3");
+
+        fIANA2JavaMap.put("ISO-8859-4",      "ISO8859_4"); 
+        fIANA2JavaMap.put("ISO-IR-110",      "ISO8859_4");
+        fIANA2JavaMap.put("ISO_8859-4",      "ISO8859_4");
+        fIANA2JavaMap.put("LATIN4",      "ISO8859_4");
+        fIANA2JavaMap.put("CSISOLATIN4",      "ISO8859_4");
+        fIANA2JavaMap.put("L4",      "ISO8859_4");
+
+        fIANA2JavaMap.put("ISO-8859-5",      "ISO8859_5"); 
+        fIANA2JavaMap.put("ISO-IR-144",      "ISO8859_5");
+        fIANA2JavaMap.put("ISO_8859-5",      "ISO8859_5");
+        fIANA2JavaMap.put("CYRILLIC",      "ISO8859_5");
+        fIANA2JavaMap.put("CSISOLATINCYRILLIC",      "ISO8859_5");
+
+        fIANA2JavaMap.put("ISO-8859-6",      "ISO8859_6"); 
+        fIANA2JavaMap.put("ISO-IR-127",      "ISO8859_6");
+        fIANA2JavaMap.put("ISO_8859-6",      "ISO8859_6");
+        fIANA2JavaMap.put("ECMA-114",      "ISO8859_6");
+        fIANA2JavaMap.put("ASMO-708",      "ISO8859_6");
+        fIANA2JavaMap.put("ARABIC",      "ISO8859_6");
+        fIANA2JavaMap.put("CSISOLATINARABIC",      "ISO8859_6");
+
+        fIANA2JavaMap.put("ISO-8859-7",      "ISO8859_7"); 
+        fIANA2JavaMap.put("ISO-IR-126",      "ISO8859_7");
+        fIANA2JavaMap.put("ISO_8859-7",      "ISO8859_7");
+        fIANA2JavaMap.put("ELOT_928",      "ISO8859_7");
+        fIANA2JavaMap.put("ECMA-118",      "ISO8859_7");
+        fIANA2JavaMap.put("GREEK",      "ISO8859_7");
+        fIANA2JavaMap.put("CSISOLATINGREEK",      "ISO8859_7");
+        fIANA2JavaMap.put("GREEK8",      "ISO8859_7");
+
+        fIANA2JavaMap.put("ISO-8859-8",      "ISO8859_8"); 
+        fIANA2JavaMap.put("ISO-8859-8-I",      "ISO8859_8"); // added since this encoding only differs w.r.t. presentation 
+        fIANA2JavaMap.put("ISO-IR-138",      "ISO8859_8");
+        fIANA2JavaMap.put("ISO_8859-8",      "ISO8859_8");
+        fIANA2JavaMap.put("HEBREW",      "ISO8859_8");
+        fIANA2JavaMap.put("CSISOLATINHEBREW",      "ISO8859_8");
+
+        fIANA2JavaMap.put("ISO-8859-9",      "ISO8859_9"); 
+        fIANA2JavaMap.put("ISO-IR-148",      "ISO8859_9");
+        fIANA2JavaMap.put("ISO_8859-9",      "ISO8859_9");
+        fIANA2JavaMap.put("LATIN5",      "ISO8859_9");
+        fIANA2JavaMap.put("CSISOLATIN5",      "ISO8859_9");
+        fIANA2JavaMap.put("L5",      "ISO8859_9");
+
+        fIANA2JavaMap.put("ISO-8859-13",      "ISO8859_13"); 
+        
+        fIANA2JavaMap.put("ISO-8859-15",      "ISO8859_15_FDIS"); 
+        fIANA2JavaMap.put("ISO_8859-15",      "ISO8859_15_FDIS");
+        fIANA2JavaMap.put("LATIN-9",          "ISO8859_15_FDIS"); 
+
+        fIANA2JavaMap.put("KOI8-R",          "KOI8_R");
+        fIANA2JavaMap.put("CSKOI8R",          "KOI8_R");
+        fIANA2JavaMap.put("US-ASCII",        "ASCII"); 
+        fIANA2JavaMap.put("ISO-IR-6",        "ASCII");
+        fIANA2JavaMap.put("ANSI_X3.4-1968",        "ASCII");
+        fIANA2JavaMap.put("ANSI_X3.4-1986",        "ASCII");
+        fIANA2JavaMap.put("ISO_646.IRV:1991",        "ASCII");
+        fIANA2JavaMap.put("ASCII",        "ASCII");
+        fIANA2JavaMap.put("CSASCII",        "ASCII");
+        fIANA2JavaMap.put("ISO646-US",        "ASCII");
+        fIANA2JavaMap.put("US",        "ASCII");
+        fIANA2JavaMap.put("IBM367",        "ASCII");
+        fIANA2JavaMap.put("CP367",        "ASCII");
+        fIANA2JavaMap.put("UTF-8",           "UTF8");
+        fIANA2JavaMap.put("UTF-16",           "UTF-16");
+        fIANA2JavaMap.put("UTF-16BE",           "UnicodeBig");
+        fIANA2JavaMap.put("UTF-16LE",           "UnicodeLittle");
+
+        // support for 1047, as proposed to be added to the 
+        // IANA registry in 
+        // http://lists.w3.org/Archives/Public/ietf-charset/2002JulSep/0049.html
+        fIANA2JavaMap.put("IBM-1047",    "Cp1047");
+        fIANA2JavaMap.put("IBM1047",    "Cp1047");
+        fIANA2JavaMap.put("CP1047",    "Cp1047");
+
+        // Adding new aliases as proposed in
+        // http://lists.w3.org/Archives/Public/ietf-charset/2002JulSep/0058.html
+        fIANA2JavaMap.put("IBM-37",    "CP037");
+        fIANA2JavaMap.put("IBM-273",    "CP273");
+        fIANA2JavaMap.put("IBM-277",    "CP277");
+        fIANA2JavaMap.put("IBM-278",    "CP278");
+        fIANA2JavaMap.put("IBM-280",    "CP280");
+        fIANA2JavaMap.put("IBM-284",    "CP284");
+        fIANA2JavaMap.put("IBM-285",    "CP285");
+        fIANA2JavaMap.put("IBM-290",    "CP290");
+        fIANA2JavaMap.put("IBM-297",    "CP297");
+        fIANA2JavaMap.put("IBM-420",    "CP420");
+        fIANA2JavaMap.put("IBM-424",    "CP424");
+        fIANA2JavaMap.put("IBM-437",    "CP437");
+        fIANA2JavaMap.put("IBM-500",    "CP500");
+        fIANA2JavaMap.put("IBM-775",    "CP775");
+        fIANA2JavaMap.put("IBM-850",    "CP850");
+        fIANA2JavaMap.put("IBM-852",    "CP852");
+        fIANA2JavaMap.put("IBM-855",    "CP855");
+        fIANA2JavaMap.put("IBM-857",    "CP857");
+        fIANA2JavaMap.put("IBM-858",    "CP858");
+        fIANA2JavaMap.put("IBM-860",    "CP860");
+        fIANA2JavaMap.put("IBM-861",    "CP861");
+        fIANA2JavaMap.put("IBM-862",    "CP862");
+        fIANA2JavaMap.put("IBM-863",    "CP863");
+        fIANA2JavaMap.put("IBM-864",    "CP864");
+        fIANA2JavaMap.put("IBM-865",    "CP865");
+        fIANA2JavaMap.put("IBM-866",    "CP866");
+        fIANA2JavaMap.put("IBM-868",    "CP868");
+        fIANA2JavaMap.put("IBM-869",    "CP869");
+        fIANA2JavaMap.put("IBM-870",    "CP870");
+        fIANA2JavaMap.put("IBM-871",    "CP871");
+        fIANA2JavaMap.put("IBM-918",    "CP918");
+        fIANA2JavaMap.put("IBM-924",    "CP924");
+        fIANA2JavaMap.put("IBM-1026",    "CP1026");
+        fIANA2JavaMap.put("IBM-1140",    "Cp1140");
+        fIANA2JavaMap.put("IBM-1141",    "Cp1141");
+        fIANA2JavaMap.put("IBM-1142",    "Cp1142");
+        fIANA2JavaMap.put("IBM-1143",    "Cp1143");
+        fIANA2JavaMap.put("IBM-1144",    "Cp1144");
+        fIANA2JavaMap.put("IBM-1145",    "Cp1145");
+        fIANA2JavaMap.put("IBM-1146",    "Cp1146");
+        fIANA2JavaMap.put("IBM-1147",    "Cp1147");
+        fIANA2JavaMap.put("IBM-1148",    "Cp1148");
+        fIANA2JavaMap.put("IBM-1149",    "Cp1149");
+        fIANA2JavaMap.put("IBM-819",      "ISO8859_1");
+        fIANA2JavaMap.put("IBM-367",        "ASCII");
+
+        // REVISIT:
+        //   j:CNS11643 -> EUC-TW?
+        //   ISO-2022-CN? ISO-2022-CN-EXT?
+                                                
+        // add Java to IANA encoding mappings
+        //fJava2IANAMap.put("8859_1",    "US-ASCII"); // ?
+        fJava2IANAMap.put("ISO8859_1",    "ISO-8859-1");
+        fJava2IANAMap.put("ISO8859_2",    "ISO-8859-2");
+        fJava2IANAMap.put("ISO8859_3",    "ISO-8859-3");
+        fJava2IANAMap.put("ISO8859_4",    "ISO-8859-4");
+        fJava2IANAMap.put("ISO8859_5",    "ISO-8859-5");
+        fJava2IANAMap.put("ISO8859_6",    "ISO-8859-6");
+        fJava2IANAMap.put("ISO8859_7",    "ISO-8859-7");
+        fJava2IANAMap.put("ISO8859_8",    "ISO-8859-8");
+        fJava2IANAMap.put("ISO8859_9",    "ISO-8859-9");
+        fJava2IANAMap.put("ISO8859_13",    "ISO-8859-13");
+        fJava2IANAMap.put("ISO8859_15",    "ISO-8859-15");
+        fJava2IANAMap.put("ISO8859_15_FDIS",    "ISO-8859-15");
+        fJava2IANAMap.put("Big5",      "BIG5");
+        fJava2IANAMap.put("CP037",     "EBCDIC-CP-US");
+        fJava2IANAMap.put("CP273",     "IBM273");
+        fJava2IANAMap.put("CP277",     "EBCDIC-CP-DK");
+        fJava2IANAMap.put("CP278",     "EBCDIC-CP-FI");
+        fJava2IANAMap.put("CP280",     "EBCDIC-CP-IT");
+        fJava2IANAMap.put("CP284",     "EBCDIC-CP-ES");
+        fJava2IANAMap.put("CP285",     "EBCDIC-CP-GB");
+        fJava2IANAMap.put("CP290",     "EBCDIC-JP-KANA");
+        fJava2IANAMap.put("CP297",     "EBCDIC-CP-FR");
+        fJava2IANAMap.put("CP420",     "EBCDIC-CP-AR1");
+        fJava2IANAMap.put("CP424",     "EBCDIC-CP-HE");
+        fJava2IANAMap.put("CP437",     "IBM437");
+        fJava2IANAMap.put("CP500",     "EBCDIC-CP-CH");
+        fJava2IANAMap.put("CP775",     "IBM775");
+        fJava2IANAMap.put("CP850",     "IBM850");
+        fJava2IANAMap.put("CP852",     "IBM852");
+        fJava2IANAMap.put("CP855",     "IBM855");
+        fJava2IANAMap.put("CP857",     "IBM857");
+        fJava2IANAMap.put("CP858",     "IBM00858");
+        fJava2IANAMap.put("CP860",     "IBM860");
+        fJava2IANAMap.put("CP861",     "IBM861");
+        fJava2IANAMap.put("CP862",     "IBM862");
+        fJava2IANAMap.put("CP863",     "IBM863");
+        fJava2IANAMap.put("CP864",     "IBM864");
+        fJava2IANAMap.put("CP865",     "IBM865");
+        fJava2IANAMap.put("CP866",     "IBM866");
+        fJava2IANAMap.put("CP868",     "IBM868");
+        fJava2IANAMap.put("CP869",     "IBM869");
+        fJava2IANAMap.put("CP870",     "EBCDIC-CP-ROECE");
+        fJava2IANAMap.put("CP871",     "EBCDIC-CP-IS");
+        fJava2IANAMap.put("CP918",     "EBCDIC-CP-AR2");
+        fJava2IANAMap.put("CP924",     "IBM00924");
+        fJava2IANAMap.put("CP1026",     "IBM1026");
+        fJava2IANAMap.put("Cp01140",     "IBM01140");
+        fJava2IANAMap.put("Cp01141",     "IBM01141");
+        fJava2IANAMap.put("Cp01142",     "IBM01142");
+        fJava2IANAMap.put("Cp01143",     "IBM01143");
+        fJava2IANAMap.put("Cp01144",     "IBM01144");
+        fJava2IANAMap.put("Cp01145",     "IBM01145");
+        fJava2IANAMap.put("Cp01146",     "IBM01146");
+        fJava2IANAMap.put("Cp01147",     "IBM01147");
+        fJava2IANAMap.put("Cp01148",     "IBM01148");
+        fJava2IANAMap.put("Cp01149",     "IBM01149");
+        fJava2IANAMap.put("EUCJIS",    "EUC-JP");
+        fJava2IANAMap.put("KS_C_5601-1987",          "KS_C_5601-1987");
+        fJava2IANAMap.put("GB2312",    "GB2312");
+        fJava2IANAMap.put("ISO2022KR", "ISO-2022-KR");
+        fJava2IANAMap.put("ISO2022CN", "ISO-2022-CN");
+        fJava2IANAMap.put("JIS",       "ISO-2022-JP");
+        fJava2IANAMap.put("KOI8_R",    "KOI8-R");
+        fJava2IANAMap.put("KSC5601",   "EUC-KR");
+        fJava2IANAMap.put("GB18030",      "GB18030");
+        fJava2IANAMap.put("GBK",       "GBK");
+        fJava2IANAMap.put("SJIS",      "SHIFT_JIS");
+        fJava2IANAMap.put("MS932",      "WINDOWS-31J");
+        fJava2IANAMap.put("UTF8",      "UTF-8");
+        fJava2IANAMap.put("Unicode",   "UTF-16");
+        fJava2IANAMap.put("UnicodeBig",   "UTF-16BE");
+        fJava2IANAMap.put("UnicodeLittle",   "UTF-16LE");
+        fJava2IANAMap.put("JIS0201",  "X0201");
+        fJava2IANAMap.put("JIS0208",  "X0208");
+        fJava2IANAMap.put("JIS0212",  "ISO-IR-159");
+
+        // proposed addition (see above for details):
+        fJava2IANAMap.put("CP1047",    "IBM1047");
+
+    }
+
+    //
+    // Constructors
+    //
+
+    /** Default constructor. */
+    public EncodingMap() {}
+
+    //
+    // Public static methods
+    //
+
+    /**
+     * Adds an IANA to Java encoding name mapping.
+     * 
+     * @param ianaEncoding The IANA encoding name.
+     * @param javaEncoding The Java encoding name.
+     */
+    public static void putIANA2JavaMapping(String ianaEncoding, 
+                                           String javaEncoding) {
+        fIANA2JavaMap.put(ianaEncoding, javaEncoding);
+    }
+
+    /**
+     * Returns the Java encoding name for the specified IANA encoding name.
+     * 
+     * @param ianaEncoding The IANA encoding name.
+     */
+    public static String getIANA2JavaMapping(String ianaEncoding) {
+        return fIANA2JavaMap.get(ianaEncoding);
+    }
+
+    /**
+     * Removes an IANA to Java encoding name mapping.
+     * 
+     * @param ianaEncoding The IANA encoding name.
+     */
+    public static String removeIANA2JavaMapping(String ianaEncoding) {
+        return fIANA2JavaMap.remove(ianaEncoding);
+    }
+
+    /**
+     * Adds a Java to IANA encoding name mapping.
+     * 
+     * @param javaEncoding The Java encoding name.
+     * @param ianaEncoding The IANA encoding name.
+     */
+    public static void putJava2IANAMapping(String javaEncoding, 
+                                           String ianaEncoding) {
+        fJava2IANAMap.put(javaEncoding, ianaEncoding);
+    }
+
+    /**
+     * Returns the IANA encoding name for the specified Java encoding name.
+     * 
+     * @param javaEncoding The Java encoding name.
+     */
+    public static String getJava2IANAMapping(String javaEncoding) {
+        return fJava2IANAMap.get(javaEncoding);
+    }
+
+    /**
+     * Removes a Java to IANA encoding name mapping.
+     * 
+     * @param javaEncoding The Java encoding name.
+     */
+    public static String removeJava2IANAMapping(String javaEncoding) {
+        return fJava2IANAMap.remove(javaEncoding);
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/ParserUtils.java b/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/ParserUtils.java
new file mode 100644
index 0000000..e8f0c17
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/ParserUtils.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.jasper.xmlparser;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.jasper.Constants;
+import org.apache.jasper.JasperException;
+import org.apache.jasper.compiler.Localizer;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.w3c.dom.Comment;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Text;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+
+/**
+ * XML parsing utilities for processing web application deployment
+ * descriptor and tag library descriptor files.  FIXME - make these
+ * use a separate class loader for the parser to be used.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: ParserUtils.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ */
+
+public class ParserUtils {
+
+    /**
+     * An error handler for use when parsing XML documents.
+     */
+    static ErrorHandler errorHandler = new MyErrorHandler();
+
+    /**
+     * An entity resolver for use when parsing XML documents.
+     */
+    static EntityResolver entityResolver = new MyEntityResolver();
+
+    // Turn off for JSP 2.0 until switch over to using xschema.
+    public static boolean validating = false;
+
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Parse the specified XML document, and return a <code>TreeNode</code>
+     * that corresponds to the root node of the document tree.
+     *
+     * @param location Location (eg URI) of the XML document being parsed
+     * @param is Input source containing the deployment descriptor
+     *
+     * @exception JasperException if an input/output error occurs
+     * @exception JasperException if a parsing error occurs
+     */
+    public TreeNode parseXMLDocument(String location, InputSource is)
+        throws JasperException {
+
+        Document document = null;
+
+        // Perform an XML parse of this document, via JAXP
+        try {
+            DocumentBuilderFactory factory =
+                DocumentBuilderFactory.newInstance();
+            factory.setNamespaceAware(true);
+            factory.setValidating(validating);
+            DocumentBuilder builder = factory.newDocumentBuilder();
+            builder.setEntityResolver(entityResolver);
+            builder.setErrorHandler(errorHandler);
+            document = builder.parse(is);
+        } catch (ParserConfigurationException ex) {
+            throw new JasperException
+                (Localizer.getMessage("jsp.error.parse.xml", location), ex);
+        } catch (SAXParseException ex) {
+            throw new JasperException
+                (Localizer.getMessage("jsp.error.parse.xml.line",
+                                      location,
+                                      Integer.toString(ex.getLineNumber()),
+                                      Integer.toString(ex.getColumnNumber())),
+                 ex);
+        } catch (SAXException sx) {
+            throw new JasperException
+                (Localizer.getMessage("jsp.error.parse.xml", location), sx);
+        } catch (IOException io) {
+            throw new JasperException
+                (Localizer.getMessage("jsp.error.parse.xml", location), io);
+        }
+
+        // Convert the resulting document to a graph of TreeNodes
+        return (convert(null, document.getDocumentElement()));
+    }
+
+
+    /**
+     * Parse the specified XML document, and return a <code>TreeNode</code>
+     * that corresponds to the root node of the document tree.
+     *
+     * @param uri URI of the XML document being parsed
+     * @param is Input stream containing the deployment descriptor
+     *
+     * @exception JasperException if an input/output error occurs
+     * @exception JasperException if a parsing error occurs
+     */
+    public TreeNode parseXMLDocument(String uri, InputStream is)
+            throws JasperException {
+
+        return (parseXMLDocument(uri, new InputSource(is)));
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Create and return a TreeNode that corresponds to the specified Node,
+     * including processing all of the attributes and children nodes.
+     *
+     * @param parent The parent TreeNode (if any) for the new TreeNode
+     * @param node The XML document Node to be converted
+     */
+    protected TreeNode convert(TreeNode parent, Node node) {
+
+        // Construct a new TreeNode for this node
+        TreeNode treeNode = new TreeNode(node.getNodeName(), parent);
+
+        // Convert all attributes of this node
+        NamedNodeMap attributes = node.getAttributes();
+        if (attributes != null) {
+            int n = attributes.getLength();
+            for (int i = 0; i < n; i++) {
+                Node attribute = attributes.item(i);
+                treeNode.addAttribute(attribute.getNodeName(),
+                                      attribute.getNodeValue());
+            }
+        }
+
+        // Create and attach all children of this node
+        NodeList children = node.getChildNodes();
+        if (children != null) {
+            int n = children.getLength();
+            for (int i = 0; i < n; i++) {
+                Node child = children.item(i);
+                if (child instanceof Comment)
+                    continue;
+                if (child instanceof Text) {
+                    String body = ((Text) child).getData();
+                    if (body != null) {
+                        body = body.trim();
+                        if (body.length() > 0)
+                            treeNode.setBody(body);
+                    }
+                } else {
+                    convert(treeNode, child);
+                }
+            }
+        }
+        
+        // Return the completed TreeNode graph
+        return (treeNode);
+    }
+}
+
+
+// ------------------------------------------------------------ Private Classes
+
+class MyEntityResolver implements EntityResolver {
+
+    @Override
+    public InputSource resolveEntity(String publicId, String systemId)
+            throws SAXException {
+        for (int i = 0; i < Constants.CACHED_DTD_PUBLIC_IDS.size(); i++) {
+            String cachedDtdPublicId = Constants.CACHED_DTD_PUBLIC_IDS.get(i);
+            if (cachedDtdPublicId.equals(publicId)) {
+                String resourcePath =
+                    Constants.CACHED_DTD_RESOURCE_PATHS.get(i);
+                InputStream input = this.getClass().getResourceAsStream(
+                        resourcePath);
+                if (input == null) {
+                    throw new SAXException(Localizer.getMessage(
+                            "jsp.error.internal.filenotfound", resourcePath));
+                }
+                InputSource isrc = new InputSource(input);
+                return isrc;
+            }
+        }
+        Log log = LogFactory.getLog(MyEntityResolver.class);
+        if (log.isDebugEnabled())
+            log.debug("Resolve entity failed" + publicId + " " + systemId);
+        log.error(Localizer.getMessage("jsp.error.parse.xml.invalidPublicId",
+                publicId));
+        return null;
+    }
+}
+
+class MyErrorHandler implements ErrorHandler {
+
+    @Override
+    public void warning(SAXParseException ex) throws SAXException {
+        Log log = LogFactory.getLog(MyErrorHandler.class);
+        if (log.isDebugEnabled())
+            log.debug("ParserUtils: warning ", ex);
+        // We ignore warnings
+    }
+
+    @Override
+    public void error(SAXParseException ex) throws SAXException {
+        throw ex;
+    }
+
+    @Override
+    public void fatalError(SAXParseException ex) throws SAXException {
+        throw ex;
+    }
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/SymbolTable.java b/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/SymbolTable.java
new file mode 100644
index 0000000..205d5c4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/SymbolTable.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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation and was
+ * originally based on software copyright (c) 1999, International
+ * Business Machines, Inc., http://www.apache.org.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.jasper.xmlparser;
+
+/**
+ * This class is a symbol table implementation that guarantees that
+ * strings used as identifiers are unique references. Multiple calls
+ * to <code>addSymbol</code> will always return the same string
+ * reference.
+ * <p>
+ * The symbol table performs the same task as <code>String.intern()</code>
+ * with the following differences:
+ * <ul>
+ *  <li>
+ *   A new string object does not need to be created in order to
+ *   retrieve a unique reference. Symbols can be added by using
+ *   a series of characters in a character array.
+ *  </li>
+ *  <li>
+ *   Users of the symbol table can provide their own symbol hashing
+ *   implementation. For example, a simple string hashing algorithm
+ *   may fail to produce a balanced set of hashcodes for symbols
+ *   that are <em>mostly</em> unique. Strings with similar leading
+ *   characters are especially prone to this poor hashing behavior.
+ *  </li>
+ * </ul>
+ *
+ * @author Andy Clark
+ * @version $Id: SymbolTable.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ */
+public class SymbolTable {
+
+    //
+    // Constants
+    //
+
+    /** Default table size. */
+    protected static final int TABLE_SIZE = 101;
+
+    //
+    // Data
+    //
+
+    /** Buckets. */
+    protected Entry[] fBuckets = null;
+
+    // actual table size
+    protected int fTableSize;
+
+    //
+    // Constructors
+    //
+
+    /** Constructs a symbol table with a default number of buckets. */
+    public SymbolTable() {
+        this(TABLE_SIZE);
+    }
+
+    /** Constructs a symbol table with a specified number of buckets. */
+    public SymbolTable(int tableSize) {
+        fTableSize = tableSize;
+        fBuckets = new Entry[fTableSize];
+    }
+
+    //
+    // Public methods
+    //
+
+    /**
+     * Adds the specified symbol to the symbol table and returns a
+     * reference to the unique symbol. If the symbol already exists,
+     * the previous symbol reference is returned instead, in order
+     * guarantee that symbol references remain unique.
+     *
+     * @param symbol The new symbol.
+     */
+    public String addSymbol(String symbol) {
+
+        // search for identical symbol
+        int bucket = hash(symbol) % fTableSize;
+        int length = symbol.length();
+        OUTER: for (Entry entry = fBuckets[bucket]; entry != null; entry = entry.next) {
+            if (length == entry.characters.length) {
+                for (int i = 0; i < length; i++) {
+                    if (symbol.charAt(i) != entry.characters[i]) {
+                        continue OUTER;
+                    }
+                }
+                return entry.symbol;
+            }
+        }
+
+        // create new entry
+        Entry entry = new Entry(symbol, fBuckets[bucket]);
+        fBuckets[bucket] = entry;
+        return entry.symbol;
+
+    } // addSymbol(String):String
+
+    /**
+     * Adds the specified symbol to the symbol table and returns a
+     * reference to the unique symbol. If the symbol already exists,
+     * the previous symbol reference is returned instead, in order
+     * guarantee that symbol references remain unique.
+     *
+     * @param buffer The buffer containing the new symbol.
+     * @param offset The offset into the buffer of the new symbol.
+     * @param length The length of the new symbol in the buffer.
+     */
+    public String addSymbol(char[] buffer, int offset, int length) {
+
+        // search for identical symbol
+        int bucket = hash(buffer, offset, length) % fTableSize;
+        OUTER: for (Entry entry = fBuckets[bucket]; entry != null; entry = entry.next) {
+            if (length == entry.characters.length) {
+                for (int i = 0; i < length; i++) {
+                    if (buffer[offset + i] != entry.characters[i]) {
+                        continue OUTER;
+                    }
+                }
+                return entry.symbol;
+            }
+        }
+
+        // add new entry
+        Entry entry = new Entry(buffer, offset, length, fBuckets[bucket]);
+        fBuckets[bucket] = entry;
+        return entry.symbol;
+
+    } // addSymbol(char[],int,int):String
+
+    /**
+     * Returns a hashcode value for the specified symbol. The value
+     * returned by this method must be identical to the value returned
+     * by the <code>hash(char[],int,int)</code> method when called
+     * with the character array that comprises the symbol string.
+     *
+     * @param symbol The symbol to hash.
+     */
+    public int hash(String symbol) {
+
+        int code = 0;
+        int length = symbol.length();
+        for (int i = 0; i < length; i++) {
+            code = code * 37 + symbol.charAt(i);
+        }
+        return code & 0x7FFFFFF;
+
+    } // hash(String):int
+
+    /**
+     * Returns a hashcode value for the specified symbol information.
+     * The value returned by this method must be identical to the value
+     * returned by the <code>hash(String)</code> method when called
+     * with the string object created from the symbol information.
+     *
+     * @param buffer The character buffer containing the symbol.
+     * @param offset The offset into the character buffer of the start
+     *               of the symbol.
+     * @param length The length of the symbol.
+     */
+    public int hash(char[] buffer, int offset, int length) {
+
+        int code = 0;
+        for (int i = 0; i < length; i++) {
+            code = code * 37 + buffer[offset + i];
+        }
+        return code & 0x7FFFFFF;
+
+    } // hash(char[],int,int):int
+
+    /**
+     * Returns true if the symbol table already contains the specified
+     * symbol.
+     *
+     * @param symbol The symbol to look for.
+     */
+    public boolean containsSymbol(String symbol) {
+
+        // search for identical symbol
+        int bucket = hash(symbol) % fTableSize;
+        int length = symbol.length();
+        OUTER: for (Entry entry = fBuckets[bucket]; entry != null; entry = entry.next) {
+            if (length == entry.characters.length) {
+                for (int i = 0; i < length; i++) {
+                    if (symbol.charAt(i) != entry.characters[i]) {
+                        continue OUTER;
+                    }
+                }
+                return true;
+            }
+        }
+
+        return false;
+
+    } // containsSymbol(String):boolean
+
+    /**
+     * Returns true if the symbol table already contains the specified
+     * symbol.
+     *
+     * @param buffer The buffer containing the symbol to look for.
+     * @param offset The offset into the buffer.
+     * @param length The length of the symbol in the buffer.
+     */
+    public boolean containsSymbol(char[] buffer, int offset, int length) {
+
+        // search for identical symbol
+        int bucket = hash(buffer, offset, length) % fTableSize;
+        OUTER: for (Entry entry = fBuckets[bucket]; entry != null; entry = entry.next) {
+            if (length == entry.characters.length) {
+                for (int i = 0; i < length; i++) {
+                    if (buffer[offset + i] != entry.characters[i]) {
+                        continue OUTER;
+                    }
+                }
+                return true;
+            }
+        }
+
+        return false;
+
+    } // containsSymbol(char[],int,int):boolean
+
+    //
+    // Classes
+    //
+
+    /**
+     * This class is a symbol table entry. Each entry acts as a node
+     * in a linked list.
+     */
+    protected static final class Entry {
+
+        //
+        // Data
+        //
+
+        /** Symbol. */
+        public String symbol;
+
+        /**
+         * Symbol characters. This information is duplicated here for
+         * comparison performance.
+         */
+        public char[] characters;
+
+        /** The next entry. */
+        public Entry next;
+
+        //
+        // Constructors
+        //
+
+        /**
+         * Constructs a new entry from the specified symbol and next entry
+         * reference.
+         */
+        public Entry(String symbol, Entry next) {
+            this.symbol = symbol.intern();
+            characters = new char[symbol.length()];
+            symbol.getChars(0, characters.length, characters, 0);
+            this.next = next;
+        }
+
+        /**
+         * Constructs a new entry from the specified symbol information and
+         * next entry reference.
+         */
+        public Entry(char[] ch, int offset, int length, Entry next) {
+            characters = new char[length];
+            System.arraycopy(ch, offset, characters, 0, length);
+            symbol = new String(characters).intern();
+            this.next = next;
+        }
+
+    } // class Entry
+
+} // class SymbolTable
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/TreeNode.java b/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/TreeNode.java
new file mode 100644
index 0000000..d58922b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/TreeNode.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.jasper.xmlparser;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+
+/**
+ * Simplified implementation of a Node from a Document Object Model (DOM)
+ * parse of an XML document.  This class is used to represent a DOM tree
+ * so that the XML parser's implementation of <code>org.w3c.dom</code> need
+ * not be visible to the remainder of Jasper.
+ * <p>
+ * <strong>WARNING</strong> - Construction of a new tree, or modifications
+ * to an existing one, are not thread-safe and such accesses must be
+ * synchronized.
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: TreeNode.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ */
+
+public class TreeNode {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new node with the specified parent.
+     *
+     * @param name The name of this node
+     * @param parent The node that is the parent of this node
+     */
+    public TreeNode(String name, TreeNode parent) {
+
+        super();
+        this.name = name;
+        this.parent = parent;
+        if (this.parent != null)
+            this.parent.addChild(this);
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The attributes of this node, keyed by attribute name,
+     * Instantiated only if required.
+     */
+    protected HashMap<String,String> attributes = null;
+
+
+    /**
+     * The body text associated with this node (if any).
+     */
+    protected String body = null;
+
+
+    /**
+     * The children of this node, instantiated only if required.
+     */
+    protected ArrayList<TreeNode> children = null;
+
+
+    /**
+     * The name of this node.
+     */
+    protected String name = null;
+
+
+    /**
+     * The parent node of this node.
+     */
+    protected TreeNode parent = null;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add an attribute to this node, replacing any existing attribute
+     * with the same name.
+     *
+     * @param name The attribute name to add
+     * @param value The new attribute value
+     */
+    public void addAttribute(String name, String value) {
+
+        if (attributes == null)
+            attributes = new HashMap<String,String>();
+        attributes.put(name, value);
+
+    }
+
+
+    /**
+     * Add a new child node to this node.
+     *
+     * @param node The new child node
+     */
+    public void addChild(TreeNode node) {
+
+        if (children == null)
+            children = new ArrayList<TreeNode>();
+        children.add(node);
+
+    }
+
+
+    /**
+     * Return the value of the specified node attribute if it exists, or
+     * <code>null</code> otherwise.
+     *
+     * @param name Name of the requested attribute
+     */
+    public String findAttribute(String name) {
+
+        if (attributes == null)
+            return null;
+        else
+            return attributes.get(name);
+
+    }
+
+
+    /**
+     * Return an Iterator of the attribute names of this node.  If there are
+     * no attributes, an empty Iterator is returned.
+     */
+    public Iterator<String> findAttributes() {
+
+        if (attributes == null) {
+            List<String> empty = Collections.emptyList(); 
+            return empty.iterator();
+        } else
+            return attributes.keySet().iterator();
+
+    }
+
+
+    /**
+     * Return the first child node of this node with the specified name,
+     * if there is one; otherwise, return <code>null</code>.
+     *
+     * @param name Name of the desired child element
+     */
+    public TreeNode findChild(String name) {
+
+        if (children == null)
+            return (null);
+        Iterator<TreeNode> items = children.iterator();
+        while (items.hasNext()) {
+            TreeNode item = items.next();
+            if (name.equals(item.getName()))
+                return (item);
+        }
+        return (null);
+
+    }
+
+
+    /**
+     * Return an Iterator of all children of this node.  If there are no
+     * children, an empty Iterator is returned.
+     */
+    public Iterator<TreeNode> findChildren() {
+
+        if (children == null) {
+            List<TreeNode> empty = Collections.emptyList(); 
+            return empty.iterator();
+        } else
+            return children.iterator();
+
+    }
+
+
+    /**
+     * Return an Iterator over all children of this node that have the
+     * specified name.  If there are no such children, an empty Iterator
+     * is returned.
+     *
+     * @param name Name used to select children
+     */
+    public Iterator<TreeNode> findChildren(String name) {
+
+        if (children == null) {
+            List<TreeNode> empty = Collections.emptyList(); 
+            return empty.iterator();
+        } 
+
+        ArrayList<TreeNode> results = new ArrayList<TreeNode>();
+        Iterator<TreeNode> items = children.iterator();
+        while (items.hasNext()) {
+            TreeNode item = items.next();
+            if (name.equals(item.getName()))
+                results.add(item);
+        }
+        return (results.iterator());
+
+    }
+
+
+    /**
+     * Return the body text associated with this node (if any).
+     */
+    public String getBody() {
+
+        return (this.body);
+
+    }
+
+
+    /**
+     * Return the name of this node.
+     */
+    public String getName() {
+
+        return (this.name);
+
+    }
+
+
+    /**
+     * Set the body text associated with this node (if any).
+     *
+     * @param body The body text (if any)
+     */
+    public void setBody(String body) {
+
+        this.body = body;
+
+    }
+
+
+    /**
+     * Return a String representation of this TreeNode.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder();
+        toString(sb, 0, this);
+        return (sb.toString());
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Append to the specified StringBuilder a character representation of
+     * this node, with the specified amount of indentation.
+     *
+     * @param sb The StringBuilder to append to
+     * @param indent Number of characters of indentation
+     * @param node The TreeNode to be printed
+     */
+    protected void toString(StringBuilder sb, int indent,
+                            TreeNode node) {
+
+        int indent2 = indent + 2;
+
+        // Reconstruct an opening node
+        for (int i = 0; i < indent; i++)
+            sb.append(' ');
+        sb.append('<');
+        sb.append(node.getName());
+        Iterator<String> names = node.findAttributes();
+        while (names.hasNext()) {
+            sb.append(' ');
+            String name = names.next();
+            sb.append(name);
+            sb.append("=\"");
+            String value = node.findAttribute(name);
+            sb.append(value);
+            sb.append("\"");
+        }
+        sb.append(">\n");
+
+        // Reconstruct the body text of this node (if any)
+        String body = node.getBody();
+        if ((body != null) && (body.length() > 0)) {
+            for (int i = 0; i < indent2; i++)
+                sb.append(' ');
+            sb.append(body);
+            sb.append("\n");
+        }
+
+        // Reconstruct child nodes with extra indentation
+        Iterator<TreeNode> children = node.findChildren();
+        while (children.hasNext()) {
+            TreeNode child = children.next();
+            toString(sb, indent2, child);
+        }
+
+        // Reconstruct a closing node marker
+        for (int i = 0; i < indent; i++)
+            sb.append(' ');
+        sb.append("</");
+        sb.append(node.getName());
+        sb.append(">\n");
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/UCSReader.java b/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/UCSReader.java
new file mode 100644
index 0000000..7818aca
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/UCSReader.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.xmlparser;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+/** 
+ * Reader for UCS-2 and UCS-4 encodings.
+ * (i.e., encodings from ISO-10646-UCS-(2|4)).
+ *
+ * @author Neil Graham, IBM
+ *
+ * @version $Id: UCSReader.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ */
+public class UCSReader extends Reader {
+
+    private final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( UCSReader.class );
+    
+    //
+    // Constants
+    //
+
+    /** Default byte buffer size (8192, larger than that of ASCIIReader
+     * since it's reasonable to surmise that the average UCS-4-encoded
+     * file should be 4 times as large as the average ASCII-encoded file). 
+     */
+    public static final int DEFAULT_BUFFER_SIZE = 8192;
+
+    public static final short UCS2LE = 1;
+    public static final short UCS2BE = 2;
+    public static final short UCS4LE = 4;
+    public static final short UCS4BE = 8;
+
+    //
+    // Data
+    //
+
+    /** Input stream. */
+    protected InputStream fInputStream;
+
+    /** Byte buffer. */
+    protected byte[] fBuffer;
+
+    // what kind of data we're dealing with
+    protected short fEncoding;
+
+    //
+    // Constructors
+    //
+
+    /** 
+     * Constructs an ASCII reader from the specified input stream 
+     * using the default buffer size.  The Endian-ness and whether this is
+     * UCS-2 or UCS-4 needs also to be known in advance.
+     *
+     * @param inputStream The input stream.
+     * @param encoding One of UCS2LE, UCS2BE, UCS4LE or UCS4BE.
+     */
+    public UCSReader(InputStream inputStream, short encoding) {
+        this(inputStream, DEFAULT_BUFFER_SIZE, encoding);
+    } // <init>(InputStream, short)
+
+    /** 
+     * Constructs an ASCII reader from the specified input stream 
+     * and buffer size.  The Endian-ness and whether this is
+     * UCS-2 or UCS-4 needs also to be known in advance.
+     *
+     * @param inputStream The input stream.
+     * @param size        The initial buffer size.
+     * @param encoding One of UCS2LE, UCS2BE, UCS4LE or UCS4BE.
+     */
+    public UCSReader(InputStream inputStream, int size, short encoding) {
+        fInputStream = inputStream;
+        fBuffer = new byte[size];
+        fEncoding = encoding;
+    } // <init>(InputStream,int,short)
+
+    //
+    // Reader methods
+    //
+
+    /**
+     * Read a single character.  This method will block until a character is
+     * available, an I/O error occurs, or the end of the stream is reached.
+     *
+     * <p> Subclasses that intend to support efficient single-character input
+     * should override this method.
+     *
+     * @return     The character read, as an integer in the range 0 to 127
+     *             (<tt>0x00-0x7f</tt>), or -1 if the end of the stream has
+     *             been reached
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    @Override
+    public int read() throws IOException { 
+        int b0 = fInputStream.read() & 0xff;
+        if (b0 == 0xff)
+            return -1;
+        int b1 = fInputStream.read() & 0xff;
+        if (b1 == 0xff)
+            return -1;
+        if(fEncoding >=4) {
+            int b2 = fInputStream.read() & 0xff;
+            if (b2 == 0xff)
+                return -1;
+            int b3 = fInputStream.read() & 0xff;
+            if (b3 == 0xff)
+                return -1;
+            if (log.isDebugEnabled())
+                log.debug("b0 is " + (b0 & 0xff) + " b1 " + (b1 & 0xff) + " b2 " + (b2 & 0xff) + " b3 " + (b3 & 0xff));
+            if (fEncoding == UCS4BE)
+                return (b0<<24)+(b1<<16)+(b2<<8)+b3;
+            else
+                return (b3<<24)+(b2<<16)+(b1<<8)+b0;
+        } else { // UCS-2
+            if (fEncoding == UCS2BE)
+                return (b0<<8)+b1;
+            else
+                return (b1<<8)+b0;
+        }
+    } // read():int
+
+    /**
+     * Read characters into a portion of an array.  This method will block
+     * until some input is available, an I/O error occurs, or the end of the
+     * stream is reached.
+     *
+     * @param      ch     Destination buffer
+     * @param      offset Offset at which to start storing characters
+     * @param      length Maximum number of characters to read
+     *
+     * @return     The number of characters read, or -1 if the end of the
+     *             stream has been reached
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    @Override
+    public int read(char ch[], int offset, int length) throws IOException {
+        int byteLength = length << ((fEncoding >= 4)?2:1);
+        if (byteLength > fBuffer.length) {
+            byteLength = fBuffer.length;
+        }
+        int count = fInputStream.read(fBuffer, 0, byteLength);
+        if(count == -1) return -1;
+        // try and make count be a multiple of the number of bytes we're looking for
+        if(fEncoding >= 4) { // BigEndian
+            // this looks ugly, but it avoids an if at any rate...
+            int numToRead = (4 - (count & 3) & 3);
+            for(int i=0; i<numToRead; i++) {
+                int charRead = fInputStream.read();
+                if(charRead == -1) { // end of input; something likely went wrong!A  Pad buffer with nulls.
+                    for (int j = i;j<numToRead; j++)
+                        fBuffer[count+j] = 0;
+                    break;
+                } else {
+                    fBuffer[count+i] = (byte)charRead; 
+                }
+            }
+            count += numToRead;
+        } else {
+            int numToRead = count & 1;
+            if(numToRead != 0) {
+                count++;
+                int charRead = fInputStream.read();
+                if(charRead == -1) { // end of input; something likely went wrong!A  Pad buffer with nulls.
+                    fBuffer[count] = 0;
+                } else {
+                    fBuffer[count] = (byte)charRead;
+                }
+            }
+        }
+
+        // now count is a multiple of the right number of bytes
+        int numChars = count >> ((fEncoding >= 4)?2:1);
+        int curPos = 0;
+        for (int i = 0; i < numChars; i++) {
+            int b0 = fBuffer[curPos++] & 0xff;
+            int b1 = fBuffer[curPos++] & 0xff;
+            if(fEncoding >=4) {
+                int b2 = fBuffer[curPos++] & 0xff;
+                int b3 = fBuffer[curPos++] & 0xff;
+                if (fEncoding == UCS4BE)
+                    ch[offset+i] = (char)((b0<<24)+(b1<<16)+(b2<<8)+b3);
+                else
+                    ch[offset+i] = (char)((b3<<24)+(b2<<16)+(b1<<8)+b0);
+            } else { // UCS-2
+                if (fEncoding == UCS2BE)
+                    ch[offset+i] = (char)((b0<<8)+b1);
+                else
+                    ch[offset+i] = (char)((b1<<8)+b0);
+            }
+        }
+        return numChars;
+    } // read(char[],int,int)
+
+    /**
+     * Skip characters.  This method will block until some characters are
+     * available, an I/O error occurs, or the end of the stream is reached.
+     *
+     * @param  n  The number of characters to skip
+     *
+     * @return    The number of characters actually skipped
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    @Override
+    public long skip(long n) throws IOException {
+        // charWidth will represent the number of bits to move
+        // n leftward to get num of bytes to skip, and then move the result rightward
+        // to get num of chars effectively skipped.
+        // The trick with &'ing, as with elsewhere in this dcode, is
+        // intended to avoid an expensive use of / that might not be optimized
+        // away.
+        int charWidth = (fEncoding >=4)?2:1;
+        long bytesSkipped = fInputStream.skip(n<<charWidth);
+        if((bytesSkipped & (charWidth | 1)) == 0) return bytesSkipped >> charWidth;
+        return (bytesSkipped >> charWidth) + 1;
+    } // skip(long):long
+
+    /**
+     * Tell whether this stream is ready to be read.
+     *
+     * @return True if the next read() is guaranteed not to block for input,
+     * false otherwise.  Note that returning false does not guarantee that the
+     * next read will block.
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    @Override
+    public boolean ready() throws IOException {
+        return false;
+    } // ready()
+
+    /**
+     * Tell whether this stream supports the mark() operation.
+     */
+    @Override
+    public boolean markSupported() {
+        return fInputStream.markSupported();
+    } // markSupported()
+
+    /**
+     * Mark the present position in the stream.  Subsequent calls to reset()
+     * will attempt to reposition the stream to this point.  Not all
+     * character-input streams support the mark() operation.
+     *
+     * @param  readAheadLimit  Limit on the number of characters that may be
+     *                         read while still preserving the mark.  After
+     *                         reading this many characters, attempting to
+     *                         reset the stream may fail.
+     *
+     * @exception  IOException  If the stream does not support mark(),
+     *                          or if some other I/O error occurs
+     */
+    @Override
+    public void mark(int readAheadLimit) throws IOException {
+        fInputStream.mark(readAheadLimit);
+    } // mark(int)
+
+    /**
+     * Reset the stream.  If the stream has been marked, then attempt to
+     * reposition it at the mark.  If the stream has not been marked, then
+     * attempt to reset it in some way appropriate to the particular stream,
+     * for example by repositioning it to its starting point.  Not all
+     * character-input streams support the reset() operation, and some support
+     * reset() without supporting mark().
+     *
+     * @exception  IOException  If the stream has not been marked,
+     *                          or if the mark has been invalidated,
+     *                          or if the stream does not support reset(),
+     *                          or if some other I/O error occurs
+     */
+    @Override
+    public void reset() throws IOException {
+        fInputStream.reset();
+    } // reset()
+
+    /**
+     * Close the stream.  Once a stream has been closed, further read(),
+     * ready(), mark(), or reset() invocations will throw an IOException.
+     * Closing a previously-closed stream, however, has no effect.
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+     @Override
+    public void close() throws IOException {
+         fInputStream.close();
+     } // close()
+
+} // class UCSReader
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/UTF8Reader.java b/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/UTF8Reader.java
new file mode 100644
index 0000000..6497cd0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/UTF8Reader.java
@@ -0,0 +1,637 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.xmlparser;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.UTFDataFormatException;
+
+import org.apache.jasper.compiler.Localizer;
+
+/**
+ * @author Andy Clark, IBM
+ *
+ * @version $Id: UTF8Reader.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ */
+public class UTF8Reader
+    extends Reader {
+
+    private final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( UTF8Reader.class );
+    
+    // debugging
+
+    /** Debug read. */
+    private static final boolean DEBUG_READ = false;
+
+    //
+    // Data
+    //
+
+    /** Input stream. */
+    protected InputStream fInputStream;
+
+    /** Byte buffer. */
+    protected byte[] fBuffer;
+
+    /** Offset into buffer. */
+    protected int fOffset;
+
+    /** Surrogate character. */
+    private int fSurrogate = -1;
+
+    //
+    // Constructors
+    //
+
+    /** 
+     * Constructs a UTF-8 reader from the specified input stream, 
+     * buffer size and MessageFormatter.
+     *
+     * @param inputStream The input stream.
+     * @param size        The initial buffer size.
+     */
+    public UTF8Reader(InputStream inputStream, int size) {
+        fInputStream = inputStream;
+        fBuffer = new byte[size];
+    }
+
+    //
+    // Reader methods
+    //
+
+    /**
+     * Read a single character.  This method will block until a character is
+     * available, an I/O error occurs, or the end of the stream is reached.
+     *
+     * <p> Subclasses that intend to support efficient single-character input
+     * should override this method.
+     *
+     * @return     The character read, as an integer in the range 0 to 16383
+     *             (<tt>0x00-0xffff</tt>), or -1 if the end of the stream has
+     *             been reached
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    @Override
+    public int read() throws IOException {
+
+        // decode character
+        int c = fSurrogate;
+        if (fSurrogate == -1) {
+            // NOTE: We use the index into the buffer if there are remaining
+            //       bytes from the last block read. -Ac
+            int index = 0;
+
+            // get first byte
+            int b0 = index == fOffset 
+                   ? fInputStream.read() : fBuffer[index++] & 0x00FF;
+            if (b0 == -1) {
+                return -1;
+            }
+
+            // UTF-8:   [0xxx xxxx]
+            // Unicode: [0000 0000] [0xxx xxxx]
+            if (b0 < 0x80) {
+                c = (char)b0;
+            }
+
+            // UTF-8:   [110y yyyy] [10xx xxxx]
+            // Unicode: [0000 0yyy] [yyxx xxxx]
+            else if ((b0 & 0xE0) == 0xC0) {
+                int b1 = index == fOffset 
+                       ? fInputStream.read() : fBuffer[index++] & 0x00FF;
+                if (b1 == -1) {
+                    expectedByte(2, 2);
+                }
+                if ((b1 & 0xC0) != 0x80) {
+                    invalidByte(2, 2);
+                }
+                c = ((b0 << 6) & 0x07C0) | (b1 & 0x003F);
+            }
+
+            // UTF-8:   [1110 zzzz] [10yy yyyy] [10xx xxxx]
+            // Unicode: [zzzz yyyy] [yyxx xxxx]
+            else if ((b0 & 0xF0) == 0xE0) {
+                int b1 = index == fOffset
+                       ? fInputStream.read() : fBuffer[index++] & 0x00FF;
+                if (b1 == -1) {
+                    expectedByte(2, 3);
+                }
+                if ((b1 & 0xC0) != 0x80) {
+                    invalidByte(2, 3);
+                }
+                int b2 = index == fOffset 
+                       ? fInputStream.read() : fBuffer[index++] & 0x00FF;
+                if (b2 == -1) {
+                    expectedByte(3, 3);
+                }
+                if ((b2 & 0xC0) != 0x80) {
+                    invalidByte(3, 3);
+                }
+                c = ((b0 << 12) & 0xF000) | ((b1 << 6) & 0x0FC0) |
+                    (b2 & 0x003F);
+            }
+
+            // UTF-8:   [1111 0uuu] [10uu zzzz] [10yy yyyy] [10xx xxxx]*
+            // Unicode: [1101 10ww] [wwzz zzyy] (high surrogate)
+            //          [1101 11yy] [yyxx xxxx] (low surrogate)
+            //          * uuuuu = wwww + 1
+            else if ((b0 & 0xF8) == 0xF0) {
+                int b1 = index == fOffset 
+                       ? fInputStream.read() : fBuffer[index++] & 0x00FF;
+                if (b1 == -1) {
+                    expectedByte(2, 4);
+                }
+                if ((b1 & 0xC0) != 0x80) {
+                    invalidByte(2, 3);
+                }
+                int b2 = index == fOffset 
+                       ? fInputStream.read() : fBuffer[index++] & 0x00FF;
+                if (b2 == -1) {
+                    expectedByte(3, 4);
+                }
+                if ((b2 & 0xC0) != 0x80) {
+                    invalidByte(3, 3);
+                }
+                int b3 = index == fOffset 
+                       ? fInputStream.read() : fBuffer[index++] & 0x00FF;
+                if (b3 == -1) {
+                    expectedByte(4, 4);
+                }
+                if ((b3 & 0xC0) != 0x80) {
+                    invalidByte(4, 4);
+                }
+                int uuuuu = ((b0 << 2) & 0x001C) | ((b1 >> 4) & 0x0003);
+                if (uuuuu > 0x10) {
+                    invalidSurrogate(uuuuu);
+                }
+                int wwww = uuuuu - 1;
+                int hs = 0xD800 | 
+                         ((wwww << 6) & 0x03C0) | ((b1 << 2) & 0x003C) | 
+                         ((b2 >> 4) & 0x0003);
+                int ls = 0xDC00 | ((b2 << 6) & 0x03C0) | (b3 & 0x003F);
+                c = hs;
+                fSurrogate = ls;
+            }
+
+            // error
+            else {
+                invalidByte(1, 1);
+            }
+        }
+
+        // use surrogate
+        else {
+            fSurrogate = -1;
+        }
+
+        // return character
+        if (DEBUG_READ) {
+            if (log.isDebugEnabled())
+                log.debug("read(): 0x"+Integer.toHexString(c));
+        }
+        return c;
+
+    } // read():int
+
+    /**
+     * Read characters into a portion of an array.  This method will block
+     * until some input is available, an I/O error occurs, or the end of the
+     * stream is reached.
+     *
+     * @param      ch     Destination buffer
+     * @param      offset Offset at which to start storing characters
+     * @param      length Maximum number of characters to read
+     *
+     * @return     The number of characters read, or -1 if the end of the
+     *             stream has been reached
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    @Override
+    public int read(char ch[], int offset, int length) throws IOException {
+
+        // handle surrogate
+        int out = offset;
+        if (fSurrogate != -1) {
+            ch[offset + 1] = (char)fSurrogate;
+            fSurrogate = -1;
+            length--;
+            out++;
+        }
+
+        // read bytes
+        int count = 0;
+        if (fOffset == 0) {
+            // adjust length to read
+            if (length > fBuffer.length) {
+                length = fBuffer.length;
+            }
+
+            // perform read operation
+            count = fInputStream.read(fBuffer, 0, length);
+            if (count == -1) {
+                return -1;
+            }
+            count += out - offset;
+        }
+
+        // skip read; last character was in error
+        // NOTE: Having an offset value other than zero means that there was
+        //       an error in the last character read. In this case, we have
+        //       skipped the read so we don't consume any bytes past the 
+        //       error. By signaling the error on the next block read we
+        //       allow the method to return the most valid characters that
+        //       it can on the previous block read. -Ac
+        else {
+            count = fOffset;
+            fOffset = 0;
+        }
+
+        // convert bytes to characters
+        final int total = count;
+        for (int in = 0; in < total; in++) {
+            int b0 = fBuffer[in] & 0x00FF;
+
+            // UTF-8:   [0xxx xxxx]
+            // Unicode: [0000 0000] [0xxx xxxx]
+            if (b0 < 0x80) {
+                ch[out++] = (char)b0;
+                continue;
+            }
+
+            // UTF-8:   [110y yyyy] [10xx xxxx]
+            // Unicode: [0000 0yyy] [yyxx xxxx]
+            if ((b0 & 0xE0) == 0xC0) {
+                int b1 = -1;
+                if (++in < total) { 
+                    b1 = fBuffer[in] & 0x00FF; 
+                }
+                else {
+                    b1 = fInputStream.read();
+                    if (b1 == -1) {
+                        if (out > offset) {
+                            fBuffer[0] = (byte)b0;
+                            fOffset = 1;
+                            return out - offset;
+                        }
+                        expectedByte(2, 2);
+                    }
+                    count++;
+                }
+                if ((b1 & 0xC0) != 0x80) {
+                    if (out > offset) {
+                        fBuffer[0] = (byte)b0;
+                        fBuffer[1] = (byte)b1;
+                        fOffset = 2;
+                        return out - offset;
+                    }
+                    invalidByte(2, 2);
+                }
+                int c = ((b0 << 6) & 0x07C0) | (b1 & 0x003F);
+                ch[out++] = (char)c;
+                count -= 1;
+                continue;
+            }
+
+            // UTF-8:   [1110 zzzz] [10yy yyyy] [10xx xxxx]
+            // Unicode: [zzzz yyyy] [yyxx xxxx]
+            if ((b0 & 0xF0) == 0xE0) {
+                int b1 = -1;
+                if (++in < total) { 
+                    b1 = fBuffer[in] & 0x00FF; 
+                }
+                else {
+                    b1 = fInputStream.read();
+                    if (b1 == -1) {
+                        if (out > offset) {
+                            fBuffer[0] = (byte)b0;
+                            fOffset = 1;
+                            return out - offset;
+                        }
+                        expectedByte(2, 3);
+                    }
+                    count++;
+                }
+                if ((b1 & 0xC0) != 0x80) {
+                    if (out > offset) {
+                        fBuffer[0] = (byte)b0;
+                        fBuffer[1] = (byte)b1;
+                        fOffset = 2;
+                        return out - offset;
+                    }
+                    invalidByte(2, 3);
+                }
+                int b2 = -1;
+                if (++in < total) { 
+                    b2 = fBuffer[in] & 0x00FF; 
+                }
+                else {
+                    b2 = fInputStream.read();
+                    if (b2 == -1) {
+                        if (out > offset) {
+                            fBuffer[0] = (byte)b0;
+                            fBuffer[1] = (byte)b1;
+                            fOffset = 2;
+                            return out - offset;
+                        }
+                        expectedByte(3, 3);
+                    }
+                    count++;
+                }
+                if ((b2 & 0xC0) != 0x80) {
+                    if (out > offset) {
+                        fBuffer[0] = (byte)b0;
+                        fBuffer[1] = (byte)b1;
+                        fBuffer[2] = (byte)b2;
+                        fOffset = 3;
+                        return out - offset;
+                    }
+                    invalidByte(3, 3);
+                }
+                int c = ((b0 << 12) & 0xF000) | ((b1 << 6) & 0x0FC0) |
+                        (b2 & 0x003F);
+                ch[out++] = (char)c;
+                count -= 2;
+                continue;
+            }
+
+            // UTF-8:   [1111 0uuu] [10uu zzzz] [10yy yyyy] [10xx xxxx]*
+            // Unicode: [1101 10ww] [wwzz zzyy] (high surrogate)
+            //          [1101 11yy] [yyxx xxxx] (low surrogate)
+            //          * uuuuu = wwww + 1
+            if ((b0 & 0xF8) == 0xF0) {
+                int b1 = -1;
+                if (++in < total) { 
+                    b1 = fBuffer[in] & 0x00FF; 
+                }
+                else {
+                    b1 = fInputStream.read();
+                    if (b1 == -1) {
+                        if (out > offset) {
+                            fBuffer[0] = (byte)b0;
+                            fOffset = 1;
+                            return out - offset;
+                        }
+                        expectedByte(2, 4);
+                    }
+                    count++;
+                }
+                if ((b1 & 0xC0) != 0x80) {
+                    if (out > offset) {
+                        fBuffer[0] = (byte)b0;
+                        fBuffer[1] = (byte)b1;
+                        fOffset = 2;
+                        return out - offset;
+                    }
+                    invalidByte(2, 4);
+                }
+                int b2 = -1;
+                if (++in < total) { 
+                    b2 = fBuffer[in] & 0x00FF; 
+                }
+                else {
+                    b2 = fInputStream.read();
+                    if (b2 == -1) {
+                        if (out > offset) {
+                            fBuffer[0] = (byte)b0;
+                            fBuffer[1] = (byte)b1;
+                            fOffset = 2;
+                            return out - offset;
+                        }
+                        expectedByte(3, 4);
+                    }
+                    count++;
+                }
+                if ((b2 & 0xC0) != 0x80) {
+                    if (out > offset) {
+                        fBuffer[0] = (byte)b0;
+                        fBuffer[1] = (byte)b1;
+                        fBuffer[2] = (byte)b2;
+                        fOffset = 3;
+                        return out - offset;
+                    }
+                    invalidByte(3, 4);
+                }
+                int b3 = -1;
+                if (++in < total) { 
+                    b3 = fBuffer[in] & 0x00FF; 
+                }
+                else {
+                    b3 = fInputStream.read();
+                    if (b3 == -1) {
+                        if (out > offset) {
+                            fBuffer[0] = (byte)b0;
+                            fBuffer[1] = (byte)b1;
+                            fBuffer[2] = (byte)b2;
+                            fOffset = 3;
+                            return out - offset;
+                        }
+                        expectedByte(4, 4);
+                    }
+                    count++;
+                }
+                if ((b3 & 0xC0) != 0x80) {
+                    if (out > offset) {
+                        fBuffer[0] = (byte)b0;
+                        fBuffer[1] = (byte)b1;
+                        fBuffer[2] = (byte)b2;
+                        fBuffer[3] = (byte)b3;
+                        fOffset = 4;
+                        return out - offset;
+                    }
+                    invalidByte(4, 4);
+                }
+
+                // decode bytes into surrogate characters
+                int uuuuu = ((b0 << 2) & 0x001C) | ((b1 >> 4) & 0x0003);
+                if (uuuuu > 0x10) {
+                    invalidSurrogate(uuuuu);
+                }
+                int wwww = uuuuu - 1;
+                int zzzz = b1 & 0x000F;
+                int yyyyyy = b2 & 0x003F;
+                int xxxxxx = b3 & 0x003F;
+                int hs = 0xD800 | ((wwww << 6) & 0x03C0) | (zzzz << 2) | (yyyyyy >> 4);
+                int ls = 0xDC00 | ((yyyyyy << 6) & 0x03C0) | xxxxxx;
+
+                // set characters
+                ch[out++] = (char)hs;
+                ch[out++] = (char)ls;
+                count -= 2;
+                continue;
+            }
+
+            // error
+            if (out > offset) {
+                fBuffer[0] = (byte)b0;
+                fOffset = 1;
+                return out - offset;
+            }
+            invalidByte(1, 1);
+        }
+
+        // return number of characters converted
+        if (DEBUG_READ) {
+            if (log.isDebugEnabled())
+                log.debug("read(char[],"+offset+','+length+"): count="+count);
+        }
+        return count;
+
+    } // read(char[],int,int)
+
+    /**
+     * Skip characters.  This method will block until some characters are
+     * available, an I/O error occurs, or the end of the stream is reached.
+     *
+     * @param  n  The number of characters to skip
+     *
+     * @return    The number of characters actually skipped
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    @Override
+    public long skip(long n) throws IOException {
+
+        long remaining = n;
+        final char[] ch = new char[fBuffer.length];
+        do {
+            int length = ch.length < remaining ? ch.length : (int)remaining;
+            int count = read(ch, 0, length);
+            if (count > 0) {
+                remaining -= count;
+            }
+            else {
+                break;
+            }
+        } while (remaining > 0);
+
+        long skipped = n - remaining;
+        return skipped;
+
+    } // skip(long):long
+
+    /**
+     * Tell whether this stream is ready to be read.
+     *
+     * @return True if the next read() is guaranteed not to block for input,
+     * false otherwise.  Note that returning false does not guarantee that the
+     * next read will block.
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    @Override
+    public boolean ready() throws IOException {
+        return false;
+    } // ready()
+
+    /**
+     * Tell whether this stream supports the mark() operation.
+     */
+    @Override
+    public boolean markSupported() {
+        return false;
+    } // markSupported()
+
+    /**
+     * Mark the present position in the stream.  Subsequent calls to reset()
+     * will attempt to reposition the stream to this point.  Not all
+     * character-input streams support the mark() operation.
+     *
+     * @param  readAheadLimit  Limit on the number of characters that may be
+     *                         read while still preserving the mark.  After
+     *                         reading this many characters, attempting to
+     *                         reset the stream may fail.
+     *
+     * @exception  IOException  If the stream does not support mark(),
+     *                          or if some other I/O error occurs
+     */
+    @Override
+    public void mark(int readAheadLimit) throws IOException {
+        throw new IOException(
+                Localizer.getMessage("jsp.error.xml.operationNotSupported",
+                                     "mark()", "UTF-8"));
+    }
+
+    /**
+     * Reset the stream.  If the stream has been marked, then attempt to
+     * reposition it at the mark.  If the stream has not been marked, then
+     * attempt to reset it in some way appropriate to the particular stream,
+     * for example by repositioning it to its starting point.  Not all
+     * character-input streams support the reset() operation, and some support
+     * reset() without supporting mark().
+     *
+     * @exception  IOException  If the stream has not been marked,
+     *                          or if the mark has been invalidated,
+     *                          or if the stream does not support reset(),
+     *                          or if some other I/O error occurs
+     */
+    @Override
+    public void reset() throws IOException {
+        fOffset = 0;
+        fSurrogate = -1;
+    } // reset()
+
+    /**
+     * Close the stream.  Once a stream has been closed, further read(),
+     * ready(), mark(), or reset() invocations will throw an IOException.
+     * Closing a previously-closed stream, however, has no effect.
+     *
+     * @exception  IOException  If an I/O error occurs
+     */
+    @Override
+    public void close() throws IOException {
+        fInputStream.close();
+    } // close()
+
+    //
+    // Private methods
+    //
+
+    /** Throws an exception for expected byte. */
+    private void expectedByte(int position, int count)
+        throws UTFDataFormatException {
+
+        throw new UTFDataFormatException(
+                Localizer.getMessage("jsp.error.xml.expectedByte",
+                                     Integer.toString(position),
+                                     Integer.toString(count)));
+
+    }
+
+    /** Throws an exception for invalid byte. */
+    private void invalidByte(int position, int count) 
+        throws UTFDataFormatException {
+
+        throw new UTFDataFormatException(
+                Localizer.getMessage("jsp.error.xml.invalidByte",
+                                     Integer.toString(position),
+                                     Integer.toString(count)));
+    }
+
+    /** Throws an exception for invalid surrogate bits. */
+    private void invalidSurrogate(int uuuuu) throws UTFDataFormatException {
+        
+        throw new UTFDataFormatException(
+                Localizer.getMessage("jsp.error.xml.invalidHighSurrogate",
+                                     Integer.toHexString(uuuuu)));
+    }
+
+} // class UTF8Reader
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/XMLChar.java b/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/XMLChar.java
new file mode 100644
index 0000000..21d4c73
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/XMLChar.java
@@ -0,0 +1,1028 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation and was
+ * originally based on software copyright (c) 1999, International
+ * Business Machines, Inc., http://www.apache.org.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.jasper.xmlparser;
+
+import java.util.Arrays;
+
+/**
+ * This class defines the basic XML character properties. The data
+ * in this class can be used to verify that a character is a valid
+ * XML character or if the character is a space, name start, or name
+ * character.
+ * <p>
+ * A series of convenience methods are supplied to ease the burden
+ * of the developer. Because inlining the checks can improve per
+ * character performance, the tables of character properties are
+ * public. Using the character as an index into the <code>CHARS</code>
+ * array and applying the appropriate mask flag (e.g.
+ * <code>MASK_VALID</code>), yields the same results as calling the
+ * convenience methods. There is one exception: check the comments
+ * for the <code>isValid</code> method for details.
+ *
+ * @author Glenn Marcy, IBM
+ * @author Andy Clark, IBM
+ * @author Eric Ye, IBM
+ * @author Arnaud  Le Hors, IBM
+ * @author Michael Glavassevich, IBM
+ * @author Rahul Srivastava, Sun Microsystems Inc.
+ *
+ * @version $Id: XMLChar.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ */
+public class XMLChar {
+
+    //
+    // Constants
+    //
+
+    /** Character flags. */
+    private static final byte[] CHARS = new byte[1 << 16];
+
+    /** Valid character mask. */
+    public static final int MASK_VALID = 0x01;
+
+    /** Space character mask. */
+    public static final int MASK_SPACE = 0x02;
+
+    /** Name start character mask. */
+    public static final int MASK_NAME_START = 0x04;
+
+    /** Name character mask. */
+    public static final int MASK_NAME = 0x08;
+
+    /** Pubid character mask. */
+    public static final int MASK_PUBID = 0x10;
+    
+    /** 
+     * Content character mask. Special characters are those that can
+     * be considered the start of markup, such as '&lt;' and '&amp;'. 
+     * The various newline characters are considered special as well.
+     * All other valid XML characters can be considered content.
+     * <p>
+     * This is an optimization for the inner loop of character scanning.
+     */
+    public static final int MASK_CONTENT = 0x20;
+
+    /** NCName start character mask. */
+    public static final int MASK_NCNAME_START = 0x40;
+
+    /** NCName character mask. */
+    public static final int MASK_NCNAME = 0x80;
+
+    //
+    // Static initialization
+    //
+
+    static {
+        
+        // Initializing the Character Flag Array
+        // Code generated by: XMLCharGenerator.
+        
+        CHARS[9] = 35;
+        CHARS[10] = 19;
+        CHARS[13] = 19;
+        CHARS[32] = 51;
+        CHARS[33] = 49;
+        CHARS[34] = 33;
+        Arrays.fill(CHARS, 35, 38, (byte) 49 ); // Fill 3 of value (byte) 49
+        CHARS[38] = 1;
+        Arrays.fill(CHARS, 39, 45, (byte) 49 ); // Fill 6 of value (byte) 49
+        Arrays.fill(CHARS, 45, 47, (byte) -71 ); // Fill 2 of value (byte) -71
+        CHARS[47] = 49;
+        Arrays.fill(CHARS, 48, 58, (byte) -71 ); // Fill 10 of value (byte) -71
+        CHARS[58] = 61;
+        CHARS[59] = 49;
+        CHARS[60] = 1;
+        CHARS[61] = 49;
+        CHARS[62] = 33;
+        Arrays.fill(CHARS, 63, 65, (byte) 49 ); // Fill 2 of value (byte) 49
+        Arrays.fill(CHARS, 65, 91, (byte) -3 ); // Fill 26 of value (byte) -3
+        Arrays.fill(CHARS, 91, 93, (byte) 33 ); // Fill 2 of value (byte) 33
+        CHARS[93] = 1;
+        CHARS[94] = 33;
+        CHARS[95] = -3;
+        CHARS[96] = 33;
+        Arrays.fill(CHARS, 97, 123, (byte) -3 ); // Fill 26 of value (byte) -3
+        Arrays.fill(CHARS, 123, 183, (byte) 33 ); // Fill 60 of value (byte) 33
+        CHARS[183] = -87;
+        Arrays.fill(CHARS, 184, 192, (byte) 33 ); // Fill 8 of value (byte) 33
+        Arrays.fill(CHARS, 192, 215, (byte) -19 ); // Fill 23 of value (byte) -19
+        CHARS[215] = 33;
+        Arrays.fill(CHARS, 216, 247, (byte) -19 ); // Fill 31 of value (byte) -19
+        CHARS[247] = 33;
+        Arrays.fill(CHARS, 248, 306, (byte) -19 ); // Fill 58 of value (byte) -19
+        Arrays.fill(CHARS, 306, 308, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 308, 319, (byte) -19 ); // Fill 11 of value (byte) -19
+        Arrays.fill(CHARS, 319, 321, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 321, 329, (byte) -19 ); // Fill 8 of value (byte) -19
+        CHARS[329] = 33;
+        Arrays.fill(CHARS, 330, 383, (byte) -19 ); // Fill 53 of value (byte) -19
+        CHARS[383] = 33;
+        Arrays.fill(CHARS, 384, 452, (byte) -19 ); // Fill 68 of value (byte) -19
+        Arrays.fill(CHARS, 452, 461, (byte) 33 ); // Fill 9 of value (byte) 33
+        Arrays.fill(CHARS, 461, 497, (byte) -19 ); // Fill 36 of value (byte) -19
+        Arrays.fill(CHARS, 497, 500, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 500, 502, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 502, 506, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 506, 536, (byte) -19 ); // Fill 30 of value (byte) -19
+        Arrays.fill(CHARS, 536, 592, (byte) 33 ); // Fill 56 of value (byte) 33
+        Arrays.fill(CHARS, 592, 681, (byte) -19 ); // Fill 89 of value (byte) -19
+        Arrays.fill(CHARS, 681, 699, (byte) 33 ); // Fill 18 of value (byte) 33
+        Arrays.fill(CHARS, 699, 706, (byte) -19 ); // Fill 7 of value (byte) -19
+        Arrays.fill(CHARS, 706, 720, (byte) 33 ); // Fill 14 of value (byte) 33
+        Arrays.fill(CHARS, 720, 722, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 722, 768, (byte) 33 ); // Fill 46 of value (byte) 33
+        Arrays.fill(CHARS, 768, 838, (byte) -87 ); // Fill 70 of value (byte) -87
+        Arrays.fill(CHARS, 838, 864, (byte) 33 ); // Fill 26 of value (byte) 33
+        Arrays.fill(CHARS, 864, 866, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 866, 902, (byte) 33 ); // Fill 36 of value (byte) 33
+        CHARS[902] = -19;
+        CHARS[903] = -87;
+        Arrays.fill(CHARS, 904, 907, (byte) -19 ); // Fill 3 of value (byte) -19
+        CHARS[907] = 33;
+        CHARS[908] = -19;
+        CHARS[909] = 33;
+        Arrays.fill(CHARS, 910, 930, (byte) -19 ); // Fill 20 of value (byte) -19
+        CHARS[930] = 33;
+        Arrays.fill(CHARS, 931, 975, (byte) -19 ); // Fill 44 of value (byte) -19
+        CHARS[975] = 33;
+        Arrays.fill(CHARS, 976, 983, (byte) -19 ); // Fill 7 of value (byte) -19
+        Arrays.fill(CHARS, 983, 986, (byte) 33 ); // Fill 3 of value (byte) 33
+        CHARS[986] = -19;
+        CHARS[987] = 33;
+        CHARS[988] = -19;
+        CHARS[989] = 33;
+        CHARS[990] = -19;
+        CHARS[991] = 33;
+        CHARS[992] = -19;
+        CHARS[993] = 33;
+        Arrays.fill(CHARS, 994, 1012, (byte) -19 ); // Fill 18 of value (byte) -19
+        Arrays.fill(CHARS, 1012, 1025, (byte) 33 ); // Fill 13 of value (byte) 33
+        Arrays.fill(CHARS, 1025, 1037, (byte) -19 ); // Fill 12 of value (byte) -19
+        CHARS[1037] = 33;
+        Arrays.fill(CHARS, 1038, 1104, (byte) -19 ); // Fill 66 of value (byte) -19
+        CHARS[1104] = 33;
+        Arrays.fill(CHARS, 1105, 1117, (byte) -19 ); // Fill 12 of value (byte) -19
+        CHARS[1117] = 33;
+        Arrays.fill(CHARS, 1118, 1154, (byte) -19 ); // Fill 36 of value (byte) -19
+        CHARS[1154] = 33;
+        Arrays.fill(CHARS, 1155, 1159, (byte) -87 ); // Fill 4 of value (byte) -87
+        Arrays.fill(CHARS, 1159, 1168, (byte) 33 ); // Fill 9 of value (byte) 33
+        Arrays.fill(CHARS, 1168, 1221, (byte) -19 ); // Fill 53 of value (byte) -19
+        Arrays.fill(CHARS, 1221, 1223, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 1223, 1225, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 1225, 1227, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 1227, 1229, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 1229, 1232, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 1232, 1260, (byte) -19 ); // Fill 28 of value (byte) -19
+        Arrays.fill(CHARS, 1260, 1262, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 1262, 1270, (byte) -19 ); // Fill 8 of value (byte) -19
+        Arrays.fill(CHARS, 1270, 1272, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 1272, 1274, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 1274, 1329, (byte) 33 ); // Fill 55 of value (byte) 33
+        Arrays.fill(CHARS, 1329, 1367, (byte) -19 ); // Fill 38 of value (byte) -19
+        Arrays.fill(CHARS, 1367, 1369, (byte) 33 ); // Fill 2 of value (byte) 33
+        CHARS[1369] = -19;
+        Arrays.fill(CHARS, 1370, 1377, (byte) 33 ); // Fill 7 of value (byte) 33
+        Arrays.fill(CHARS, 1377, 1415, (byte) -19 ); // Fill 38 of value (byte) -19
+        Arrays.fill(CHARS, 1415, 1425, (byte) 33 ); // Fill 10 of value (byte) 33
+        Arrays.fill(CHARS, 1425, 1442, (byte) -87 ); // Fill 17 of value (byte) -87
+        CHARS[1442] = 33;
+        Arrays.fill(CHARS, 1443, 1466, (byte) -87 ); // Fill 23 of value (byte) -87
+        CHARS[1466] = 33;
+        Arrays.fill(CHARS, 1467, 1470, (byte) -87 ); // Fill 3 of value (byte) -87
+        CHARS[1470] = 33;
+        CHARS[1471] = -87;
+        CHARS[1472] = 33;
+        Arrays.fill(CHARS, 1473, 1475, (byte) -87 ); // Fill 2 of value (byte) -87
+        CHARS[1475] = 33;
+        CHARS[1476] = -87;
+        Arrays.fill(CHARS, 1477, 1488, (byte) 33 ); // Fill 11 of value (byte) 33
+        Arrays.fill(CHARS, 1488, 1515, (byte) -19 ); // Fill 27 of value (byte) -19
+        Arrays.fill(CHARS, 1515, 1520, (byte) 33 ); // Fill 5 of value (byte) 33
+        Arrays.fill(CHARS, 1520, 1523, (byte) -19 ); // Fill 3 of value (byte) -19
+        Arrays.fill(CHARS, 1523, 1569, (byte) 33 ); // Fill 46 of value (byte) 33
+        Arrays.fill(CHARS, 1569, 1595, (byte) -19 ); // Fill 26 of value (byte) -19
+        Arrays.fill(CHARS, 1595, 1600, (byte) 33 ); // Fill 5 of value (byte) 33
+        CHARS[1600] = -87;
+        Arrays.fill(CHARS, 1601, 1611, (byte) -19 ); // Fill 10 of value (byte) -19
+        Arrays.fill(CHARS, 1611, 1619, (byte) -87 ); // Fill 8 of value (byte) -87
+        Arrays.fill(CHARS, 1619, 1632, (byte) 33 ); // Fill 13 of value (byte) 33
+        Arrays.fill(CHARS, 1632, 1642, (byte) -87 ); // Fill 10 of value (byte) -87
+        Arrays.fill(CHARS, 1642, 1648, (byte) 33 ); // Fill 6 of value (byte) 33
+        CHARS[1648] = -87;
+        Arrays.fill(CHARS, 1649, 1720, (byte) -19 ); // Fill 71 of value (byte) -19
+        Arrays.fill(CHARS, 1720, 1722, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 1722, 1727, (byte) -19 ); // Fill 5 of value (byte) -19
+        CHARS[1727] = 33;
+        Arrays.fill(CHARS, 1728, 1743, (byte) -19 ); // Fill 15 of value (byte) -19
+        CHARS[1743] = 33;
+        Arrays.fill(CHARS, 1744, 1748, (byte) -19 ); // Fill 4 of value (byte) -19
+        CHARS[1748] = 33;
+        CHARS[1749] = -19;
+        Arrays.fill(CHARS, 1750, 1765, (byte) -87 ); // Fill 15 of value (byte) -87
+        Arrays.fill(CHARS, 1765, 1767, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 1767, 1769, (byte) -87 ); // Fill 2 of value (byte) -87
+        CHARS[1769] = 33;
+        Arrays.fill(CHARS, 1770, 1774, (byte) -87 ); // Fill 4 of value (byte) -87
+        Arrays.fill(CHARS, 1774, 1776, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 1776, 1786, (byte) -87 ); // Fill 10 of value (byte) -87
+        Arrays.fill(CHARS, 1786, 2305, (byte) 33 ); // Fill 519 of value (byte) 33
+        Arrays.fill(CHARS, 2305, 2308, (byte) -87 ); // Fill 3 of value (byte) -87
+        CHARS[2308] = 33;
+        Arrays.fill(CHARS, 2309, 2362, (byte) -19 ); // Fill 53 of value (byte) -19
+        Arrays.fill(CHARS, 2362, 2364, (byte) 33 ); // Fill 2 of value (byte) 33
+        CHARS[2364] = -87;
+        CHARS[2365] = -19;
+        Arrays.fill(CHARS, 2366, 2382, (byte) -87 ); // Fill 16 of value (byte) -87
+        Arrays.fill(CHARS, 2382, 2385, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 2385, 2389, (byte) -87 ); // Fill 4 of value (byte) -87
+        Arrays.fill(CHARS, 2389, 2392, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 2392, 2402, (byte) -19 ); // Fill 10 of value (byte) -19
+        Arrays.fill(CHARS, 2402, 2404, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 2404, 2406, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2406, 2416, (byte) -87 ); // Fill 10 of value (byte) -87
+        Arrays.fill(CHARS, 2416, 2433, (byte) 33 ); // Fill 17 of value (byte) 33
+        Arrays.fill(CHARS, 2433, 2436, (byte) -87 ); // Fill 3 of value (byte) -87
+        CHARS[2436] = 33;
+        Arrays.fill(CHARS, 2437, 2445, (byte) -19 ); // Fill 8 of value (byte) -19
+        Arrays.fill(CHARS, 2445, 2447, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2447, 2449, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 2449, 2451, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2451, 2473, (byte) -19 ); // Fill 22 of value (byte) -19
+        CHARS[2473] = 33;
+        Arrays.fill(CHARS, 2474, 2481, (byte) -19 ); // Fill 7 of value (byte) -19
+        CHARS[2481] = 33;
+        CHARS[2482] = -19;
+        Arrays.fill(CHARS, 2483, 2486, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 2486, 2490, (byte) -19 ); // Fill 4 of value (byte) -19
+        Arrays.fill(CHARS, 2490, 2492, (byte) 33 ); // Fill 2 of value (byte) 33
+        CHARS[2492] = -87;
+        CHARS[2493] = 33;
+        Arrays.fill(CHARS, 2494, 2501, (byte) -87 ); // Fill 7 of value (byte) -87
+        Arrays.fill(CHARS, 2501, 2503, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2503, 2505, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 2505, 2507, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2507, 2510, (byte) -87 ); // Fill 3 of value (byte) -87
+        Arrays.fill(CHARS, 2510, 2519, (byte) 33 ); // Fill 9 of value (byte) 33
+        CHARS[2519] = -87;
+        Arrays.fill(CHARS, 2520, 2524, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 2524, 2526, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[2526] = 33;
+        Arrays.fill(CHARS, 2527, 2530, (byte) -19 ); // Fill 3 of value (byte) -19
+        Arrays.fill(CHARS, 2530, 2532, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 2532, 2534, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2534, 2544, (byte) -87 ); // Fill 10 of value (byte) -87
+        Arrays.fill(CHARS, 2544, 2546, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 2546, 2562, (byte) 33 ); // Fill 16 of value (byte) 33
+        CHARS[2562] = -87;
+        Arrays.fill(CHARS, 2563, 2565, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2565, 2571, (byte) -19 ); // Fill 6 of value (byte) -19
+        Arrays.fill(CHARS, 2571, 2575, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 2575, 2577, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 2577, 2579, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2579, 2601, (byte) -19 ); // Fill 22 of value (byte) -19
+        CHARS[2601] = 33;
+        Arrays.fill(CHARS, 2602, 2609, (byte) -19 ); // Fill 7 of value (byte) -19
+        CHARS[2609] = 33;
+        Arrays.fill(CHARS, 2610, 2612, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[2612] = 33;
+        Arrays.fill(CHARS, 2613, 2615, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[2615] = 33;
+        Arrays.fill(CHARS, 2616, 2618, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 2618, 2620, (byte) 33 ); // Fill 2 of value (byte) 33
+        CHARS[2620] = -87;
+        CHARS[2621] = 33;
+        Arrays.fill(CHARS, 2622, 2627, (byte) -87 ); // Fill 5 of value (byte) -87
+        Arrays.fill(CHARS, 2627, 2631, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 2631, 2633, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 2633, 2635, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2635, 2638, (byte) -87 ); // Fill 3 of value (byte) -87
+        Arrays.fill(CHARS, 2638, 2649, (byte) 33 ); // Fill 11 of value (byte) 33
+        Arrays.fill(CHARS, 2649, 2653, (byte) -19 ); // Fill 4 of value (byte) -19
+        CHARS[2653] = 33;
+        CHARS[2654] = -19;
+        Arrays.fill(CHARS, 2655, 2662, (byte) 33 ); // Fill 7 of value (byte) 33
+        Arrays.fill(CHARS, 2662, 2674, (byte) -87 ); // Fill 12 of value (byte) -87
+        Arrays.fill(CHARS, 2674, 2677, (byte) -19 ); // Fill 3 of value (byte) -19
+        Arrays.fill(CHARS, 2677, 2689, (byte) 33 ); // Fill 12 of value (byte) 33
+        Arrays.fill(CHARS, 2689, 2692, (byte) -87 ); // Fill 3 of value (byte) -87
+        CHARS[2692] = 33;
+        Arrays.fill(CHARS, 2693, 2700, (byte) -19 ); // Fill 7 of value (byte) -19
+        CHARS[2700] = 33;
+        CHARS[2701] = -19;
+        CHARS[2702] = 33;
+        Arrays.fill(CHARS, 2703, 2706, (byte) -19 ); // Fill 3 of value (byte) -19
+        CHARS[2706] = 33;
+        Arrays.fill(CHARS, 2707, 2729, (byte) -19 ); // Fill 22 of value (byte) -19
+        CHARS[2729] = 33;
+        Arrays.fill(CHARS, 2730, 2737, (byte) -19 ); // Fill 7 of value (byte) -19
+        CHARS[2737] = 33;
+        Arrays.fill(CHARS, 2738, 2740, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[2740] = 33;
+        Arrays.fill(CHARS, 2741, 2746, (byte) -19 ); // Fill 5 of value (byte) -19
+        Arrays.fill(CHARS, 2746, 2748, (byte) 33 ); // Fill 2 of value (byte) 33
+        CHARS[2748] = -87;
+        CHARS[2749] = -19;
+        Arrays.fill(CHARS, 2750, 2758, (byte) -87 ); // Fill 8 of value (byte) -87
+        CHARS[2758] = 33;
+        Arrays.fill(CHARS, 2759, 2762, (byte) -87 ); // Fill 3 of value (byte) -87
+        CHARS[2762] = 33;
+        Arrays.fill(CHARS, 2763, 2766, (byte) -87 ); // Fill 3 of value (byte) -87
+        Arrays.fill(CHARS, 2766, 2784, (byte) 33 ); // Fill 18 of value (byte) 33
+        CHARS[2784] = -19;
+        Arrays.fill(CHARS, 2785, 2790, (byte) 33 ); // Fill 5 of value (byte) 33
+        Arrays.fill(CHARS, 2790, 2800, (byte) -87 ); // Fill 10 of value (byte) -87
+        Arrays.fill(CHARS, 2800, 2817, (byte) 33 ); // Fill 17 of value (byte) 33
+        Arrays.fill(CHARS, 2817, 2820, (byte) -87 ); // Fill 3 of value (byte) -87
+        CHARS[2820] = 33;
+        Arrays.fill(CHARS, 2821, 2829, (byte) -19 ); // Fill 8 of value (byte) -19
+        Arrays.fill(CHARS, 2829, 2831, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2831, 2833, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 2833, 2835, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2835, 2857, (byte) -19 ); // Fill 22 of value (byte) -19
+        CHARS[2857] = 33;
+        Arrays.fill(CHARS, 2858, 2865, (byte) -19 ); // Fill 7 of value (byte) -19
+        CHARS[2865] = 33;
+        Arrays.fill(CHARS, 2866, 2868, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 2868, 2870, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2870, 2874, (byte) -19 ); // Fill 4 of value (byte) -19
+        Arrays.fill(CHARS, 2874, 2876, (byte) 33 ); // Fill 2 of value (byte) 33
+        CHARS[2876] = -87;
+        CHARS[2877] = -19;
+        Arrays.fill(CHARS, 2878, 2884, (byte) -87 ); // Fill 6 of value (byte) -87
+        Arrays.fill(CHARS, 2884, 2887, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 2887, 2889, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 2889, 2891, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 2891, 2894, (byte) -87 ); // Fill 3 of value (byte) -87
+        Arrays.fill(CHARS, 2894, 2902, (byte) 33 ); // Fill 8 of value (byte) 33
+        Arrays.fill(CHARS, 2902, 2904, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 2904, 2908, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 2908, 2910, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[2910] = 33;
+        Arrays.fill(CHARS, 2911, 2914, (byte) -19 ); // Fill 3 of value (byte) -19
+        Arrays.fill(CHARS, 2914, 2918, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 2918, 2928, (byte) -87 ); // Fill 10 of value (byte) -87
+        Arrays.fill(CHARS, 2928, 2946, (byte) 33 ); // Fill 18 of value (byte) 33
+        Arrays.fill(CHARS, 2946, 2948, (byte) -87 ); // Fill 2 of value (byte) -87
+        CHARS[2948] = 33;
+        Arrays.fill(CHARS, 2949, 2955, (byte) -19 ); // Fill 6 of value (byte) -19
+        Arrays.fill(CHARS, 2955, 2958, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 2958, 2961, (byte) -19 ); // Fill 3 of value (byte) -19
+        CHARS[2961] = 33;
+        Arrays.fill(CHARS, 2962, 2966, (byte) -19 ); // Fill 4 of value (byte) -19
+        Arrays.fill(CHARS, 2966, 2969, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 2969, 2971, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[2971] = 33;
+        CHARS[2972] = -19;
+        CHARS[2973] = 33;
+        Arrays.fill(CHARS, 2974, 2976, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 2976, 2979, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 2979, 2981, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 2981, 2984, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 2984, 2987, (byte) -19 ); // Fill 3 of value (byte) -19
+        Arrays.fill(CHARS, 2987, 2990, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 2990, 2998, (byte) -19 ); // Fill 8 of value (byte) -19
+        CHARS[2998] = 33;
+        Arrays.fill(CHARS, 2999, 3002, (byte) -19 ); // Fill 3 of value (byte) -19
+        Arrays.fill(CHARS, 3002, 3006, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 3006, 3011, (byte) -87 ); // Fill 5 of value (byte) -87
+        Arrays.fill(CHARS, 3011, 3014, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 3014, 3017, (byte) -87 ); // Fill 3 of value (byte) -87
+        CHARS[3017] = 33;
+        Arrays.fill(CHARS, 3018, 3022, (byte) -87 ); // Fill 4 of value (byte) -87
+        Arrays.fill(CHARS, 3022, 3031, (byte) 33 ); // Fill 9 of value (byte) 33
+        CHARS[3031] = -87;
+        Arrays.fill(CHARS, 3032, 3047, (byte) 33 ); // Fill 15 of value (byte) 33
+        Arrays.fill(CHARS, 3047, 3056, (byte) -87 ); // Fill 9 of value (byte) -87
+        Arrays.fill(CHARS, 3056, 3073, (byte) 33 ); // Fill 17 of value (byte) 33
+        Arrays.fill(CHARS, 3073, 3076, (byte) -87 ); // Fill 3 of value (byte) -87
+        CHARS[3076] = 33;
+        Arrays.fill(CHARS, 3077, 3085, (byte) -19 ); // Fill 8 of value (byte) -19
+        CHARS[3085] = 33;
+        Arrays.fill(CHARS, 3086, 3089, (byte) -19 ); // Fill 3 of value (byte) -19
+        CHARS[3089] = 33;
+        Arrays.fill(CHARS, 3090, 3113, (byte) -19 ); // Fill 23 of value (byte) -19
+        CHARS[3113] = 33;
+        Arrays.fill(CHARS, 3114, 3124, (byte) -19 ); // Fill 10 of value (byte) -19
+        CHARS[3124] = 33;
+        Arrays.fill(CHARS, 3125, 3130, (byte) -19 ); // Fill 5 of value (byte) -19
+        Arrays.fill(CHARS, 3130, 3134, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 3134, 3141, (byte) -87 ); // Fill 7 of value (byte) -87
+        CHARS[3141] = 33;
+        Arrays.fill(CHARS, 3142, 3145, (byte) -87 ); // Fill 3 of value (byte) -87
+        CHARS[3145] = 33;
+        Arrays.fill(CHARS, 3146, 3150, (byte) -87 ); // Fill 4 of value (byte) -87
+        Arrays.fill(CHARS, 3150, 3157, (byte) 33 ); // Fill 7 of value (byte) 33
+        Arrays.fill(CHARS, 3157, 3159, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 3159, 3168, (byte) 33 ); // Fill 9 of value (byte) 33
+        Arrays.fill(CHARS, 3168, 3170, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 3170, 3174, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 3174, 3184, (byte) -87 ); // Fill 10 of value (byte) -87
+        Arrays.fill(CHARS, 3184, 3202, (byte) 33 ); // Fill 18 of value (byte) 33
+        Arrays.fill(CHARS, 3202, 3204, (byte) -87 ); // Fill 2 of value (byte) -87
+        CHARS[3204] = 33;
+        Arrays.fill(CHARS, 3205, 3213, (byte) -19 ); // Fill 8 of value (byte) -19
+        CHARS[3213] = 33;
+        Arrays.fill(CHARS, 3214, 3217, (byte) -19 ); // Fill 3 of value (byte) -19
+        CHARS[3217] = 33;
+        Arrays.fill(CHARS, 3218, 3241, (byte) -19 ); // Fill 23 of value (byte) -19
+        CHARS[3241] = 33;
+        Arrays.fill(CHARS, 3242, 3252, (byte) -19 ); // Fill 10 of value (byte) -19
+        CHARS[3252] = 33;
+        Arrays.fill(CHARS, 3253, 3258, (byte) -19 ); // Fill 5 of value (byte) -19
+        Arrays.fill(CHARS, 3258, 3262, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 3262, 3269, (byte) -87 ); // Fill 7 of value (byte) -87
+        CHARS[3269] = 33;
+        Arrays.fill(CHARS, 3270, 3273, (byte) -87 ); // Fill 3 of value (byte) -87
+        CHARS[3273] = 33;
+        Arrays.fill(CHARS, 3274, 3278, (byte) -87 ); // Fill 4 of value (byte) -87
+        Arrays.fill(CHARS, 3278, 3285, (byte) 33 ); // Fill 7 of value (byte) 33
+        Arrays.fill(CHARS, 3285, 3287, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 3287, 3294, (byte) 33 ); // Fill 7 of value (byte) 33
+        CHARS[3294] = -19;
+        CHARS[3295] = 33;
+        Arrays.fill(CHARS, 3296, 3298, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 3298, 3302, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 3302, 3312, (byte) -87 ); // Fill 10 of value (byte) -87
+        Arrays.fill(CHARS, 3312, 3330, (byte) 33 ); // Fill 18 of value (byte) 33
+        Arrays.fill(CHARS, 3330, 3332, (byte) -87 ); // Fill 2 of value (byte) -87
+        CHARS[3332] = 33;
+        Arrays.fill(CHARS, 3333, 3341, (byte) -19 ); // Fill 8 of value (byte) -19
+        CHARS[3341] = 33;
+        Arrays.fill(CHARS, 3342, 3345, (byte) -19 ); // Fill 3 of value (byte) -19
+        CHARS[3345] = 33;
+        Arrays.fill(CHARS, 3346, 3369, (byte) -19 ); // Fill 23 of value (byte) -19
+        CHARS[3369] = 33;
+        Arrays.fill(CHARS, 3370, 3386, (byte) -19 ); // Fill 16 of value (byte) -19
+        Arrays.fill(CHARS, 3386, 3390, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 3390, 3396, (byte) -87 ); // Fill 6 of value (byte) -87
+        Arrays.fill(CHARS, 3396, 3398, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 3398, 3401, (byte) -87 ); // Fill 3 of value (byte) -87
+        CHARS[3401] = 33;
+        Arrays.fill(CHARS, 3402, 3406, (byte) -87 ); // Fill 4 of value (byte) -87
+        Arrays.fill(CHARS, 3406, 3415, (byte) 33 ); // Fill 9 of value (byte) 33
+        CHARS[3415] = -87;
+        Arrays.fill(CHARS, 3416, 3424, (byte) 33 ); // Fill 8 of value (byte) 33
+        Arrays.fill(CHARS, 3424, 3426, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 3426, 3430, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 3430, 3440, (byte) -87 ); // Fill 10 of value (byte) -87
+        Arrays.fill(CHARS, 3440, 3585, (byte) 33 ); // Fill 145 of value (byte) 33
+        Arrays.fill(CHARS, 3585, 3631, (byte) -19 ); // Fill 46 of value (byte) -19
+        CHARS[3631] = 33;
+        CHARS[3632] = -19;
+        CHARS[3633] = -87;
+        Arrays.fill(CHARS, 3634, 3636, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 3636, 3643, (byte) -87 ); // Fill 7 of value (byte) -87
+        Arrays.fill(CHARS, 3643, 3648, (byte) 33 ); // Fill 5 of value (byte) 33
+        Arrays.fill(CHARS, 3648, 3654, (byte) -19 ); // Fill 6 of value (byte) -19
+        Arrays.fill(CHARS, 3654, 3663, (byte) -87 ); // Fill 9 of value (byte) -87
+        CHARS[3663] = 33;
+        Arrays.fill(CHARS, 3664, 3674, (byte) -87 ); // Fill 10 of value (byte) -87
+        Arrays.fill(CHARS, 3674, 3713, (byte) 33 ); // Fill 39 of value (byte) 33
+        Arrays.fill(CHARS, 3713, 3715, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[3715] = 33;
+        CHARS[3716] = -19;
+        Arrays.fill(CHARS, 3717, 3719, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 3719, 3721, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[3721] = 33;
+        CHARS[3722] = -19;
+        Arrays.fill(CHARS, 3723, 3725, (byte) 33 ); // Fill 2 of value (byte) 33
+        CHARS[3725] = -19;
+        Arrays.fill(CHARS, 3726, 3732, (byte) 33 ); // Fill 6 of value (byte) 33
+        Arrays.fill(CHARS, 3732, 3736, (byte) -19 ); // Fill 4 of value (byte) -19
+        CHARS[3736] = 33;
+        Arrays.fill(CHARS, 3737, 3744, (byte) -19 ); // Fill 7 of value (byte) -19
+        CHARS[3744] = 33;
+        Arrays.fill(CHARS, 3745, 3748, (byte) -19 ); // Fill 3 of value (byte) -19
+        CHARS[3748] = 33;
+        CHARS[3749] = -19;
+        CHARS[3750] = 33;
+        CHARS[3751] = -19;
+        Arrays.fill(CHARS, 3752, 3754, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 3754, 3756, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[3756] = 33;
+        Arrays.fill(CHARS, 3757, 3759, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[3759] = 33;
+        CHARS[3760] = -19;
+        CHARS[3761] = -87;
+        Arrays.fill(CHARS, 3762, 3764, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 3764, 3770, (byte) -87 ); // Fill 6 of value (byte) -87
+        CHARS[3770] = 33;
+        Arrays.fill(CHARS, 3771, 3773, (byte) -87 ); // Fill 2 of value (byte) -87
+        CHARS[3773] = -19;
+        Arrays.fill(CHARS, 3774, 3776, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 3776, 3781, (byte) -19 ); // Fill 5 of value (byte) -19
+        CHARS[3781] = 33;
+        CHARS[3782] = -87;
+        CHARS[3783] = 33;
+        Arrays.fill(CHARS, 3784, 3790, (byte) -87 ); // Fill 6 of value (byte) -87
+        Arrays.fill(CHARS, 3790, 3792, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 3792, 3802, (byte) -87 ); // Fill 10 of value (byte) -87
+        Arrays.fill(CHARS, 3802, 3864, (byte) 33 ); // Fill 62 of value (byte) 33
+        Arrays.fill(CHARS, 3864, 3866, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 3866, 3872, (byte) 33 ); // Fill 6 of value (byte) 33
+        Arrays.fill(CHARS, 3872, 3882, (byte) -87 ); // Fill 10 of value (byte) -87
+        Arrays.fill(CHARS, 3882, 3893, (byte) 33 ); // Fill 11 of value (byte) 33
+        CHARS[3893] = -87;
+        CHARS[3894] = 33;
+        CHARS[3895] = -87;
+        CHARS[3896] = 33;
+        CHARS[3897] = -87;
+        Arrays.fill(CHARS, 3898, 3902, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 3902, 3904, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 3904, 3912, (byte) -19 ); // Fill 8 of value (byte) -19
+        CHARS[3912] = 33;
+        Arrays.fill(CHARS, 3913, 3946, (byte) -19 ); // Fill 33 of value (byte) -19
+        Arrays.fill(CHARS, 3946, 3953, (byte) 33 ); // Fill 7 of value (byte) 33
+        Arrays.fill(CHARS, 3953, 3973, (byte) -87 ); // Fill 20 of value (byte) -87
+        CHARS[3973] = 33;
+        Arrays.fill(CHARS, 3974, 3980, (byte) -87 ); // Fill 6 of value (byte) -87
+        Arrays.fill(CHARS, 3980, 3984, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 3984, 3990, (byte) -87 ); // Fill 6 of value (byte) -87
+        CHARS[3990] = 33;
+        CHARS[3991] = -87;
+        CHARS[3992] = 33;
+        Arrays.fill(CHARS, 3993, 4014, (byte) -87 ); // Fill 21 of value (byte) -87
+        Arrays.fill(CHARS, 4014, 4017, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 4017, 4024, (byte) -87 ); // Fill 7 of value (byte) -87
+        CHARS[4024] = 33;
+        CHARS[4025] = -87;
+        Arrays.fill(CHARS, 4026, 4256, (byte) 33 ); // Fill 230 of value (byte) 33
+        Arrays.fill(CHARS, 4256, 4294, (byte) -19 ); // Fill 38 of value (byte) -19
+        Arrays.fill(CHARS, 4294, 4304, (byte) 33 ); // Fill 10 of value (byte) 33
+        Arrays.fill(CHARS, 4304, 4343, (byte) -19 ); // Fill 39 of value (byte) -19
+        Arrays.fill(CHARS, 4343, 4352, (byte) 33 ); // Fill 9 of value (byte) 33
+        CHARS[4352] = -19;
+        CHARS[4353] = 33;
+        Arrays.fill(CHARS, 4354, 4356, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[4356] = 33;
+        Arrays.fill(CHARS, 4357, 4360, (byte) -19 ); // Fill 3 of value (byte) -19
+        CHARS[4360] = 33;
+        CHARS[4361] = -19;
+        CHARS[4362] = 33;
+        Arrays.fill(CHARS, 4363, 4365, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[4365] = 33;
+        Arrays.fill(CHARS, 4366, 4371, (byte) -19 ); // Fill 5 of value (byte) -19
+        Arrays.fill(CHARS, 4371, 4412, (byte) 33 ); // Fill 41 of value (byte) 33
+        CHARS[4412] = -19;
+        CHARS[4413] = 33;
+        CHARS[4414] = -19;
+        CHARS[4415] = 33;
+        CHARS[4416] = -19;
+        Arrays.fill(CHARS, 4417, 4428, (byte) 33 ); // Fill 11 of value (byte) 33
+        CHARS[4428] = -19;
+        CHARS[4429] = 33;
+        CHARS[4430] = -19;
+        CHARS[4431] = 33;
+        CHARS[4432] = -19;
+        Arrays.fill(CHARS, 4433, 4436, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 4436, 4438, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 4438, 4441, (byte) 33 ); // Fill 3 of value (byte) 33
+        CHARS[4441] = -19;
+        Arrays.fill(CHARS, 4442, 4447, (byte) 33 ); // Fill 5 of value (byte) 33
+        Arrays.fill(CHARS, 4447, 4450, (byte) -19 ); // Fill 3 of value (byte) -19
+        CHARS[4450] = 33;
+        CHARS[4451] = -19;
+        CHARS[4452] = 33;
+        CHARS[4453] = -19;
+        CHARS[4454] = 33;
+        CHARS[4455] = -19;
+        CHARS[4456] = 33;
+        CHARS[4457] = -19;
+        Arrays.fill(CHARS, 4458, 4461, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 4461, 4463, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 4463, 4466, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 4466, 4468, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[4468] = 33;
+        CHARS[4469] = -19;
+        Arrays.fill(CHARS, 4470, 4510, (byte) 33 ); // Fill 40 of value (byte) 33
+        CHARS[4510] = -19;
+        Arrays.fill(CHARS, 4511, 4520, (byte) 33 ); // Fill 9 of value (byte) 33
+        CHARS[4520] = -19;
+        Arrays.fill(CHARS, 4521, 4523, (byte) 33 ); // Fill 2 of value (byte) 33
+        CHARS[4523] = -19;
+        Arrays.fill(CHARS, 4524, 4526, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 4526, 4528, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 4528, 4535, (byte) 33 ); // Fill 7 of value (byte) 33
+        Arrays.fill(CHARS, 4535, 4537, (byte) -19 ); // Fill 2 of value (byte) -19
+        CHARS[4537] = 33;
+        CHARS[4538] = -19;
+        CHARS[4539] = 33;
+        Arrays.fill(CHARS, 4540, 4547, (byte) -19 ); // Fill 7 of value (byte) -19
+        Arrays.fill(CHARS, 4547, 4587, (byte) 33 ); // Fill 40 of value (byte) 33
+        CHARS[4587] = -19;
+        Arrays.fill(CHARS, 4588, 4592, (byte) 33 ); // Fill 4 of value (byte) 33
+        CHARS[4592] = -19;
+        Arrays.fill(CHARS, 4593, 4601, (byte) 33 ); // Fill 8 of value (byte) 33
+        CHARS[4601] = -19;
+        Arrays.fill(CHARS, 4602, 7680, (byte) 33 ); // Fill 3078 of value (byte) 33
+        Arrays.fill(CHARS, 7680, 7836, (byte) -19 ); // Fill 156 of value (byte) -19
+        Arrays.fill(CHARS, 7836, 7840, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 7840, 7930, (byte) -19 ); // Fill 90 of value (byte) -19
+        Arrays.fill(CHARS, 7930, 7936, (byte) 33 ); // Fill 6 of value (byte) 33
+        Arrays.fill(CHARS, 7936, 7958, (byte) -19 ); // Fill 22 of value (byte) -19
+        Arrays.fill(CHARS, 7958, 7960, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 7960, 7966, (byte) -19 ); // Fill 6 of value (byte) -19
+        Arrays.fill(CHARS, 7966, 7968, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 7968, 8006, (byte) -19 ); // Fill 38 of value (byte) -19
+        Arrays.fill(CHARS, 8006, 8008, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 8008, 8014, (byte) -19 ); // Fill 6 of value (byte) -19
+        Arrays.fill(CHARS, 8014, 8016, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 8016, 8024, (byte) -19 ); // Fill 8 of value (byte) -19
+        CHARS[8024] = 33;
+        CHARS[8025] = -19;
+        CHARS[8026] = 33;
+        CHARS[8027] = -19;
+        CHARS[8028] = 33;
+        CHARS[8029] = -19;
+        CHARS[8030] = 33;
+        Arrays.fill(CHARS, 8031, 8062, (byte) -19 ); // Fill 31 of value (byte) -19
+        Arrays.fill(CHARS, 8062, 8064, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 8064, 8117, (byte) -19 ); // Fill 53 of value (byte) -19
+        CHARS[8117] = 33;
+        Arrays.fill(CHARS, 8118, 8125, (byte) -19 ); // Fill 7 of value (byte) -19
+        CHARS[8125] = 33;
+        CHARS[8126] = -19;
+        Arrays.fill(CHARS, 8127, 8130, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 8130, 8133, (byte) -19 ); // Fill 3 of value (byte) -19
+        CHARS[8133] = 33;
+        Arrays.fill(CHARS, 8134, 8141, (byte) -19 ); // Fill 7 of value (byte) -19
+        Arrays.fill(CHARS, 8141, 8144, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 8144, 8148, (byte) -19 ); // Fill 4 of value (byte) -19
+        Arrays.fill(CHARS, 8148, 8150, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 8150, 8156, (byte) -19 ); // Fill 6 of value (byte) -19
+        Arrays.fill(CHARS, 8156, 8160, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 8160, 8173, (byte) -19 ); // Fill 13 of value (byte) -19
+        Arrays.fill(CHARS, 8173, 8178, (byte) 33 ); // Fill 5 of value (byte) 33
+        Arrays.fill(CHARS, 8178, 8181, (byte) -19 ); // Fill 3 of value (byte) -19
+        CHARS[8181] = 33;
+        Arrays.fill(CHARS, 8182, 8189, (byte) -19 ); // Fill 7 of value (byte) -19
+        Arrays.fill(CHARS, 8189, 8400, (byte) 33 ); // Fill 211 of value (byte) 33
+        Arrays.fill(CHARS, 8400, 8413, (byte) -87 ); // Fill 13 of value (byte) -87
+        Arrays.fill(CHARS, 8413, 8417, (byte) 33 ); // Fill 4 of value (byte) 33
+        CHARS[8417] = -87;
+        Arrays.fill(CHARS, 8418, 8486, (byte) 33 ); // Fill 68 of value (byte) 33
+        CHARS[8486] = -19;
+        Arrays.fill(CHARS, 8487, 8490, (byte) 33 ); // Fill 3 of value (byte) 33
+        Arrays.fill(CHARS, 8490, 8492, (byte) -19 ); // Fill 2 of value (byte) -19
+        Arrays.fill(CHARS, 8492, 8494, (byte) 33 ); // Fill 2 of value (byte) 33
+        CHARS[8494] = -19;
+        Arrays.fill(CHARS, 8495, 8576, (byte) 33 ); // Fill 81 of value (byte) 33
+        Arrays.fill(CHARS, 8576, 8579, (byte) -19 ); // Fill 3 of value (byte) -19
+        Arrays.fill(CHARS, 8579, 12293, (byte) 33 ); // Fill 3714 of value (byte) 33
+        CHARS[12293] = -87;
+        CHARS[12294] = 33;
+        CHARS[12295] = -19;
+        Arrays.fill(CHARS, 12296, 12321, (byte) 33 ); // Fill 25 of value (byte) 33
+        Arrays.fill(CHARS, 12321, 12330, (byte) -19 ); // Fill 9 of value (byte) -19
+        Arrays.fill(CHARS, 12330, 12336, (byte) -87 ); // Fill 6 of value (byte) -87
+        CHARS[12336] = 33;
+        Arrays.fill(CHARS, 12337, 12342, (byte) -87 ); // Fill 5 of value (byte) -87
+        Arrays.fill(CHARS, 12342, 12353, (byte) 33 ); // Fill 11 of value (byte) 33
+        Arrays.fill(CHARS, 12353, 12437, (byte) -19 ); // Fill 84 of value (byte) -19
+        Arrays.fill(CHARS, 12437, 12441, (byte) 33 ); // Fill 4 of value (byte) 33
+        Arrays.fill(CHARS, 12441, 12443, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 12443, 12445, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 12445, 12447, (byte) -87 ); // Fill 2 of value (byte) -87
+        Arrays.fill(CHARS, 12447, 12449, (byte) 33 ); // Fill 2 of value (byte) 33
+        Arrays.fill(CHARS, 12449, 12539, (byte) -19 ); // Fill 90 of value (byte) -19
+        CHARS[12539] = 33;
+        Arrays.fill(CHARS, 12540, 12543, (byte) -87 ); // Fill 3 of value (byte) -87
+        Arrays.fill(CHARS, 12543, 12549, (byte) 33 ); // Fill 6 of value (byte) 33
+        Arrays.fill(CHARS, 12549, 12589, (byte) -19 ); // Fill 40 of value (byte) -19
+        Arrays.fill(CHARS, 12589, 19968, (byte) 33 ); // Fill 7379 of value (byte) 33
+        Arrays.fill(CHARS, 19968, 40870, (byte) -19 ); // Fill 20902 of value (byte) -19
+        Arrays.fill(CHARS, 40870, 44032, (byte) 33 ); // Fill 3162 of value (byte) 33
+        Arrays.fill(CHARS, 44032, 55204, (byte) -19 ); // Fill 11172 of value (byte) -19
+        Arrays.fill(CHARS, 55204, 55296, (byte) 33 ); // Fill 92 of value (byte) 33
+        Arrays.fill(CHARS, 57344, 65534, (byte) 33 ); // Fill 8190 of value (byte) 33
+
+    } // <clinit>()
+
+    //
+    // Public static methods
+    //
+
+    /**
+     * Returns true if the specified character is a supplemental character.
+     *
+     * @param c The character to check.
+     */
+    public static boolean isSupplemental(int c) {
+        return (c >= 0x10000 && c <= 0x10FFFF);
+    }
+
+    /**
+     * Returns true the supplemental character corresponding to the given
+     * surrogates.
+     *
+     * @param h The high surrogate.
+     * @param l The low surrogate.
+     */
+    public static int supplemental(char h, char l) {
+        return (h - 0xD800) * 0x400 + (l - 0xDC00) + 0x10000;
+    }
+
+    /**
+     * Returns the high surrogate of a supplemental character
+     *
+     * @param c The supplemental character to "split".
+     */
+    public static char highSurrogate(int c) {
+        return (char) (((c - 0x00010000) >> 10) + 0xD800);
+    }
+
+    /**
+     * Returns the low surrogate of a supplemental character
+     *
+     * @param c The supplemental character to "split".
+     */
+    public static char lowSurrogate(int c) {
+        return (char) (((c - 0x00010000) & 0x3FF) + 0xDC00);
+    }
+
+    /**
+     * Returns whether the given character is a high surrogate
+     *
+     * @param c The character to check.
+     */
+    public static boolean isHighSurrogate(int c) {
+        return (0xD800 <= c && c <= 0xDBFF);
+    }
+
+    /**
+     * Returns whether the given character is a low surrogate
+     *
+     * @param c The character to check.
+     */
+    public static boolean isLowSurrogate(int c) {
+        return (0xDC00 <= c && c <= 0xDFFF);
+    }
+
+
+    /**
+     * Returns true if the specified character is valid. This method
+     * also checks the surrogate character range from 0x10000 to 0x10FFFF.
+     * <p>
+     * If the program chooses to apply the mask directly to the
+     * <code>CHARS</code> array, then they are responsible for checking
+     * the surrogate character range.
+     *
+     * @param c The character to check.
+     */
+    public static boolean isValid(int c) {
+        return (c < 0x10000 && (CHARS[c] & MASK_VALID) != 0) ||
+               (0x10000 <= c && c <= 0x10FFFF);
+    } // isValid(int):boolean
+
+    /**
+     * Returns true if the specified character is invalid.
+     *
+     * @param c The character to check.
+     */
+    public static boolean isInvalid(int c) {
+        return !isValid(c);
+    } // isInvalid(int):boolean
+
+    /**
+     * Returns true if the specified character can be considered content.
+     *
+     * @param c The character to check.
+     */
+    public static boolean isContent(int c) {
+        return (c < 0x10000 && (CHARS[c] & MASK_CONTENT) != 0) ||
+               (0x10000 <= c && c <= 0x10FFFF);
+    } // isContent(int):boolean
+
+    /**
+     * Returns true if the specified character can be considered markup.
+     * Markup characters include '&lt;', '&amp;', and '%'.
+     *
+     * @param c The character to check.
+     */
+    public static boolean isMarkup(int c) {
+        return c == '<' || c == '&' || c == '%';
+    } // isMarkup(int):boolean
+
+    /**
+     * Returns true if the specified character is a space character
+     * as defined by production [3] in the XML 1.0 specification.
+     *
+     * @param c The character to check.
+     */
+    public static boolean isSpace(int c) {
+        return c <= 0x20 && (CHARS[c] & MASK_SPACE) != 0;
+    } // isSpace(int):boolean
+
+    /**
+     * Returns true if the specified character is a valid name start
+     * character as defined by production [5] in the XML 1.0
+     * specification.
+     *
+     * @param c The character to check.
+     */
+    public static boolean isNameStart(int c) {
+        return c < 0x10000 && (CHARS[c] & MASK_NAME_START) != 0;
+    } // isNameStart(int):boolean
+
+    /**
+     * Returns true if the specified character is a valid name
+     * character as defined by production [4] in the XML 1.0
+     * specification.
+     *
+     * @param c The character to check.
+     */
+    public static boolean isName(int c) {
+        return c < 0x10000 && (CHARS[c] & MASK_NAME) != 0;
+    } // isName(int):boolean
+
+    /**
+     * Returns true if the specified character is a valid NCName start
+     * character as defined by production [4] in Namespaces in XML
+     * recommendation.
+     *
+     * @param c The character to check.
+     */
+    public static boolean isNCNameStart(int c) {
+        return c < 0x10000 && (CHARS[c] & MASK_NCNAME_START) != 0;
+    } // isNCNameStart(int):boolean
+
+    /**
+     * Returns true if the specified character is a valid NCName
+     * character as defined by production [5] in Namespaces in XML
+     * recommendation.
+     *
+     * @param c The character to check.
+     */
+    public static boolean isNCName(int c) {
+        return c < 0x10000 && (CHARS[c] & MASK_NCNAME) != 0;
+    } // isNCName(int):boolean
+
+    /**
+     * Returns true if the specified character is a valid Pubid
+     * character as defined by production [13] in the XML 1.0
+     * specification.
+     *
+     * @param c The character to check.
+     */
+    public static boolean isPubid(int c) {
+        return c < 0x10000 && (CHARS[c] & MASK_PUBID) != 0;
+    } // isPubid(int):boolean
+
+    /*
+     * [5] Name ::= (Letter | '_' | ':') (NameChar)*
+     */
+    /**
+     * Check to see if a string is a valid Name according to [5]
+     * in the XML 1.0 Recommendation
+     *
+     * @param name string to check
+     * @return true if name is a valid Name
+     */
+    public static boolean isValidName(String name) {
+        if (name.length() == 0)
+            return false;
+        char ch = name.charAt(0);
+        if( isNameStart(ch) == false)
+           return false;
+        for (int i = 1; i < name.length(); i++ ) {
+           ch = name.charAt(i);
+           if( isName( ch ) == false ){
+              return false;
+           }
+        }
+        return true;
+    } // isValidName(String):boolean
+    
+
+    /*
+     * from the namespace rec
+     * [4] NCName ::= (Letter | '_') (NCNameChar)*
+     */
+    /**
+     * Check to see if a string is a valid NCName according to [4]
+     * from the XML Namespaces 1.0 Recommendation
+     *
+     * @param ncName string to check
+     * @return true if name is a valid NCName
+     */
+    public static boolean isValidNCName(String ncName) {
+        if (ncName.length() == 0)
+            return false;
+        char ch = ncName.charAt(0);
+        if( isNCNameStart(ch) == false)
+           return false;
+        for (int i = 1; i < ncName.length(); i++ ) {
+           ch = ncName.charAt(i);
+           if( isNCName( ch ) == false ){
+              return false;
+           }
+        }
+        return true;
+    } // isValidNCName(String):boolean
+
+    /*
+     * [7] Nmtoken ::= (NameChar)+
+     */
+    /**
+     * Check to see if a string is a valid Nmtoken according to [7]
+     * in the XML 1.0 Recommendation
+     *
+     * @param nmtoken string to check
+     * @return true if nmtoken is a valid Nmtoken 
+     */
+    public static boolean isValidNmtoken(String nmtoken) {
+        if (nmtoken.length() == 0)
+            return false;
+        for (int i = 0; i < nmtoken.length(); i++ ) {
+           char ch = nmtoken.charAt(i);
+           if(  ! isName( ch ) ){
+              return false;
+           }
+        }
+        return true;
+    } // isValidName(String):boolean
+
+
+    // encodings
+
+    /**
+     * Returns true if the encoding name is a valid IANA encoding.
+     * This method does not verify that there is a decoder available
+     * for this encoding, only that the characters are valid for an
+     * IANA encoding name.
+     *
+     * @param ianaEncoding The IANA encoding name.
+     */
+    public static boolean isValidIANAEncoding(String ianaEncoding) {
+        if (ianaEncoding != null) {
+            int length = ianaEncoding.length();
+            if (length > 0) {
+                char c = ianaEncoding.charAt(0);
+                if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
+                    for (int i = 1; i < length; i++) {
+                        c = ianaEncoding.charAt(i);
+                        if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z') &&
+                            (c < '0' || c > '9') && c != '.' && c != '_' &&
+                            c != '-') {
+                            return false;
+                        }
+                    }
+                    return true;
+                }
+            }
+        }
+        return false;
+    } // isValidIANAEncoding(String):boolean
+
+    /**
+     * Returns true if the encoding name is a valid Java encoding.
+     * This method does not verify that there is a decoder available
+     * for this encoding, only that the characters are valid for an
+     * Java encoding name.
+     *
+     * @param javaEncoding The Java encoding name.
+     */
+    public static boolean isValidJavaEncoding(String javaEncoding) {
+        if (javaEncoding != null) {
+            int length = javaEncoding.length();
+            if (length > 0) {
+                for (int i = 1; i < length; i++) {
+                    char c = javaEncoding.charAt(i);
+                    if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z') &&
+                        (c < '0' || c > '9') && c != '.' && c != '_' &&
+                        c != '-') {
+                        return false;
+                    }
+                }
+                return true;
+            }
+        }
+        return false;
+    } // isValidIANAEncoding(String):boolean
+
+
+} // class XMLChar
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/XMLEncodingDetector.java b/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/XMLEncodingDetector.java
new file mode 100644
index 0000000..0529251
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/XMLEncodingDetector.java
@@ -0,0 +1,1640 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation and was
+ * originally based on software copyright (c) 1999, International
+ * Business Machines, Inc., http://www.apache.org.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.jasper.xmlparser;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.Locale;
+import java.util.jar.JarFile;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.apache.jasper.compiler.ErrorDispatcher;
+import org.apache.jasper.compiler.JspUtil;
+import org.apache.jasper.compiler.Localizer;
+
+public class XMLEncodingDetector {
+    
+    private InputStream stream;
+    private String encoding;
+    private boolean isEncodingSetInProlog;
+    private boolean isBomPresent;
+    private int skip;
+    private Boolean isBigEndian;
+    private Reader reader;
+    
+    // org.apache.xerces.impl.XMLEntityManager fields
+    public static final int DEFAULT_BUFFER_SIZE = 2048;
+    public static final int DEFAULT_XMLDECL_BUFFER_SIZE = 64;
+    private boolean fAllowJavaEncodings;
+    private SymbolTable fSymbolTable;
+    private XMLEncodingDetector fCurrentEntity;
+    private int fBufferSize = DEFAULT_BUFFER_SIZE;
+    
+    // org.apache.xerces.impl.XMLEntityManager.ScannedEntity fields
+    private int lineNumber = 1;
+    private int columnNumber = 1;
+    private boolean literal;
+    private char[] ch = new char[DEFAULT_BUFFER_SIZE];
+    private int position;
+    private int count;
+    private boolean mayReadChunks = false;
+    
+    // org.apache.xerces.impl.XMLScanner fields
+    private XMLString fString = new XMLString();    
+    private XMLStringBuffer fStringBuffer = new XMLStringBuffer();
+    private XMLStringBuffer fStringBuffer2 = new XMLStringBuffer();
+    private static final String fVersionSymbol = "version";
+    private static final String fEncodingSymbol = "encoding";
+    private static final String fStandaloneSymbol = "standalone";
+    
+    // org.apache.xerces.impl.XMLDocumentFragmentScannerImpl fields
+    private int fMarkupDepth = 0;
+    private String[] fStrings = new String[3];
+
+    private ErrorDispatcher err;
+
+    /**
+     * Constructor
+     */
+    public XMLEncodingDetector() {
+        fSymbolTable = new SymbolTable();
+        fCurrentEntity = this;
+    }
+
+    /**
+     * Autodetects the encoding of the XML document supplied by the given
+     * input stream.
+     *
+     * Encoding autodetection is done according to the XML 1.0 specification,
+     * Appendix F.1: Detection Without External Encoding Information.
+     *
+     * @return Two-element array, where the first element (of type
+     * java.lang.String) contains the name of the (auto)detected encoding, and
+     * the second element (of type java.lang.Boolean) specifies whether the 
+     * encoding was specified using the 'encoding' attribute of an XML prolog
+     * (TRUE) or autodetected (FALSE).
+     */
+    public static Object[] getEncoding(String fname, JarFile jarFile,
+                                       JspCompilationContext ctxt,
+                                       ErrorDispatcher err)
+        throws IOException, JasperException
+    {
+        InputStream inStream = JspUtil.getInputStream(fname, jarFile, ctxt,
+                                                      err);
+        XMLEncodingDetector detector = new XMLEncodingDetector();
+        Object[] ret = detector.getEncoding(inStream, err);
+        inStream.close();
+
+        return ret;
+    }
+
+    private Object[] getEncoding(InputStream in, ErrorDispatcher err)
+        throws IOException, JasperException
+    {
+        this.stream = in;
+        this.err=err;
+        createInitialReader();
+        scanXMLDecl();
+
+        return new Object[] { this.encoding,
+                              Boolean.valueOf(this.isEncodingSetInProlog),
+                              Boolean.valueOf(this.isBomPresent),
+                              Integer.valueOf(this.skip) };
+    }
+    
+    // stub method
+    void endEntity() {
+    }
+    
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.startEntity()
+    private void createInitialReader() throws IOException, JasperException {
+
+        // wrap this stream in RewindableInputStream
+        stream = new RewindableInputStream(stream);
+
+        // perform auto-detect of encoding if necessary
+        if (encoding == null) {
+            // read first four bytes and determine encoding
+            final byte[] b4 = new byte[4];
+            int count = 0;
+            for (; count<4; count++ ) {
+                b4[count] = (byte)stream.read();
+            }
+            if (count == 4) {
+                Object [] encodingDesc = getEncodingName(b4, count);
+                encoding = (String)(encodingDesc[0]);
+                isBigEndian = (Boolean)(encodingDesc[1]);
+        
+                if (encodingDesc.length > 3) {
+                    isBomPresent = ((Boolean)(encodingDesc[2])).booleanValue();
+                    skip = ((Integer)(encodingDesc[3])).intValue();
+                } else {
+                    isBomPresent = true;
+                    skip = ((Integer)(encodingDesc[2])).intValue();
+                }
+
+                stream.reset();
+                // Special case UTF-8 files with BOM created by Microsoft
+                // tools. It's more efficient to consume the BOM than make
+                // the reader perform extra checks. -Ac
+                if (count > 2 && encoding.equals("UTF-8")) {
+                    int b0 = b4[0] & 0xFF;
+                    int b1 = b4[1] & 0xFF;
+                    int b2 = b4[2] & 0xFF;
+                    if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF) {
+                        // ignore first three bytes...
+                        long skipped = stream.skip(3);
+                        if (skipped != 3) {
+                            throw new IOException(Localizer.getMessage(
+                                    "xmlParser.skipBomFail"));
+                        }
+                    }
+                }
+                reader = createReader(stream, encoding, isBigEndian);
+            } else {
+                reader = createReader(stream, encoding, isBigEndian);
+            }
+        }
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.createReader
+    /**
+     * Creates a reader capable of reading the given input stream in
+     * the specified encoding.
+     *
+     * @param inputStream  The input stream.
+     * @param encoding     The encoding name that the input stream is
+     *                     encoded using. If the user has specified that
+     *                     Java encoding names are allowed, then the
+     *                     encoding name may be a Java encoding name;
+     *                     otherwise, it is an ianaEncoding name.
+     * @param isBigEndian   For encodings (like uCS-4), whose names cannot
+     *                      specify a byte order, this tells whether the order
+     *                      is bigEndian. null means unknown or not relevant.
+     *
+     * @return Returns a reader.
+     */
+    private Reader createReader(InputStream inputStream, String encoding,
+                                Boolean isBigEndian)
+                throws IOException, JasperException {
+
+        // normalize encoding name
+        if (encoding == null) {
+            encoding = "UTF-8";
+        }
+
+        // try to use an optimized reader
+        String ENCODING = encoding.toUpperCase(Locale.ENGLISH);
+        if (ENCODING.equals("UTF-8")) {
+            return new UTF8Reader(inputStream, fBufferSize);
+        }
+        if (ENCODING.equals("US-ASCII")) {
+            return new ASCIIReader(inputStream, fBufferSize);
+        }
+        if (ENCODING.equals("ISO-10646-UCS-4")) {
+            if (isBigEndian != null) {
+                boolean isBE = isBigEndian.booleanValue();
+                if (isBE) {
+                    return new UCSReader(inputStream, UCSReader.UCS4BE);
+                } else {
+                    return new UCSReader(inputStream, UCSReader.UCS4LE);
+                }
+            } else {
+                err.jspError("jsp.error.xml.encodingByteOrderUnsupported",
+                             encoding);
+            }
+        }
+        if (ENCODING.equals("ISO-10646-UCS-2")) {
+            if (isBigEndian != null) { // sould never happen with this encoding...
+                boolean isBE = isBigEndian.booleanValue();
+                if (isBE) {
+                    return new UCSReader(inputStream, UCSReader.UCS2BE);
+                } else {
+                    return new UCSReader(inputStream, UCSReader.UCS2LE);
+                }
+            } else {
+                err.jspError("jsp.error.xml.encodingByteOrderUnsupported",
+                             encoding);
+            }
+        }
+
+        // check for valid name
+        boolean validIANA = XMLChar.isValidIANAEncoding(encoding);
+        boolean validJava = XMLChar.isValidJavaEncoding(encoding);
+        if (!validIANA || (fAllowJavaEncodings && !validJava)) {
+            err.jspError("jsp.error.xml.encodingDeclInvalid", encoding);
+            // NOTE: AndyH suggested that, on failure, we use ISO Latin 1
+            //       because every byte is a valid ISO Latin 1 character.
+            //       It may not translate correctly but if we failed on
+            //       the encoding anyway, then we're expecting the content
+            //       of the document to be bad. This will just prevent an
+            //       invalid UTF-8 sequence to be detected. This is only
+            //       important when continue-after-fatal-error is turned
+            //       on. -Ac
+            encoding = "ISO-8859-1";
+        }
+
+        // try to use a Java reader
+        String javaEncoding = EncodingMap.getIANA2JavaMapping(ENCODING);
+        if (javaEncoding == null) {
+            if (fAllowJavaEncodings) {
+                javaEncoding = encoding;
+            } else {
+                err.jspError("jsp.error.xml.encodingDeclInvalid", encoding);
+                // see comment above.
+                javaEncoding = "ISO8859_1";
+            }
+        }
+        return new InputStreamReader(inputStream, javaEncoding);
+
+    } // createReader(InputStream,String, Boolean): Reader
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.getEncodingName
+    /**
+     * Returns the IANA encoding name that is auto-detected from
+     * the bytes specified, with the endian-ness of that encoding where
+     * appropriate.
+     *
+     * @param b4    The first four bytes of the input.
+     * @param count The number of bytes actually read.
+     * @return a 2-element array:  the first element, an IANA-encoding string,
+     *  the second element a Boolean which is true iff the document is big
+     *  endian, false if it's little-endian, and null if the distinction isn't
+     *  relevant.
+     */
+    private Object[] getEncodingName(byte[] b4, int count) {
+
+        if (count < 2) {
+            return new Object[]{"UTF-8", null, Boolean.FALSE, Integer.valueOf(0)};
+        }
+
+        // UTF-16, with BOM
+        int b0 = b4[0] & 0xFF;
+        int b1 = b4[1] & 0xFF;
+        if (b0 == 0xFE && b1 == 0xFF) {
+            // UTF-16, big-endian
+            return new Object [] {"UTF-16BE", Boolean.TRUE, Integer.valueOf(2)};
+        }
+        if (b0 == 0xFF && b1 == 0xFE) {
+            // UTF-16, little-endian
+            return new Object [] {"UTF-16LE", Boolean.FALSE, Integer.valueOf(2)};
+        }
+
+        // default to UTF-8 if we don't have enough bytes to make a
+        // good determination of the encoding
+        if (count < 3) {
+            return new Object [] {"UTF-8", null, Boolean.FALSE, Integer.valueOf(0)};
+        }
+
+        // UTF-8 with a BOM
+        int b2 = b4[2] & 0xFF;
+        if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF) {
+            return new Object [] {"UTF-8", null, Integer.valueOf(3)};
+        }
+
+        // default to UTF-8 if we don't have enough bytes to make a
+        // good determination of the encoding
+        if (count < 4) {
+            return new Object [] {"UTF-8", null, Integer.valueOf(0)};
+        }
+
+        // other encodings
+        int b3 = b4[3] & 0xFF;
+        if (b0 == 0x00 && b1 == 0x00 && b2 == 0x00 && b3 == 0x3C) {
+            // UCS-4, big endian (1234)
+            return new Object [] {"ISO-10646-UCS-4", Boolean.TRUE, Integer.valueOf(4)};
+        }
+        if (b0 == 0x3C && b1 == 0x00 && b2 == 0x00 && b3 == 0x00) {
+            // UCS-4, little endian (4321)
+            return new Object [] {"ISO-10646-UCS-4", Boolean.FALSE, Integer.valueOf(4)};
+        }
+        if (b0 == 0x00 && b1 == 0x00 && b2 == 0x3C && b3 == 0x00) {
+            // UCS-4, unusual octet order (2143)
+            // REVISIT: What should this be?
+            return new Object [] {"ISO-10646-UCS-4", null, Integer.valueOf(4)};
+        }
+        if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x00) {
+            // UCS-4, unusual octect order (3412)
+            // REVISIT: What should this be?
+            return new Object [] {"ISO-10646-UCS-4", null, Integer.valueOf(4)};
+        }
+        if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x3F) {
+            // UTF-16, big-endian, no BOM
+            // (or could turn out to be UCS-2...
+            // REVISIT: What should this be?
+            return new Object [] {"UTF-16BE", Boolean.TRUE, Integer.valueOf(4)};
+        }
+        if (b0 == 0x3C && b1 == 0x00 && b2 == 0x3F && b3 == 0x00) {
+            // UTF-16, little-endian, no BOM
+            // (or could turn out to be UCS-2...
+            return new Object [] {"UTF-16LE", Boolean.FALSE, Integer.valueOf(4)};
+        }
+        if (b0 == 0x4C && b1 == 0x6F && b2 == 0xA7 && b3 == 0x94) {
+            // EBCDIC
+            // a la xerces1, return CP037 instead of EBCDIC here
+            return new Object [] {"CP037", null, Integer.valueOf(4)};
+        }
+
+        // default encoding
+        return new Object [] {"UTF-8", null, Boolean.FALSE, Integer.valueOf(0)};
+
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.EntityScanner.isExternal
+    /** Returns true if the current entity being scanned is external. */
+    public boolean isExternal() {
+        return true;
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.EntityScanner.peekChar
+    /**
+     * Returns the next character on the input.
+     * <p>
+     * <strong>Note:</strong> The character is <em>not</em> consumed.
+     *
+     * @throws IOException  Thrown if i/o error occurs.
+     * @throws EOFException Thrown on end of file.
+     */
+    public int peekChar() throws IOException {
+        
+        // load more characters, if needed
+        if (fCurrentEntity.position == fCurrentEntity.count) {
+            load(0, true);
+        }
+        
+        // peek at character
+        int c = fCurrentEntity.ch[fCurrentEntity.position];
+
+        // return peeked character
+        if (fCurrentEntity.isExternal()) {
+            return c != '\r' ? c : '\n';
+        }
+        else {
+            return c;
+        }
+        
+    } // peekChar():int
+    
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.EntityScanner.scanChar
+    /**
+     * Returns the next character on the input.
+     * <p>
+     * <strong>Note:</strong> The character is consumed.
+     *
+     * @throws IOException  Thrown if i/o error occurs.
+     * @throws EOFException Thrown on end of file.
+     */
+    public int scanChar() throws IOException {
+
+        // load more characters, if needed
+        if (fCurrentEntity.position == fCurrentEntity.count) {
+            load(0, true);
+        }
+
+        // scan character
+        int c = fCurrentEntity.ch[fCurrentEntity.position++];
+        boolean external = false;
+        if (c == '\n' ||
+            (c == '\r' && (external = fCurrentEntity.isExternal()))) {
+            fCurrentEntity.lineNumber++;
+            fCurrentEntity.columnNumber = 1;
+            if (fCurrentEntity.position == fCurrentEntity.count) {
+                fCurrentEntity.ch[0] = (char)c;
+                load(1, false);
+            }
+            if (c == '\r' && external) {
+                if (fCurrentEntity.ch[fCurrentEntity.position++] != '\n') {
+                    fCurrentEntity.position--;
+                }
+                c = '\n';
+            }
+        }
+
+        // return character that was scanned
+        fCurrentEntity.columnNumber++;
+        return c;
+        
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.EntityScanner.scanName
+    /**
+     * Returns a string matching the Name production appearing immediately
+     * on the input as a symbol, or null if no Name string is present.
+     * <p>
+     * <strong>Note:</strong> The Name characters are consumed.
+     * <p>
+     * <strong>Note:</strong> The string returned must be a symbol. The
+     * SymbolTable can be used for this purpose.
+     *
+     * @throws IOException  Thrown if i/o error occurs.
+     * @throws EOFException Thrown on end of file.
+     *
+     * @see SymbolTable
+     * @see XMLChar#isName
+     * @see XMLChar#isNameStart
+     */
+    public String scanName() throws IOException {
+        
+        // load more characters, if needed
+        if (fCurrentEntity.position == fCurrentEntity.count) {
+            load(0, true);
+        }
+        
+        // scan name
+        int offset = fCurrentEntity.position;
+        if (XMLChar.isNameStart(fCurrentEntity.ch[offset])) {
+            if (++fCurrentEntity.position == fCurrentEntity.count) {
+                fCurrentEntity.ch[0] = fCurrentEntity.ch[offset];
+                offset = 0;
+                if (load(1, false)) {
+                    fCurrentEntity.columnNumber++;
+                    String symbol = fSymbolTable.addSymbol(fCurrentEntity.ch,
+                                                           0, 1);
+                    return symbol;
+                }
+            }
+            while (XMLChar.isName(fCurrentEntity.ch[fCurrentEntity.position])) {
+                if (++fCurrentEntity.position == fCurrentEntity.count) {
+                    int length = fCurrentEntity.position - offset;
+                    if (length == fBufferSize) {
+                        // bad luck we have to resize our buffer
+                        char[] tmp = new char[fBufferSize * 2];
+                        System.arraycopy(fCurrentEntity.ch, offset,
+                                         tmp, 0, length);
+                        fCurrentEntity.ch = tmp;
+                        fBufferSize *= 2;
+                    } else {
+                        System.arraycopy(fCurrentEntity.ch, offset,
+                                         fCurrentEntity.ch, 0, length);
+                    }
+                    offset = 0;
+                    if (load(length, false)) {
+                        break;
+                    }
+                }
+            }
+        }
+        int length = fCurrentEntity.position - offset;
+        fCurrentEntity.columnNumber += length;
+
+        // return name
+        String symbol = null;
+        if (length > 0) {
+            symbol = fSymbolTable.addSymbol(fCurrentEntity.ch, offset, length);
+        }
+        return symbol;
+        
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.EntityScanner.scanLiteral
+    /**
+     * Scans a range of attribute value data, setting the fields of the
+     * XMLString structure, appropriately.
+     * <p>
+     * <strong>Note:</strong> The characters are consumed.
+     * <p>
+     * <strong>Note:</strong> This method does not guarantee to return
+     * the longest run of attribute value data. This method may return
+     * before the quote character due to reaching the end of the input
+     * buffer or any other reason.
+     * <p>
+     * <strong>Note:</strong> The fields contained in the XMLString
+     * structure are not guaranteed to remain valid upon subsequent calls
+     * to the entity scanner. Therefore, the caller is responsible for
+     * immediately using the returned character data or making a copy of
+     * the character data.
+     *
+     * @param quote   The quote character that signifies the end of the
+     *                attribute value data.
+     * @param content The content structure to fill.
+     *
+     * @return Returns the next character on the input, if known. This
+     *         value may be -1 but this does <em>note</em> designate
+     *         end of file.
+     *
+     * @throws IOException  Thrown if i/o error occurs.
+     * @throws EOFException Thrown on end of file.
+     */
+    public int scanLiteral(int quote, XMLString content)
+        throws IOException {
+
+        // load more characters, if needed
+        if (fCurrentEntity.position == fCurrentEntity.count) {
+            load(0, true);
+        } else if (fCurrentEntity.position == fCurrentEntity.count - 1) {
+            fCurrentEntity.ch[0] = fCurrentEntity.ch[fCurrentEntity.count - 1];
+            load(1, false);
+            fCurrentEntity.position = 0;
+        }
+
+        // normalize newlines
+        int offset = fCurrentEntity.position;
+        int c = fCurrentEntity.ch[offset];
+        int newlines = 0;
+        boolean external = fCurrentEntity.isExternal();
+        if (c == '\n' || (c == '\r' && external)) {
+            do {
+                c = fCurrentEntity.ch[fCurrentEntity.position++];
+                if (c == '\r' && external) {
+                    newlines++;
+                    fCurrentEntity.lineNumber++;
+                    fCurrentEntity.columnNumber = 1;
+                    if (fCurrentEntity.position == fCurrentEntity.count) {
+                        offset = 0;
+                        fCurrentEntity.position = newlines;
+                        if (load(newlines, false)) {
+                            break;
+                        }
+                    }
+                    if (fCurrentEntity.ch[fCurrentEntity.position] == '\n') {
+                        fCurrentEntity.position++;
+                        offset++;
+                    }
+                    /*** NEWLINE NORMALIZATION ***/
+                    else {
+                        newlines++;
+                    }
+                    /***/
+                }
+                else if (c == '\n') {
+                    newlines++;
+                    fCurrentEntity.lineNumber++;
+                    fCurrentEntity.columnNumber = 1;
+                    if (fCurrentEntity.position == fCurrentEntity.count) {
+                        offset = 0;
+                        fCurrentEntity.position = newlines;
+                        if (load(newlines, false)) {
+                            break;
+                        }
+                    }
+                    /*** NEWLINE NORMALIZATION ***
+                         if (fCurrentEntity.ch[fCurrentEntity.position] == '\r'
+                         && external) {
+                         fCurrentEntity.position++;
+                         offset++;
+                         }
+                         /***/
+                }
+                else {
+                    fCurrentEntity.position--;
+                    break;
+                }
+            } while (fCurrentEntity.position < fCurrentEntity.count - 1);
+            for (int i = offset; i < fCurrentEntity.position; i++) {
+                fCurrentEntity.ch[i] = '\n';
+            }
+            int length = fCurrentEntity.position - offset;
+            if (fCurrentEntity.position == fCurrentEntity.count - 1) {
+                content.setValues(fCurrentEntity.ch, offset, length);
+                return -1;
+            }
+        }
+
+        // scan literal value
+        while (fCurrentEntity.position < fCurrentEntity.count) {
+            c = fCurrentEntity.ch[fCurrentEntity.position++];
+            if ((c == quote &&
+                 (!fCurrentEntity.literal || external))
+                || c == '%' || !XMLChar.isContent(c)) {
+                fCurrentEntity.position--;
+                break;
+            }
+        }
+        int length = fCurrentEntity.position - offset;
+        fCurrentEntity.columnNumber += length - newlines;
+        content.setValues(fCurrentEntity.ch, offset, length);
+
+        // return next character
+        if (fCurrentEntity.position != fCurrentEntity.count) {
+            c = fCurrentEntity.ch[fCurrentEntity.position];
+            // NOTE: We don't want to accidentally signal the
+            //       end of the literal if we're expanding an
+            //       entity appearing in the literal. -Ac
+            if (c == quote && fCurrentEntity.literal) {
+                c = -1;
+            }
+        }
+        else {
+            c = -1;
+        }
+        return c;
+
+    }
+
+    /**
+     * Scans a range of character data up to the specified delimiter,
+     * setting the fields of the XMLString structure, appropriately.
+     * <p>
+     * <strong>Note:</strong> The characters are consumed.
+     * <p>
+     * <strong>Note:</strong> This assumes that the internal buffer is
+     * at least the same size, or bigger, than the length of the delimiter
+     * and that the delimiter contains at least one character.
+     * <p>
+     * <strong>Note:</strong> This method does not guarantee to return
+     * the longest run of character data. This method may return before
+     * the delimiter due to reaching the end of the input buffer or any
+     * other reason.
+     * <p>
+     * <strong>Note:</strong> The fields contained in the XMLString
+     * structure are not guaranteed to remain valid upon subsequent calls
+     * to the entity scanner. Therefore, the caller is responsible for
+     * immediately using the returned character data or making a copy of
+     * the character data.
+     *
+     * @param delimiter The string that signifies the end of the character
+     *                  data to be scanned.
+     * @param buffer    The data structure to fill.
+     *
+     * @return Returns true if there is more data to scan, false otherwise.
+     *
+     * @throws IOException  Thrown if i/o error occurs.
+     * @throws EOFException Thrown on end of file.
+     */
+    public boolean scanData(String delimiter, XMLStringBuffer buffer)
+        throws IOException {
+
+        boolean done = false;
+        int delimLen = delimiter.length();
+        char charAt0 = delimiter.charAt(0);
+        boolean external = fCurrentEntity.isExternal();
+        do {
+    
+            // load more characters, if needed
+    
+            if (fCurrentEntity.position == fCurrentEntity.count) {
+                load(0, true);
+            }
+            else if (fCurrentEntity.position >= fCurrentEntity.count - delimLen) {
+                System.arraycopy(fCurrentEntity.ch, fCurrentEntity.position,
+                                 fCurrentEntity.ch, 0, fCurrentEntity.count - fCurrentEntity.position);
+                load(fCurrentEntity.count - fCurrentEntity.position, false);
+                fCurrentEntity.position = 0;
+            } 
+            if (fCurrentEntity.position >= fCurrentEntity.count - delimLen) {
+                // something must be wrong with the input: e.g., file ends an
+                // unterminated comment
+                int length = fCurrentEntity.count - fCurrentEntity.position;
+                buffer.append (fCurrentEntity.ch, fCurrentEntity.position,
+                               length); 
+                fCurrentEntity.columnNumber += fCurrentEntity.count;
+                fCurrentEntity.position = fCurrentEntity.count;
+                load(0,true);
+                return false;
+            }
+    
+            // normalize newlines
+            int offset = fCurrentEntity.position;
+            int c = fCurrentEntity.ch[offset];
+            int newlines = 0;
+            if (c == '\n' || (c == '\r' && external)) {
+                do {
+                    c = fCurrentEntity.ch[fCurrentEntity.position++];
+                    if (c == '\r' && external) {
+                        newlines++;
+                        fCurrentEntity.lineNumber++;
+                        fCurrentEntity.columnNumber = 1;
+                        if (fCurrentEntity.position == fCurrentEntity.count) {
+                            offset = 0;
+                            fCurrentEntity.position = newlines;
+                            if (load(newlines, false)) {
+                                break;
+                            }
+                        }
+                        if (fCurrentEntity.ch[fCurrentEntity.position] == '\n') {
+                            fCurrentEntity.position++;
+                            offset++;
+                        }
+                        /*** NEWLINE NORMALIZATION ***/
+                        else {
+                            newlines++;
+                        }
+                    }
+                    else if (c == '\n') {
+                        newlines++;
+                        fCurrentEntity.lineNumber++;
+                        fCurrentEntity.columnNumber = 1;
+                        if (fCurrentEntity.position == fCurrentEntity.count) {
+                            offset = 0;
+                            fCurrentEntity.position = newlines;
+                            fCurrentEntity.count = newlines;
+                            if (load(newlines, false)) {
+                                break;
+                            }
+                        }
+                    }
+                    else {
+                        fCurrentEntity.position--;
+                        break;
+                    }
+                } while (fCurrentEntity.position < fCurrentEntity.count - 1);
+                for (int i = offset; i < fCurrentEntity.position; i++) {
+                    fCurrentEntity.ch[i] = '\n';
+                }
+                int length = fCurrentEntity.position - offset;
+                if (fCurrentEntity.position == fCurrentEntity.count - 1) {
+                    buffer.append(fCurrentEntity.ch, offset, length);
+                    return true;
+                }
+            }
+    
+            // iterate over buffer looking for delimiter
+        OUTER: while (fCurrentEntity.position < fCurrentEntity.count) {
+            c = fCurrentEntity.ch[fCurrentEntity.position++];
+            if (c == charAt0) {
+                // looks like we just hit the delimiter
+                int delimOffset = fCurrentEntity.position - 1;
+                for (int i = 1; i < delimLen; i++) {
+                    if (fCurrentEntity.position == fCurrentEntity.count) {
+                        fCurrentEntity.position -= i;
+                        break OUTER;
+                    }
+                    c = fCurrentEntity.ch[fCurrentEntity.position++];
+                    if (delimiter.charAt(i) != c) {
+                        fCurrentEntity.position--;
+                        break;
+                    }
+                }
+                if (fCurrentEntity.position == delimOffset + delimLen) {
+                    done = true;
+                    break;
+                }
+            }
+            else if (c == '\n' || (external && c == '\r')) {
+                fCurrentEntity.position--;
+                break;
+            }
+            else if (XMLChar.isInvalid(c)) {
+                fCurrentEntity.position--;
+                int length = fCurrentEntity.position - offset;
+                fCurrentEntity.columnNumber += length - newlines;
+                buffer.append(fCurrentEntity.ch, offset, length); 
+                return true;
+            }
+        }
+            int length = fCurrentEntity.position - offset;
+            fCurrentEntity.columnNumber += length - newlines;
+            if (done) {
+                length -= delimLen;
+            }
+            buffer.append (fCurrentEntity.ch, offset, length);
+    
+            // return true if string was skipped
+        } while (!done);
+        return !done;
+
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.EntityScanner.skipChar
+    /**
+     * Skips a character appearing immediately on the input.
+     * <p>
+     * <strong>Note:</strong> The character is consumed only if it matches
+     * the specified character.
+     *
+     * @param c The character to skip.
+     *
+     * @return Returns true if the character was skipped.
+     *
+     * @throws IOException  Thrown if i/o error occurs.
+     * @throws EOFException Thrown on end of file.
+     */
+    public boolean skipChar(int c) throws IOException {
+
+        // load more characters, if needed
+        if (fCurrentEntity.position == fCurrentEntity.count) {
+            load(0, true);
+        }
+
+        // skip character
+        int cc = fCurrentEntity.ch[fCurrentEntity.position];
+        if (cc == c) {
+            fCurrentEntity.position++;
+            if (c == '\n') {
+                fCurrentEntity.lineNumber++;
+                fCurrentEntity.columnNumber = 1;
+            }
+            else {
+                fCurrentEntity.columnNumber++;
+            }
+            return true;
+        } else if (c == '\n' && cc == '\r' && fCurrentEntity.isExternal()) {
+            // handle newlines
+            if (fCurrentEntity.position == fCurrentEntity.count) {
+                fCurrentEntity.ch[0] = (char)cc;
+                load(1, false);
+            }
+            fCurrentEntity.position++;
+            if (fCurrentEntity.ch[fCurrentEntity.position] == '\n') {
+                fCurrentEntity.position++;
+            }
+            fCurrentEntity.lineNumber++;
+            fCurrentEntity.columnNumber = 1;
+            return true;
+        }
+
+        // character was not skipped
+        return false;
+
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.EntityScanner.skipSpaces
+    /**
+     * Skips space characters appearing immediately on the input.
+     * <p>
+     * <strong>Note:</strong> The characters are consumed only if they are
+     * space characters.
+     *
+     * @return Returns true if at least one space character was skipped.
+     *
+     * @throws IOException  Thrown if i/o error occurs.
+     * @throws EOFException Thrown on end of file.
+     *
+     * @see XMLChar#isSpace
+     */
+    public boolean skipSpaces() throws IOException {
+
+        // load more characters, if needed
+        if (fCurrentEntity.position == fCurrentEntity.count) {
+            load(0, true);
+        }
+
+        // skip spaces
+        int c = fCurrentEntity.ch[fCurrentEntity.position];
+        if (XMLChar.isSpace(c)) {
+            boolean external = fCurrentEntity.isExternal();
+            do {
+                boolean entityChanged = false;
+                // handle newlines
+                if (c == '\n' || (external && c == '\r')) {
+                    fCurrentEntity.lineNumber++;
+                    fCurrentEntity.columnNumber = 1;
+                    if (fCurrentEntity.position == fCurrentEntity.count - 1) {
+                        fCurrentEntity.ch[0] = (char)c;
+                        entityChanged = load(1, true);
+                        if (!entityChanged)
+                                // the load change the position to be 1,
+                                // need to restore it when entity not changed
+                            fCurrentEntity.position = 0;
+                    }
+                    if (c == '\r' && external) {
+                        // REVISIT: Does this need to be updated to fix the
+                        //          #x0D ^#x0A newline normalization problem? -Ac
+                        if (fCurrentEntity.ch[++fCurrentEntity.position] != '\n') {
+                            fCurrentEntity.position--;
+                        }
+                    }
+                    /*** NEWLINE NORMALIZATION ***
+                         else {
+                         if (fCurrentEntity.ch[fCurrentEntity.position + 1] == '\r'
+                         && external) {
+                         fCurrentEntity.position++;
+                         }
+                         }
+                         /***/
+                }
+                else {
+                    fCurrentEntity.columnNumber++;
+                }
+                // load more characters, if needed
+                if (!entityChanged)
+                    fCurrentEntity.position++;
+                if (fCurrentEntity.position == fCurrentEntity.count) {
+                    load(0, true);
+                }
+            } while (XMLChar.isSpace(c = fCurrentEntity.ch[fCurrentEntity.position]));
+            return true;
+        }
+
+        // no spaces were found
+        return false;
+
+    }
+
+    /**
+     * Skips the specified string appearing immediately on the input.
+     * <p>
+     * <strong>Note:</strong> The characters are consumed only if they are
+     * space characters.
+     *
+     * @param s The string to skip.
+     *
+     * @return Returns true if the string was skipped.
+     *
+     * @throws IOException  Thrown if i/o error occurs.
+     * @throws EOFException Thrown on end of file.
+     */
+    public boolean skipString(String s) throws IOException {
+
+        // load more characters, if needed
+        if (fCurrentEntity.position == fCurrentEntity.count) {
+            load(0, true);
+        }
+
+        // skip string
+        final int length = s.length();
+        for (int i = 0; i < length; i++) {
+            char c = fCurrentEntity.ch[fCurrentEntity.position++];
+            if (c != s.charAt(i)) {
+                fCurrentEntity.position -= i + 1;
+                return false;
+            }
+            if (i < length - 1 && fCurrentEntity.position == fCurrentEntity.count) {
+                System.arraycopy(fCurrentEntity.ch, fCurrentEntity.count - i - 1, fCurrentEntity.ch, 0, i + 1);
+                // REVISIT: Can a string to be skipped cross an
+                //          entity boundary? -Ac
+                if (load(i + 1, false)) {
+                    fCurrentEntity.position -= i + 1;
+                    return false;
+                }
+            }
+        }
+        fCurrentEntity.columnNumber += length;
+        return true;
+
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.EntityScanner.load
+    /**
+     * Loads a chunk of text.
+     *
+     * @param offset       The offset into the character buffer to
+     *                     read the next batch of characters.
+     * @param changeEntity True if the load should change entities
+     *                     at the end of the entity, otherwise leave
+     *                     the current entity in place and the entity
+     *                     boundary will be signaled by the return
+     *                     value.
+     *
+     * @return Returns true if the entity changed as a result of this
+     *         load operation.
+     */
+    final boolean load(int offset, boolean changeEntity)
+        throws IOException {
+
+        // read characters
+        int length = fCurrentEntity.mayReadChunks?
+            (fCurrentEntity.ch.length - offset):
+            (DEFAULT_XMLDECL_BUFFER_SIZE);
+        int count = fCurrentEntity.reader.read(fCurrentEntity.ch, offset,
+                                               length);
+
+        // reset count and position
+        boolean entityChanged = false;
+        if (count != -1) {
+            if (count != 0) {
+                fCurrentEntity.count = count + offset;
+                fCurrentEntity.position = offset;
+            }
+        }
+
+        // end of this entity
+        else {
+            fCurrentEntity.count = offset;
+            fCurrentEntity.position = offset;
+            entityChanged = true;
+            if (changeEntity) {
+                endEntity();
+                if (fCurrentEntity == null) {
+                    throw new EOFException();
+                }
+                // handle the trailing edges
+                if (fCurrentEntity.position == fCurrentEntity.count) {
+                    load(0, false);
+                }
+            }
+        }
+
+        return entityChanged;
+
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.RewindableInputStream
+    /**
+     * This class wraps the byte inputstreams we're presented with.
+     * We need it because java.io.InputStreams don't provide
+     * functionality to reread processed bytes, and they have a habit
+     * of reading more than one character when you call their read()
+     * methods.  This means that, once we discover the true (declared)
+     * encoding of a document, we can neither backtrack to read the
+     * whole doc again nor start reading where we are with a new
+     * reader.
+     *
+     * This class allows rewinding an inputStream by allowing a mark
+     * to be set, and the stream reset to that position.  <strong>The
+     * class assumes that it needs to read one character per
+     * invocation when it's read() method is invoked, but uses the
+     * underlying InputStream's read(char[], offset length) method--it
+     * won't buffer data read this way!</strong>
+     *
+     * @author Neil Graham, IBM
+     * @author Glenn Marcy, IBM
+     */
+    private final class RewindableInputStream extends InputStream {
+
+        private InputStream fInputStream;
+        private byte[] fData;
+        private int fEndOffset;
+        private int fOffset;
+        private int fLength;
+        private int fMark;
+
+        public RewindableInputStream(InputStream is) {
+            fData = new byte[DEFAULT_XMLDECL_BUFFER_SIZE];
+            fInputStream = is;
+            fEndOffset = -1;
+            fOffset = 0;
+            fLength = 0;
+            fMark = 0;
+        }
+
+        @Override
+        public int read() throws IOException {
+            int b = 0;
+            if (fOffset < fLength) {
+                return fData[fOffset++] & 0xff;
+            }
+            if (fOffset == fEndOffset) {
+                return -1;
+            }
+            if (fOffset == fData.length) {
+                byte[] newData = new byte[fOffset << 1];
+                System.arraycopy(fData, 0, newData, 0, fOffset);
+                fData = newData;
+            }
+            b = fInputStream.read();
+            if (b == -1) {
+                fEndOffset = fOffset;
+                return -1;
+            }
+            fData[fLength++] = (byte)b;
+            fOffset++;
+            return b & 0xff;
+        }
+
+        @Override
+        public int read(byte[] b, int off, int len) throws IOException {
+            int bytesLeft = fLength - fOffset;
+            if (bytesLeft == 0) {
+                if (fOffset == fEndOffset) {
+                    return -1;
+                }
+                // better get some more for the voracious reader...
+                if (fCurrentEntity.mayReadChunks) {
+                    return fInputStream.read(b, off, len);
+                }
+                int returnedVal = read();
+                if (returnedVal == -1) {
+                    fEndOffset = fOffset;
+                    return -1;
+                }
+                b[off] = (byte)returnedVal;
+                return 1;
+            }
+            if (len < bytesLeft) {
+                if (len <= 0) {
+                    return 0;
+                }
+            }
+            else {
+                len = bytesLeft;
+            }
+            if (b != null) {
+                System.arraycopy(fData, fOffset, b, off, len);
+            }
+            fOffset += len;
+            return len;
+        }
+
+        @Override
+        public long skip(long n)
+            throws IOException
+        {
+            int bytesLeft;
+            if (n <= 0) {
+                return 0;
+            }
+            bytesLeft = fLength - fOffset;
+            if (bytesLeft == 0) {
+                if (fOffset == fEndOffset) {
+                    return 0;
+                }
+                return fInputStream.skip(n);
+            }
+            if (n <= bytesLeft) {
+                fOffset += n;
+                return n;
+            }
+            fOffset += bytesLeft;
+            if (fOffset == fEndOffset) {
+                return bytesLeft;
+            }
+            n -= bytesLeft;
+            /*
+             * In a manner of speaking, when this class isn't permitting more
+             * than one byte at a time to be read, it is "blocking".  The
+             * available() method should indicate how much can be read without
+             * blocking, so while we're in this mode, it should only indicate
+             * that bytes in its buffer are available; otherwise, the result of
+             * available() on the underlying InputStream is appropriate.
+             */
+            return fInputStream.skip(n) + bytesLeft;
+        }
+
+        @Override
+        public int available() throws IOException {
+            int bytesLeft = fLength - fOffset;
+            if (bytesLeft == 0) {
+                if (fOffset == fEndOffset) {
+                    return -1;
+                }
+                return fCurrentEntity.mayReadChunks ? fInputStream.available()
+                    : 0;
+            }
+            return bytesLeft;
+        }
+
+        @Override
+        public synchronized void mark(int howMuch) {
+            fMark = fOffset;
+        }
+
+        @Override
+        public synchronized void reset() {
+            fOffset = fMark;
+        }
+
+        @Override
+        public boolean markSupported() {
+            return true;
+        }
+
+        @Override
+        public void close() throws IOException {
+            if (fInputStream != null) {
+                fInputStream.close();
+                fInputStream = null;
+            }
+        }
+    } // end of RewindableInputStream class
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLDocumentScannerImpl.dispatch
+    private void scanXMLDecl() throws IOException, JasperException {
+
+        if (skipString("<?xml")) {
+            fMarkupDepth++;
+            // NOTE: special case where document starts with a PI
+            //       whose name starts with "xml" (e.g. "xmlfoo")
+            if (XMLChar.isName(peekChar())) {
+                fStringBuffer.clear();
+                fStringBuffer.append("xml");
+                while (XMLChar.isName(peekChar())) {
+                    fStringBuffer.append((char)scanChar());
+                }
+                String target = fSymbolTable.addSymbol(fStringBuffer.ch,
+                                                       fStringBuffer.offset,
+                                                       fStringBuffer.length);
+                scanPIData(target, fString);
+            }
+
+            // standard XML declaration
+            else {
+                scanXMLDeclOrTextDecl(false);
+            }
+        }
+    }
+    
+    // Adapted from:
+    // org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanXMLDeclOrTextDecl
+    /**
+     * Scans an XML or text declaration.
+     * <p>
+     * <pre>
+     * [23] XMLDecl ::= '&lt;?xml' VersionInfo EncodingDecl? SDDecl? S? '?>'
+     * [24] VersionInfo ::= S 'version' Eq (' VersionNum ' | " VersionNum ")
+     * [80] EncodingDecl ::= S 'encoding' Eq ('"' EncName '"' |  "'" EncName "'" )
+     * [81] EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')*
+     * [32] SDDecl ::= S 'standalone' Eq (("'" ('yes' | 'no') "'")
+     *                 | ('"' ('yes' | 'no') '"'))
+     *
+     * [77] TextDecl ::= '&lt;?xml' VersionInfo? EncodingDecl S? '?>'
+     * </pre>
+     *
+     * @param scanningTextDecl True if a text declaration is to
+     *                         be scanned instead of an XML
+     *                         declaration.
+     */
+    private void scanXMLDeclOrTextDecl(boolean scanningTextDecl) 
+        throws IOException, JasperException {
+
+        // scan decl
+        scanXMLDeclOrTextDecl(scanningTextDecl, fStrings);
+        fMarkupDepth--;
+
+        // pseudo-attribute values
+        String encodingPseudoAttr = fStrings[1];
+
+        // set encoding on reader
+        if (encodingPseudoAttr != null) {
+            isEncodingSetInProlog = true;
+            encoding = encodingPseudoAttr;
+        }
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLScanner.scanXMLDeclOrTextDecl
+    /**
+     * Scans an XML or text declaration.
+     * <p>
+     * <pre>
+     * [23] XMLDecl ::= '<?xml' VersionInfo EncodingDecl? SDDecl? S? '?>'
+     * [24] VersionInfo ::= S 'version' Eq (' VersionNum ' | " VersionNum ")
+     * [80] EncodingDecl ::= S 'encoding' Eq ('"' EncName '"' |  "'" EncName "'" )
+     * [81] EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')*
+     * [32] SDDecl ::= S 'standalone' Eq (("'" ('yes' | 'no') "'")
+     *                 | ('"' ('yes' | 'no') '"'))
+     *
+     * [77] TextDecl ::= '<?xml' VersionInfo? EncodingDecl S? '?>'
+     * </pre>
+     *
+     * @param scanningTextDecl True if a text declaration is to
+     *                         be scanned instead of an XML
+     *                         declaration.
+     * @param pseudoAttributeValues An array of size 3 to return the version,
+     *                         encoding and standalone pseudo attribute values
+     *                         (in that order).
+     *
+     * <strong>Note:</strong> This method uses fString, anything in it
+     * at the time of calling is lost.
+     */
+    private void scanXMLDeclOrTextDecl(boolean scanningTextDecl,
+                                       String[] pseudoAttributeValues) 
+                throws IOException, JasperException {
+
+        // pseudo-attribute values
+        String version = null;
+        String encoding = null;
+        String standalone = null;
+
+        // scan pseudo-attributes
+        final int STATE_VERSION = 0;
+        final int STATE_ENCODING = 1;
+        final int STATE_STANDALONE = 2;
+        final int STATE_DONE = 3;
+        int state = STATE_VERSION;
+
+        boolean dataFoundForTarget = false;
+        boolean sawSpace = skipSpaces();
+        while (peekChar() != '?') {
+            dataFoundForTarget = true;
+            String name = scanPseudoAttribute(scanningTextDecl, fString);
+            switch (state) {
+                case STATE_VERSION: {
+                    if (name == fVersionSymbol) {
+                        if (!sawSpace) {
+                            reportFatalError(scanningTextDecl
+                                       ? "jsp.error.xml.spaceRequiredBeforeVersionInTextDecl"
+                                       : "jsp.error.xml.spaceRequiredBeforeVersionInXMLDecl",
+                                             null);
+                        }
+                        version = fString.toString();
+                        state = STATE_ENCODING;
+                        if (!version.equals("1.0")) {
+                            // REVISIT: XML REC says we should throw an error
+                            // in such cases.
+                            // some may object the throwing of fatalError.
+                            err.jspError("jsp.error.xml.versionNotSupported",
+                                         version);
+                        }
+                    } else if (name == fEncodingSymbol) {
+                        if (!scanningTextDecl) {
+                            err.jspError("jsp.error.xml.versionInfoRequired");
+                        }
+                        if (!sawSpace) {
+                            reportFatalError(scanningTextDecl
+                                      ? "jsp.error.xml.spaceRequiredBeforeEncodingInTextDecl"
+                                      : "jsp.error.xml.spaceRequiredBeforeEncodingInXMLDecl",
+                                             null);
+                        }
+                        encoding = fString.toString();
+                        state = scanningTextDecl ? STATE_DONE : STATE_STANDALONE;
+                    } else {
+                        if (scanningTextDecl) {
+                            err.jspError("jsp.error.xml.encodingDeclRequired");
+                        }
+                        else {
+                            err.jspError("jsp.error.xml.versionInfoRequired");
+                        }
+                    }
+                    break;
+                }
+                case STATE_ENCODING: {
+                    if (name == fEncodingSymbol) {
+                        if (!sawSpace) {
+                            reportFatalError(scanningTextDecl
+                                      ? "jsp.error.xml.spaceRequiredBeforeEncodingInTextDecl"
+                                      : "jsp.error.xml.spaceRequiredBeforeEncodingInXMLDecl",
+                                             null);
+                        }
+                        encoding = fString.toString();
+                        state = scanningTextDecl ? STATE_DONE : STATE_STANDALONE;
+                        // TODO: check encoding name; set encoding on
+                        //       entity scanner
+                    } else if (!scanningTextDecl && name == fStandaloneSymbol) {
+                        if (!sawSpace) {
+                            err.jspError("jsp.error.xml.spaceRequiredBeforeStandalone");
+                        }
+                        standalone = fString.toString();
+                        state = STATE_DONE;
+                        if (!standalone.equals("yes") && !standalone.equals("no")) {
+                            err.jspError("jsp.error.xml.sdDeclInvalid");
+                        }
+                    } else {
+                        err.jspError("jsp.error.xml.encodingDeclRequired");
+                    }
+                    break;
+                }
+                case STATE_STANDALONE: {
+                    if (name == fStandaloneSymbol) {
+                        if (!sawSpace) {
+                            err.jspError("jsp.error.xml.spaceRequiredBeforeStandalone");
+                        }
+                        standalone = fString.toString();
+                        state = STATE_DONE;
+                        if (!standalone.equals("yes") && !standalone.equals("no")) {
+                            err.jspError("jsp.error.xml.sdDeclInvalid");
+                        }
+                    } else {
+                        err.jspError("jsp.error.xml.encodingDeclRequired");
+                    }
+                    break;
+                }
+                default: {
+                    err.jspError("jsp.error.xml.noMorePseudoAttributes");
+                }
+            }
+            sawSpace = skipSpaces();
+        }
+        // REVISIT: should we remove this error reporting?
+        if (scanningTextDecl && state != STATE_DONE) {
+            err.jspError("jsp.error.xml.morePseudoAttributes");
+        }
+        
+        // If there is no data in the xml or text decl then we fail to report
+        // error for version or encoding info above.
+        if (scanningTextDecl) {
+            if (!dataFoundForTarget && encoding == null) {
+                err.jspError("jsp.error.xml.encodingDeclRequired");
+            }
+        } else {
+            if (!dataFoundForTarget && version == null) {
+                err.jspError("jsp.error.xml.versionInfoRequired");
+            }
+        }
+
+        // end
+        if (!skipChar('?')) {
+            err.jspError("jsp.error.xml.xmlDeclUnterminated");
+        }
+        if (!skipChar('>')) {
+            err.jspError("jsp.error.xml.xmlDeclUnterminated");
+
+        }
+        
+        // fill in return array
+        pseudoAttributeValues[0] = version;
+        pseudoAttributeValues[1] = encoding;
+        pseudoAttributeValues[2] = standalone;
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLScanner.scanPseudoAttribute
+    /**
+     * Scans a pseudo attribute.
+     *
+     * @param scanningTextDecl True if scanning this pseudo-attribute for a
+     *                         TextDecl; false if scanning XMLDecl. This 
+     *                         flag is needed to report the correct type of
+     *                         error.
+     * @param value            The string to fill in with the attribute 
+     *                         value.
+     *
+     * @return The name of the attribute
+     *
+     * <strong>Note:</strong> This method uses fStringBuffer2, anything in it
+     * at the time of calling is lost.
+     */
+    public String scanPseudoAttribute(boolean scanningTextDecl, 
+                                      XMLString value) 
+                throws IOException, JasperException {
+
+        String name = scanName();
+        if (name == null) {
+            err.jspError("jsp.error.xml.pseudoAttrNameExpected");
+        }
+        skipSpaces();
+        if (!skipChar('=')) {
+            reportFatalError(scanningTextDecl ?
+                             "jsp.error.xml.eqRequiredInTextDecl"
+                             : "jsp.error.xml.eqRequiredInXMLDecl",
+                             name);
+        }
+        skipSpaces();
+        int quote = peekChar();
+        if (quote != '\'' && quote != '"') {
+            reportFatalError(scanningTextDecl ?
+                             "jsp.error.xml.quoteRequiredInTextDecl"
+                             : "jsp.error.xml.quoteRequiredInXMLDecl" ,
+                             name);
+        }
+        scanChar();
+        int c = scanLiteral(quote, value);
+        if (c != quote) {
+            fStringBuffer2.clear();
+            do {
+                fStringBuffer2.append(value);
+                if (c != -1) {
+                    if (c == '&' || c == '%' || c == '<' || c == ']') {
+                        fStringBuffer2.append((char)scanChar());
+                    }
+                    else if (XMLChar.isHighSurrogate(c)) {
+                        scanSurrogates(fStringBuffer2);
+                    }
+                    else if (XMLChar.isInvalid(c)) {
+                        String key = scanningTextDecl
+                            ? "jsp.error.xml.invalidCharInTextDecl"
+                            : "jsp.error.xml.invalidCharInXMLDecl";
+                        reportFatalError(key, Integer.toString(c, 16));
+                        scanChar();
+                    }
+                }
+                c = scanLiteral(quote, value);
+            } while (c != quote);
+            fStringBuffer2.append(value);
+            value.setValues(fStringBuffer2);
+        }
+        if (!skipChar(quote)) {
+            reportFatalError(scanningTextDecl ?
+                             "jsp.error.xml.closeQuoteMissingInTextDecl"
+                             : "jsp.error.xml.closeQuoteMissingInXMLDecl",
+                             name);
+        }
+
+        // return
+        return name;
+
+    }
+    
+    // Adapted from:
+    // org.apache.xerces.impl.XMLScanner.scanPIData
+    /**
+     * Scans a processing data. This is needed to handle the situation
+     * where a document starts with a processing instruction whose 
+     * target name <em>starts with</em> "xml". (e.g. xmlfoo)
+     *
+     * <strong>Note:</strong> This method uses fStringBuffer, anything in it
+     * at the time of calling is lost.
+     *
+     * @param target The PI target
+     * @param data The string to fill in with the data
+     */
+    private void scanPIData(String target, XMLString data) 
+        throws IOException, JasperException {
+
+        // check target
+        if (target.length() == 3) {
+            char c0 = Character.toLowerCase(target.charAt(0));
+            char c1 = Character.toLowerCase(target.charAt(1));
+            char c2 = Character.toLowerCase(target.charAt(2));
+            if (c0 == 'x' && c1 == 'm' && c2 == 'l') {
+                err.jspError("jsp.error.xml.reservedPITarget");
+            }
+        }
+
+        // spaces
+        if (!skipSpaces()) {
+            if (skipString("?>")) {
+                // we found the end, there is no data
+                data.clear();
+                return;
+            }
+            else {
+                // if there is data there should be some space
+                err.jspError("jsp.error.xml.spaceRequiredInPI");
+            }
+        }
+
+        fStringBuffer.clear();
+        // data
+        if (scanData("?>", fStringBuffer)) {
+            do {
+                int c = peekChar();
+                if (c != -1) {
+                    if (XMLChar.isHighSurrogate(c)) {
+                        scanSurrogates(fStringBuffer);
+                    } else if (XMLChar.isInvalid(c)) {
+                        err.jspError("jsp.error.xml.invalidCharInPI",
+                                     Integer.toHexString(c));
+                        scanChar();
+                    }
+                }
+            } while (scanData("?>", fStringBuffer));
+        }
+        data.setValues(fStringBuffer);
+
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLScanner.scanSurrogates
+    /**
+     * Scans surrogates and append them to the specified buffer.
+     * <p>
+     * <strong>Note:</strong> This assumes the current char has already been
+     * identified as a high surrogate.
+     *
+     * @param buf The StringBuffer to append the read surrogates to.
+     * @return True if it succeeded.
+     */
+    private boolean scanSurrogates(XMLStringBuffer buf)
+        throws IOException, JasperException {
+
+        int high = scanChar();
+        int low = peekChar();
+        if (!XMLChar.isLowSurrogate(low)) {
+            err.jspError("jsp.error.xml.invalidCharInContent",
+                         Integer.toString(high, 16));
+            return false;
+        }
+        scanChar();
+
+        // convert surrogates to supplemental character
+        int c = XMLChar.supplemental((char)high, (char)low);
+
+        // supplemental character must be a valid XML character
+        if (!XMLChar.isValid(c)) {
+            err.jspError("jsp.error.xml.invalidCharInContent",
+                         Integer.toString(c, 16)); 
+            return false;
+        }
+
+        // fill in the buffer
+        buf.append((char)high);
+        buf.append((char)low);
+
+        return true;
+
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLScanner.reportFatalError
+    /**
+     * Convenience function used in all XML scanners.
+     */
+    private void reportFatalError(String msgId, String arg)
+                throws JasperException {
+        err.jspError(msgId, arg);
+    }
+
+}
+
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/XMLString.java b/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/XMLString.java
new file mode 100644
index 0000000..3b4e434
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/XMLString.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation and was
+ * originally based on software copyright (c) 1999, International
+ * Business Machines, Inc., http://www.apache.org.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.jasper.xmlparser;
+
+/**
+ * This class is used as a structure to pass text contained in the underlying
+ * character buffer of the scanner. The offset and length fields allow the
+ * buffer to be re-used without creating new character arrays.
+ * <p>
+ * <strong>Note:</strong> Methods that are passed an XMLString structure
+ * should consider the contents read-only and not make any modifications
+ * to the contents of the buffer. The method receiving this structure
+ * should also not modify the offset and length if this structure (or
+ * the values of this structure) are passed to another method.
+ * <p>
+ * <strong>Note:</strong> Methods that are passed an XMLString structure
+ * are required to copy the information out of the buffer if it is to be
+ * saved for use beyond the scope of the method. The contents of the 
+ * structure are volatile and the contents of the character buffer cannot
+ * be assured once the method that is passed this structure returns.
+ * Therefore, methods passed this structure should not save any reference
+ * to the structure or the character array contained in the structure.
+ *
+ * @author Eric Ye, IBM
+ * @author Andy Clark, IBM
+ *
+ * @version $Id: XMLString.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ */
+public class XMLString {
+
+    //
+    // Data
+    //
+
+    /** The character array. */
+    public char[] ch;
+
+    /** The offset into the character array. */
+    public int offset;
+
+    /** The length of characters from the offset. */
+    public int length;
+
+    //
+    // Constructors
+    //
+
+    /** Default constructor. */
+    public XMLString() {
+    } // <init>()
+
+    //
+    // Public methods
+    //
+
+    /**
+     * Initializes the contents of the XMLString structure with the
+     * specified values.
+     * 
+     * @param ch     The character array.
+     * @param offset The offset into the character array.
+     * @param length The length of characters from the offset.
+     */
+    public void setValues(char[] ch, int offset, int length) {
+        this.ch = ch;
+        this.offset = offset;
+        this.length = length;
+    } // setValues(char[],int,int)
+
+    /**
+     * Initializes the contents of the XMLString structure with copies
+     * of the given string structure.
+     * <p>
+     * <strong>Note:</strong> This does not copy the character array;
+     * only the reference to the array is copied.
+     * 
+     * @param s
+     */
+    public void setValues(XMLString s) {
+        setValues(s.ch, s.offset, s.length);
+    } // setValues(XMLString)
+
+    /** Resets all of the values to their defaults. */
+    public void clear() {
+        this.ch = null;
+        this.offset = 0;
+        this.length = -1;
+    } // clear()
+
+
+    /**
+     * Returns true if the contents of this XMLString structure and
+     * the specified string are equal.
+     * 
+     * @param s The string to compare.
+     */
+    public boolean equals(String s) {
+        if (s == null) {
+            return false;
+        }
+        if ( length != s.length() ) {
+            return false;
+        }
+
+        // is this faster than call s.toCharArray first and compare the 
+        // two arrays directly, which will possibly involve creating a
+        // new char array object.
+        for (int i=0; i<length; i++) {
+            if (ch[offset+i] != s.charAt(i)) {
+                return false;
+            }
+        }
+
+        return true;
+    } // equals(String):boolean
+
+    //
+    // Object methods
+    //
+
+    /** Returns a string representation of this object. */
+    @Override
+    public String toString() {
+        return length > 0 ? new String(ch, offset, length) : "";
+    } // toString():String
+
+} // class XMLString
diff --git a/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/XMLStringBuffer.java b/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/XMLStringBuffer.java
new file mode 100644
index 0000000..d9018a5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/jasper/xmlparser/XMLStringBuffer.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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation and was
+ * originally based on software copyright (c) 1999, International
+ * Business Machines, Inc., http://www.apache.org.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.jasper.xmlparser;
+
+/**
+ * XMLString is a structure used to pass character arrays. However,
+ * XMLStringBuffer is a buffer in which characters can be appended
+ * and extends XMLString so that it can be passed to methods
+ * expecting an XMLString object. This is a safe operation because
+ * it is assumed that any callee will <strong>not</strong> modify
+ * the contents of the XMLString structure.
+ * <p> 
+ * The contents of the string are managed by the string buffer. As
+ * characters are appended, the string buffer will grow as needed.
+ * <p>
+ * <strong>Note:</strong> Never set the <code>ch</code>, 
+ * <code>offset</code>, and <code>length</code> fields directly.
+ * These fields are managed by the string buffer. In order to reset
+ * the buffer, call <code>clear()</code>.
+ * 
+ * @author Andy Clark, IBM
+ * @author Eric Ye, IBM
+ *
+ * @version $Id: XMLStringBuffer.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ */
+public class XMLStringBuffer
+    extends XMLString {
+
+    //
+    // Constants
+    //
+
+    /** Default buffer size (32). */
+    public static final int DEFAULT_SIZE = 32;
+
+    //
+    // Constructors
+    //
+
+    /**
+     * 
+     */
+    public XMLStringBuffer() {
+        this(DEFAULT_SIZE);
+    } // <init>()
+
+    /**
+     * 
+     * 
+     * @param size 
+     */
+    public XMLStringBuffer(int size) {
+        ch = new char[size];
+    } // <init>(int)
+
+    //
+    // Public methods
+    //
+
+    /** Clears the string buffer. */
+    @Override
+    public void clear() {
+        offset = 0;
+        length = 0;
+    }
+
+    /**
+     * append
+     * 
+     * @param c 
+     */
+    public void append(char c) {
+        if (this.length + 1 > this.ch.length) {
+                    int newLength = this.ch.length*2;
+                    if (newLength < this.ch.length + DEFAULT_SIZE)
+                        newLength = this.ch.length + DEFAULT_SIZE;
+                    char[] newch = new char[newLength];
+                    System.arraycopy(this.ch, 0, newch, 0, this.length);
+                    this.ch = newch;
+        }
+        this.ch[this.length] = c;
+        this.length++;
+    } // append(char)
+
+    /**
+     * append
+     * 
+     * @param s 
+     */
+    public void append(String s) {
+        int length = s.length();
+        if (this.length + length > this.ch.length) {
+            int newLength = this.ch.length*2;
+            if (newLength < this.length + length + DEFAULT_SIZE)
+                newLength = this.ch.length + length + DEFAULT_SIZE;
+            char[] newch = new char[newLength];            
+            System.arraycopy(this.ch, 0, newch, 0, this.length);
+            this.ch = newch;
+        }
+        s.getChars(0, length, this.ch, this.length);
+        this.length += length;
+    } // append(String)
+
+    /**
+     * append
+     * 
+     * @param ch 
+     * @param offset 
+     * @param length 
+     */
+    public void append(char[] ch, int offset, int length) {
+        if (this.length + length > this.ch.length) {
+            char[] newch = new char[this.ch.length + length + DEFAULT_SIZE];
+            System.arraycopy(this.ch, 0, newch, 0, this.length);
+            this.ch = newch;
+        }
+        System.arraycopy(ch, offset, this.ch, this.length, length);
+        this.length += length;
+    } // append(char[],int,int)
+
+    /**
+     * append
+     * 
+     * @param s 
+     */
+    public void append(XMLString s) {
+        append(s.ch, s.offset, s.length);
+    } // append(XMLString)
+
+} // class XMLStringBuffer
diff --git a/bundles/org.apache.tomcat/src/org/apache/juli/logging/DirectJDKLog.java b/bundles/org.apache.tomcat/src/org/apache/juli/logging/DirectJDKLog.java
new file mode 100644
index 0000000..33d8ce0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/juli/logging/DirectJDKLog.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.juli.logging;
+
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/** 
+ * Hardcoded java.util.logging commons-logging implementation.
+ * 
+ * In addition, it curr 
+ * 
+ */
+class DirectJDKLog implements Log {
+    // no reason to hide this - but good reasons to not hide
+    public Logger logger;
+    
+    /** Alternate config reader and console format 
+     */
+    private static final String SIMPLE_FMT="java.util.logging.SimpleFormatter";
+    private static final String SIMPLE_CFG="org.apache.juli.JdkLoggerConfig"; //doesn't exist
+    private static final String FORMATTER="org.apache.juli.formatter";
+
+    static {
+        if( System.getProperty("java.util.logging.config.class") ==null  &&
+                System.getProperty("java.util.logging.config.file") ==null ) {
+            // default configuration - it sucks. Let's override at least the 
+            // formatter for the console
+            try {
+                Class.forName(SIMPLE_CFG).newInstance();                
+            } catch( Throwable t ) {                
+            }
+            try {
+                Formatter fmt=(Formatter)Class.forName(System.getProperty(FORMATTER, SIMPLE_FMT)).newInstance(); 
+                // it is also possible that the user modified jre/lib/logging.properties - 
+                // but that's really stupid in most cases
+                Logger root=Logger.getLogger("");
+                Handler handlers[]=root.getHandlers();
+                for( int i=0; i< handlers.length; i++ ) {
+                    // I only care about console - that's what's used in default config anyway
+                    if( handlers[i] instanceof  ConsoleHandler ) {
+                        handlers[i].setFormatter(fmt);
+                    }
+                }
+            } catch( Throwable t ) {
+                // maybe it wasn't included - the ugly default will be used.
+            }
+            
+        }
+    }
+    
+    public DirectJDKLog(String name ) {
+        logger=Logger.getLogger(name);        
+    }
+    
+    @Override
+    public final boolean isErrorEnabled() {
+        return logger.isLoggable(Level.SEVERE);
+    }
+    
+    @Override
+    public final boolean isWarnEnabled() {
+        return logger.isLoggable(Level.WARNING); 
+    }
+    
+    @Override
+    public final boolean isInfoEnabled() {
+        return logger.isLoggable(Level.INFO);
+    }
+    
+    @Override
+    public final boolean isDebugEnabled() {
+        return logger.isLoggable(Level.FINE);
+    }
+    
+    @Override
+    public final boolean isFatalEnabled() {
+        return logger.isLoggable(Level.SEVERE);
+    }
+    
+    @Override
+    public final boolean isTraceEnabled() {
+        return logger.isLoggable(Level.FINER);
+    }
+    
+    @Override
+    public final void debug(Object message) {
+        log(Level.FINE, String.valueOf(message), null);
+    }
+    
+    @Override
+    public final void debug(Object message, Throwable t) {
+        log(Level.FINE, String.valueOf(message), t);
+    }
+    
+    @Override
+    public final void trace(Object message) {
+        log(Level.FINER, String.valueOf(message), null);
+    }
+    
+    @Override
+    public final void trace(Object message, Throwable t) {
+        log(Level.FINER, String.valueOf(message), t);
+    }
+    
+    @Override
+    public final void info(Object message) {
+        log(Level.INFO, String.valueOf(message), null);
+    }
+    
+    @Override
+    public final void info(Object message, Throwable t) {        
+        log(Level.INFO, String.valueOf(message), t);
+    }
+    
+    @Override
+    public final void warn(Object message) {
+        log(Level.WARNING, String.valueOf(message), null);
+    }
+    
+    @Override
+    public final void warn(Object message, Throwable t) {
+        log(Level.WARNING, String.valueOf(message), t);
+    }
+    
+    @Override
+    public final void error(Object message) {
+        log(Level.SEVERE, String.valueOf(message), null);
+    }
+    
+    @Override
+    public final void error(Object message, Throwable t) {
+        log(Level.SEVERE, String.valueOf(message), t);
+    }
+    
+    @Override
+    public final void fatal(Object message) {
+        log(Level.SEVERE, String.valueOf(message), null);
+    }
+    
+    @Override
+    public final void fatal(Object message, Throwable t) {
+        log(Level.SEVERE, String.valueOf(message), t);
+    }    
+
+    // from commons logging. This would be my number one reason why java.util.logging
+    // is bad - design by committee can be really bad ! The impact on performance of 
+    // using java.util.logging - and the ugliness if you need to wrap it - is far
+    // worse than the unfriendly and uncommon default format for logs. 
+    
+    private void log( Level level, String msg, Throwable ex ) {
+        if (logger.isLoggable(level)) {
+            // Hack (?) to get the stack trace.
+            Throwable dummyException=new Throwable();
+            StackTraceElement locations[]=dummyException.getStackTrace();
+            // Caller will be the third element
+            String cname="unknown";
+            String method="unknown";
+            if( locations!=null && locations.length >2 ) {
+                StackTraceElement caller=locations[2];
+                cname=caller.getClassName();
+                method=caller.getMethodName();
+            }
+            if( ex==null ) {
+                logger.logp( level, cname, method, msg );
+            } else {
+                logger.logp( level, cname, method, msg, ex );
+            }
+        }
+    }        
+
+    // for LogFactory
+    static void release() {
+        
+    }
+    
+    static Log getInstance(String name) {
+        return new DirectJDKLog( name );
+    }
+}
+
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/juli/logging/Log.java b/bundles/org.apache.tomcat/src/org/apache/juli/logging/Log.java
new file mode 100644
index 0000000..474ad5b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/juli/logging/Log.java
@@ -0,0 +1,234 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+
+package org.apache.juli.logging;
+
+/**
+ * <p>A simple logging interface abstracting logging APIs.  In order to be
+ * instantiated successfully by {@link LogFactory}, classes that implement
+ * this interface must have a constructor that takes a single String
+ * parameter representing the "name" of this Log.</p>
+ *
+ * <p> The six logging levels used by <code>Log</code> are (in order):
+ * <ol>
+ * <li>trace (the least serious)</li>
+ * <li>debug</li>
+ * <li>info</li>
+ * <li>warn</li>
+ * <li>error</li>
+ * <li>fatal (the most serious)</li>
+ * </ol>
+ * The mapping of these log levels to the concepts used by the underlying
+ * logging system is implementation dependent.
+ * The implementation should ensure, though, that this ordering behaves
+ * as expected.</p>
+ *
+ * <p>Performance is often a logging concern.
+ * By examining the appropriate property,
+ * a component can avoid expensive operations (producing information
+ * to be logged).</p>
+ *
+ * <p> For example,
+ * <code><pre>
+ *    if (log.isDebugEnabled()) {
+ *        ... do something expensive ...
+ *        log.debug(theResult);
+ *    }
+ * </pre></code>
+ * </p>
+ *
+ * <p>Configuration of the underlying logging system will generally be done
+ * external to the Logging APIs, through whatever mechanism is supported by
+ * that system.</p>
+ *
+ * @author <a href="mailto:sanders@apache.org">Scott Sanders</a>
+ * @author Rod Waldhoff
+ * @version $Id: Log.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+public interface Log {
+
+
+    // ----------------------------------------------------- Logging Properties
+
+
+    /**
+     * <p> Is debug logging currently enabled? </p>
+     *
+     * <p> Call this method to prevent having to perform expensive operations
+     * (for example, <code>String</code> concatenation)
+     * when the log level is more than debug. </p>
+     */
+    public boolean isDebugEnabled();
+
+
+    /**
+     * <p> Is error logging currently enabled? </p>
+     *
+     * <p> Call this method to prevent having to perform expensive operations
+     * (for example, <code>String</code> concatenation)
+     * when the log level is more than error. </p>
+     */
+    public boolean isErrorEnabled();
+
+
+    /**
+     * <p> Is fatal logging currently enabled? </p>
+     *
+     * <p> Call this method to prevent having to perform expensive operations
+     * (for example, <code>String</code> concatenation)
+     * when the log level is more than fatal. </p>
+     */
+    public boolean isFatalEnabled();
+
+
+    /**
+     * <p> Is info logging currently enabled? </p>
+     *
+     * <p> Call this method to prevent having to perform expensive operations
+     * (for example, <code>String</code> concatenation)
+     * when the log level is more than info. </p>
+     */
+    public boolean isInfoEnabled();
+
+
+    /**
+     * <p> Is trace logging currently enabled? </p>
+     *
+     * <p> Call this method to prevent having to perform expensive operations
+     * (for example, <code>String</code> concatenation)
+     * when the log level is more than trace. </p>
+     */
+    public boolean isTraceEnabled();
+
+
+    /**
+     * <p> Is warn logging currently enabled? </p>
+     *
+     * <p> Call this method to prevent having to perform expensive operations
+     * (for example, <code>String</code> concatenation)
+     * when the log level is more than warn. </p>
+     */
+    public boolean isWarnEnabled();
+
+
+    // -------------------------------------------------------- Logging Methods
+
+
+    /**
+     * <p> Log a message with trace log level. </p>
+     *
+     * @param message log this message
+     */
+    public void trace(Object message);
+
+
+    /**
+     * <p> Log an error with trace log level. </p>
+     *
+     * @param message log this message
+     * @param t log this cause
+     */
+    public void trace(Object message, Throwable t);
+
+
+    /**
+     * <p> Log a message with debug log level. </p>
+     *
+     * @param message log this message
+     */
+    public void debug(Object message);
+
+
+    /**
+     * <p> Log an error with debug log level. </p>
+     *
+     * @param message log this message
+     * @param t log this cause
+     */
+    public void debug(Object message, Throwable t);
+
+
+    /**
+     * <p> Log a message with info log level. </p>
+     *
+     * @param message log this message
+     */
+    public void info(Object message);
+
+
+    /**
+     * <p> Log an error with info log level. </p>
+     *
+     * @param message log this message
+     * @param t log this cause
+     */
+    public void info(Object message, Throwable t);
+
+
+    /**
+     * <p> Log a message with warn log level. </p>
+     *
+     * @param message log this message
+     */
+    public void warn(Object message);
+
+
+    /**
+     * <p> Log an error with warn log level. </p>
+     *
+     * @param message log this message
+     * @param t log this cause
+     */
+    public void warn(Object message, Throwable t);
+
+
+    /**
+     * <p> Log a message with error log level. </p>
+     *
+     * @param message log this message
+     */
+    public void error(Object message);
+
+
+    /**
+     * <p> Log an error with error log level. </p>
+     *
+     * @param message log this message
+     * @param t log this cause
+     */
+    public void error(Object message, Throwable t);
+
+
+    /**
+     * <p> Log a message with fatal log level. </p>
+     *
+     * @param message log this message
+     */
+    public void fatal(Object message);
+
+
+    /**
+     * <p> Log an error with fatal log level. </p>
+     *
+     * @param message log this message
+     * @param t log this cause
+     */
+    public void fatal(Object message, Throwable t);
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/juli/logging/LogConfigurationException.java b/bundles/org.apache.tomcat/src/org/apache/juli/logging/LogConfigurationException.java
new file mode 100644
index 0000000..ff75a6c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/juli/logging/LogConfigurationException.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.juli.logging;
+
+
+/**
+ * <p>An exception that is thrown only if a suitable <code>LogFactory</code>
+ * or <code>Log</code> instance cannot be created by the corresponding
+ * factory methods.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: LogConfigurationException.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+
+public class LogConfigurationException extends RuntimeException {
+
+
+    private static final long serialVersionUID = 1L;
+
+
+    /**
+     * Construct a new exception with <code>null</code> as its detail message.
+     */
+    public LogConfigurationException() {
+        super();
+    }
+
+
+    /**
+     * Construct a new exception with the specified detail message.
+     *
+     * @param message The detail message
+     */
+    public LogConfigurationException(String message) {
+        super(message);
+    }
+
+
+    /**
+     * Construct a new exception with the specified cause and a derived
+     * detail message.
+     *
+     * @param cause The underlying cause
+     */
+    public LogConfigurationException(Throwable cause) {
+        this( ((cause == null) ? null : cause.toString()), cause);
+    }
+
+
+    /**
+     * Construct a new exception with the specified detail message and cause.
+     *
+     * @param message The detail message
+     * @param cause The underlying cause
+     */
+    public LogConfigurationException(String message, Throwable cause) {
+
+        super(message);
+        this.cause = cause; // Two-argument version requires JDK 1.4 or later
+
+    }
+
+
+    /**
+     * The underlying cause of this exception.
+     */
+    protected Throwable cause = null;
+
+
+    /**
+     * Return the underlying cause of this exception (if any).
+     */
+    @Override
+    public Throwable getCause() {
+
+        return (this.cause);
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/juli/logging/LogFactory.java b/bundles/org.apache.tomcat/src/org/apache/juli/logging/LogFactory.java
new file mode 100644
index 0000000..aa57192
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/juli/logging/LogFactory.java
@@ -0,0 +1,365 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.logging;
+
+
+import java.util.Properties;
+import java.util.logging.LogManager;
+
+
+/**
+ * Modified LogFactory: removed all discovery, hardcode a specific implementation
+ * If you like a different logging implementation - use either the discovery-based
+ * commons-logging, or better - another implementation hardcoded to your favourite
+ * logging impl.
+ * 
+ * Why ? Each application and deployment can choose a logging implementation - 
+ * that involves configuration, installing the logger jar and optional plugins, etc.
+ * As part of this process - they can as well install the commons-logging implementation
+ * that corresponds to their logger of choice. This completely avoids any discovery
+ * problem, while still allowing the user to switch. 
+ * 
+ * Note that this implementation is not just a wrapper around JDK logging ( like
+ * the original commons-logging impl ). It adds 2 features - a simpler configuration
+ * ( which is in fact a subset of log4j.properties ) and a formatter that is 
+ * less ugly.   
+ * 
+ * The removal of 'abstract' preserves binary backward compatibility. It is possible
+ * to preserve the abstract - and introduce another ( hardcoded ) factory - but I 
+ * see no benefit. 
+ * 
+ * Since this class is not intended to be extended - and provides
+ * no plugin for other LogFactory implementation - all protected methods are removed.
+ * This can be changed - but again, there is little value in keeping dead code.
+ * Just take a quick look at the removed code ( and it's complexity)  
+ * 
+ * --------------
+ * 
+ * Original comment:
+ * <p>Factory for creating {@link Log} instances, with discovery and
+ * configuration features similar to that employed by standard Java APIs
+ * such as JAXP.</p>
+ * 
+ * <p><strong>IMPLEMENTATION NOTE</strong> - This implementation is heavily
+ * based on the SAXParserFactory and DocumentBuilderFactory implementations
+ * (corresponding to the JAXP pluggability APIs) found in Apache Xerces.</p>
+ * 
+ *
+ * @author Craig R. McClanahan
+ * @author Costin Manolache
+ * @author Richard A. Sitze
+ * @version $Id: LogFactory.java,v 1.1 2011/06/28 21:08:15 rherrmann Exp $
+ */
+public /* abstract */ class LogFactory {
+
+    // ----------------------------------------------------- Manifest Constants
+
+    /**
+     * The name of the property used to identify the LogFactory implementation
+     * class name.
+     */
+    public static final String FACTORY_PROPERTY =
+        "org.apache.commons.logging.LogFactory";
+
+    /**
+     * The fully qualified class name of the fallback <code>LogFactory</code>
+     * implementation class to use, if no other can be found.
+     */
+    public static final String FACTORY_DEFAULT =
+        "org.apache.commons.logging.impl.LogFactoryImpl";
+
+    /**
+     * The name of the properties file to search for.
+     */
+    public static final String FACTORY_PROPERTIES =
+        "commons-logging.properties";
+    
+    /**
+     * <p>Setting this system property value allows the <code>Hashtable</code> used to store
+     * classloaders to be substituted by an alternative implementation.
+     * </p>
+     * <p>
+     * <strong>Note:</strong> <code>LogFactory</code> will print:
+     * <code><pre>
+     * [ERROR] LogFactory: Load of custom hashtable failed</em>
+     * </code></pre>
+     * to system error and then continue using a standard Hashtable.
+     * </p>
+     * <p>
+     * <strong>Usage:</strong> Set this property when Java is invoked
+     * and <code>LogFactory</code> will attempt to load a new instance 
+     * of the given implementation class.
+     * For example, running the following ant scriptlet:
+     * <code><pre>
+     *  &lt;java classname="${test.runner}" fork="yes" failonerror="${test.failonerror}"&gt;
+     *     ...
+     *     &lt;sysproperty 
+     *        key="org.apache.commons.logging.LogFactory.HashtableImpl"
+     *        value="org.apache.commons.logging.AltHashtable"/&gt;
+     *  &lt;/java&gt;
+     * </pre></code>
+     * will mean that <code>LogFactory</code> will load an instance of
+     * <code>org.apache.commons.logging.AltHashtable</code>.
+     * </p>
+     * <p>
+     * A typical use case is to allow a custom
+     * Hashtable implementation using weak references to be substituted.
+     * This will allow classloaders to be garbage collected without
+     * the need to release them (on 1.3+ JVMs only, of course ;)
+     * </p>
+     */
+    public static final String HASHTABLE_IMPLEMENTATION_PROPERTY =
+        "org.apache.commons.logging.LogFactory.HashtableImpl";
+    
+    private static LogFactory singleton=new LogFactory();
+
+    Properties logConfig;
+    
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Protected constructor that is not available for public use.
+     */
+    private LogFactory() {
+        logConfig=new Properties();
+    }
+    
+    // hook for syserr logger - class level
+    void setLogConfig( Properties p ) {
+        this.logConfig=p;
+    }
+    // --------------------------------------------------------- Public Methods
+
+    // only those 2 methods need to change to use a different direct logger.
+    
+    /**
+     * <p>Construct (if necessary) and return a <code>Log</code> instance,
+     * using the factory's current set of configuration attributes.</p>
+     *
+     * <p><strong>NOTE</strong> - Depending upon the implementation of
+     * the <code>LogFactory</code> you are using, the <code>Log</code>
+     * instance you are returned may or may not be local to the current
+     * application, and may or may not be returned again on a subsequent
+     * call with the same name argument.</p>
+     *
+     * @param name Logical name of the <code>Log</code> instance to be
+     *  returned (the meaning of this name is only known to the underlying
+     *  logging implementation that is being wrapped)
+     *
+     * @exception LogConfigurationException if a suitable <code>Log</code>
+     *  instance cannot be returned
+     */
+    public Log getInstance(String name)
+        throws LogConfigurationException {
+        return DirectJDKLog.getInstance(name);
+    }
+
+
+    /**
+     * Release any internal references to previously created {@link Log}
+     * instances returned by this factory.  This is useful in environments
+     * like servlet containers, which implement application reloading by
+     * throwing away a ClassLoader.  Dangling references to objects in that
+     * class loader would prevent garbage collection.
+     */
+    public void release() {
+        DirectJDKLog.release();
+    }
+
+    /**
+     * Return the configuration attribute with the specified name (if any),
+     * or <code>null</code> if there is no such attribute.
+     *
+     * @param name Name of the attribute to return
+     */
+    public Object getAttribute(String name) {
+        return logConfig.get(name);
+    }
+
+
+    /**
+     * Return an array containing the names of all currently defined
+     * configuration attributes.  If there are no such attributes, a zero
+     * length array is returned.
+     */
+    public String[] getAttributeNames() {
+        String result[] = new String[logConfig.size()];
+        return logConfig.keySet().toArray(result);
+    }
+
+    /**
+     * Remove any configuration attribute associated with the specified name.
+     * If there is no such attribute, no action is taken.
+     *
+     * @param name Name of the attribute to remove
+     */
+    public void removeAttribute(String name) {
+        logConfig.remove(name);
+     }   
+
+
+    /**
+     * Set the configuration attribute with the specified name.  Calling
+     * this with a <code>null</code> value is equivalent to calling
+     * <code>removeAttribute(name)</code>.
+     *
+     * @param name Name of the attribute to set
+     * @param value Value of the attribute to set, or <code>null</code>
+     *  to remove any setting for this attribute
+     */
+    public void setAttribute(String name, Object value) {
+        logConfig.put(name, value);
+    }
+
+
+    /**
+     * Convenience method to derive a name from the specified class and
+     * call <code>getInstance(String)</code> with it.
+     *
+     * @param clazz Class for which a suitable Log name will be derived
+     *
+     * @exception LogConfigurationException if a suitable <code>Log</code>
+     *  instance cannot be returned
+     */
+    public Log getInstance(Class<?> clazz)
+        throws LogConfigurationException {
+        return getInstance( clazz.getName());
+    }
+
+
+    // ------------------------------------------------------- Static Variables
+
+
+    // --------------------------------------------------------- Static Methods
+
+
+    /**
+     * <p>Construct (if necessary) and return a <code>LogFactory</code>
+     * instance, using the following ordered lookup procedure to determine
+     * the name of the implementation class to be loaded.</p>
+     * <ul>
+     * <li>The <code>org.apache.commons.logging.LogFactory</code> system
+     *     property.</li>
+     * <li>The JDK 1.3 Service Discovery mechanism</li>
+     * <li>Use the properties file <code>commons-logging.properties</code>
+     *     file, if found in the class path of this class.  The configuration
+     *     file is in standard <code>java.util.Properties</code> format and
+     *     contains the fully qualified name of the implementation class
+     *     with the key being the system property defined above.</li>
+     * <li>Fall back to a default implementation class
+     *     (<code>org.apache.commons.logging.impl.LogFactoryImpl</code>).</li>
+     * </ul>
+     *
+     * <p><em>NOTE</em> - If the properties file method of identifying the
+     * <code>LogFactory</code> implementation class is utilized, all of the
+     * properties defined in this file will be set as configuration attributes
+     * on the corresponding <code>LogFactory</code> instance.</p>
+     *
+     * @exception LogConfigurationException if the implementation class is not
+     *  available or cannot be instantiated.
+     */
+    public static LogFactory getFactory() throws LogConfigurationException {
+        return singleton;
+    }
+
+
+    /**
+     * Convenience method to return a named logger, without the application
+     * having to care about factories.
+     *
+     * @param clazz Class from which a log name will be derived
+     *
+     * @exception LogConfigurationException if a suitable <code>Log</code>
+     *  instance cannot be returned
+     */
+    public static Log getLog(Class<?> clazz)
+        throws LogConfigurationException {
+        return (getFactory().getInstance(clazz));
+
+    }
+
+
+    /**
+     * Convenience method to return a named logger, without the application
+     * having to care about factories.
+     *
+     * @param name Logical name of the <code>Log</code> instance to be
+     *  returned (the meaning of this name is only known to the underlying
+     *  logging implementation that is being wrapped)
+     *
+     * @exception LogConfigurationException if a suitable <code>Log</code>
+     *  instance cannot be returned
+     */
+    public static Log getLog(String name)
+        throws LogConfigurationException {
+        return (getFactory().getInstance(name));
+
+    }
+
+
+    /**
+     * Release any internal references to previously created {@link LogFactory}
+     * instances that have been associated with the specified class loader
+     * (if any), after calling the instance method <code>release()</code> on
+     * each of them.
+     *
+     * @param classLoader ClassLoader for which to release the LogFactory
+     */
+    public static void release(ClassLoader classLoader) {
+        // JULI's log manager looks at the current classLoader so there is no
+        // need to use the passed in classLoader, the default implementation
+        // does not so calling reset in that case will break things
+        if (!LogManager.getLogManager().getClass().getName().equals(
+                "java.util.logging.LogManager")) {
+            LogManager.getLogManager().reset();
+        }
+    }
+
+
+    /**
+     * Release any internal references to previously created {@link LogFactory}
+     * instances, after calling the instance method <code>release()</code> on
+     * each of them.  This is useful in environments like servlet containers,
+     * which implement application reloading by throwing away a ClassLoader.
+     * Dangling references to objects in that class loader would prevent
+     * garbage collection.
+     */
+    public static void releaseAll() {
+        singleton.release();
+    }
+
+    /**
+     * Returns a string that uniquely identifies the specified object, including
+     * its class.
+     * <p>
+     * The returned string is of form "classname@hashcode", ie is the same as
+     * the return value of the Object.toString() method, but works even when
+     * the specified object's class has overridden the toString method.
+     * 
+     * @param o may be null.
+     * @return a string of form classname@hashcode, or "null" if param o is null.
+     */
+    public static String objectId(Object o) {
+        if (o == null) {
+            return "null";
+        } else {
+            return o.getClass().getName() + "@" + System.identityHashCode(o);
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/juli/logging/package.html b/bundles/org.apache.tomcat/src/org/apache/juli/logging/package.html
new file mode 100644
index 0000000..6d12ce5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/juli/logging/package.html
@@ -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.
+-->
+<body>
+
+<h3>Overview</h3>
+
+
+<p>This implementation of commons-logging uses a  commons-logging.jar
+ specific to a particular logging framework, instead of discovery. This takes 
+out the guessing, is simpler, faster and more robust. Just like you chose a 
+logging implementation, you should also use a matching commons-logging - for
+example you download log4j.jar and commons-logging-log4j.jar, or use jdk 
+logging and use commons-logging-jdk.jar.</p>
+
+<p>A similar packaging is used by Eclipse SWT - they provide a common widget API,
+ but each platform uses a different implementation jar - instead of using a complex 
+ discovery/plugin mechanism.
+</p> 
+
+<p>This package generates commons-logging-jdk14.jar - i.e. the java.util implementation
+of commons-logging api.<p>
+
+</body>
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/Constants.java b/bundles/org.apache.tomcat/src/org/apache/naming/Constants.java
new file mode 100644
index 0000000..ffb3c42
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/Constants.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.naming;
+
+
+/**
+ * Static constants for this package.
+ */
+
+public final class Constants {
+
+    public static final String Package = "org.apache.naming";
+
+    /**
+     * 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/naming/ContextAccessController.java b/bundles/org.apache.tomcat/src/org/apache/naming/ContextAccessController.java
new file mode 100644
index 0000000..436b749
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/ContextAccessController.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.naming;
+
+import java.util.Hashtable;
+
+/**
+ * Handles the access control on the JNDI contexts.
+ *
+ * @author Remy Maucherat
+ * @version $Id: ContextAccessController.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+
+public class ContextAccessController {
+
+
+    // -------------------------------------------------------------- Variables
+
+
+    /**
+     * Catalina context names on which writing is not allowed.
+     */
+    private static Hashtable<Object,Object> readOnlyContexts =
+        new Hashtable<Object,Object>();
+
+
+    /**
+     * Security tokens repository.
+     */
+    private static Hashtable<Object,Object> securityTokens =
+        new Hashtable<Object,Object>();
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Set a security token for a context. Can be set only once.
+     * 
+     * @param name Name of the context
+     * @param token Security token
+     */
+    public static void setSecurityToken(Object name, Object token) {
+        if ((!securityTokens.containsKey(name)) && (token != null)) {
+            securityTokens.put(name, token);
+        }
+    }
+
+
+    /**
+     * Remove a security token for a context.
+     * 
+     * @param name Name of the context
+     * @param token Security token
+     */
+    public static void unsetSecurityToken(Object name, Object token) {
+        if (checkSecurityToken(name, token)) {
+            securityTokens.remove(name);
+        }
+    }
+
+
+    /**
+     * Check a submitted security token. The submitted token must be equal to
+     * the token present in the repository. If no token is present for the 
+     * context, then returns true.
+     * 
+     * @param name Name of the context
+     * @param token Submitted security token
+     */
+    public static boolean checkSecurityToken
+        (Object name, Object token) {
+        Object refToken = securityTokens.get(name);
+        return (refToken == null || refToken.equals(token));
+    }
+
+
+    /**
+     * Allow writing to a context.
+     * 
+     * @param name Name of the context
+     * @param token Security token
+     */
+    public static void setWritable(Object name, Object token) {
+        if (checkSecurityToken(name, token))
+            readOnlyContexts.remove(name);
+    }
+
+
+    /**
+     * Set whether or not a context is writable.
+     * 
+     * @param name Name of the context
+     */
+    public static void setReadOnly(Object name) {
+        readOnlyContexts.put(name, name);
+    }
+
+
+    /**
+     * Returns if a context is writable.
+     * 
+     * @param name Name of the context
+     */
+    public static boolean isWritable(Object name) {
+        return !(readOnlyContexts.containsKey(name));
+    }
+
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/ContextBindings.java b/bundles/org.apache.tomcat/src/org/apache/naming/ContextBindings.java
new file mode 100644
index 0000000..4dee87b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/ContextBindings.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.naming;
+
+import java.util.Hashtable;
+
+import javax.naming.Context;
+import javax.naming.NamingException;
+
+/**
+ * Handles the associations :
+ * <ul>
+ * <li>Catalina context name with the NamingContext</li>
+ * <li>Calling thread with the NamingContext</li>
+ * </ul>
+ *
+ * @author Remy Maucherat
+ * @version $Id: ContextBindings.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+
+public class ContextBindings {
+
+
+    // -------------------------------------------------------------- Variables
+
+
+    /**
+     * Bindings name - naming context. Keyed by name.
+     */
+    private static Hashtable<Object,Context> contextNameBindings =
+        new Hashtable<Object,Context>();
+
+
+    /**
+     * Bindings thread - naming context. Keyed by thread id.
+     */
+    private static Hashtable<Thread,Context> threadBindings =
+        new Hashtable<Thread,Context>();
+
+
+    /**
+     * Bindings thread - name. Keyed by thread id.
+     */
+    private static Hashtable<Thread,Object> threadNameBindings =
+        new Hashtable<Thread,Object>();
+
+
+    /**
+     * Bindings class loader - naming context. Keyed by CL id.
+     */
+    private static Hashtable<ClassLoader,Context> clBindings =
+        new Hashtable<ClassLoader,Context>();
+
+
+    /**
+     * Bindings class loader - name. Keyed by CL id.
+     */
+    private static Hashtable<ClassLoader,Object> clNameBindings =
+        new Hashtable<ClassLoader,Object>();
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm = 
+        StringManager.getManager(Constants.Package);
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Binds a context name.
+     * 
+     * @param name Name of the context
+     * @param context Associated naming context instance
+     */
+    public static void bindContext(Object name, Context context) {
+        bindContext(name, context, null);
+    }
+
+
+    /**
+     * Binds a context name.
+     * 
+     * @param name Name of the context
+     * @param context Associated naming context instance
+     * @param token Security token
+     */
+    public static void bindContext(Object name, Context context, 
+                                   Object token) {
+        if (ContextAccessController.checkSecurityToken(name, token))
+            contextNameBindings.put(name, context);
+    }
+
+
+    /**
+     * Unbind context name.
+     * 
+     * @param name Name of the context
+     */
+    public static void unbindContext(Object name) {
+        unbindContext(name, null);
+    }
+
+
+    /**
+     * Unbind context name.
+     * 
+     * @param name Name of the context
+     * @param token Security token
+     */
+    public static void unbindContext(Object name, Object token) {
+        if (ContextAccessController.checkSecurityToken(name, token))
+            contextNameBindings.remove(name);
+    }
+
+
+    /**
+     * Retrieve a naming context.
+     * 
+     * @param name Name of the context
+     */
+    static Context getContext(Object name) {
+        return contextNameBindings.get(name);
+    }
+
+
+    /**
+     * Binds a naming context to a thread.
+     * 
+     * @param name Name of the context
+     */
+    public static void bindThread(Object name) 
+        throws NamingException {
+        bindThread(name, null);
+    }
+
+
+    /**
+     * Binds a naming context to a thread.
+     * 
+     * @param name Name of the context
+     * @param token Security token
+     */
+    public static void bindThread(Object name, Object token) 
+        throws NamingException {
+        if (ContextAccessController.checkSecurityToken(name, token)) {
+            Context context = contextNameBindings.get(name);
+            if (context == null)
+                throw new NamingException
+                    (sm.getString("contextBindings.unknownContext", name));
+            threadBindings.put(Thread.currentThread(), context);
+            threadNameBindings.put(Thread.currentThread(), name);
+        }
+    }
+
+
+    /**
+     * Unbinds a naming context to a thread.
+     * 
+     * @param name Name of the context
+     */
+    public static void unbindThread(Object name) {
+        unbindThread(name, null);
+    }
+
+
+    /**
+     * Unbinds a naming context to a thread.
+     * 
+     * @param name Name of the context
+     * @param token Security token
+     */
+    public static void unbindThread(Object name, Object token) {
+        if (ContextAccessController.checkSecurityToken(name, token)) {
+            threadBindings.remove(Thread.currentThread());
+            threadNameBindings.remove(Thread.currentThread());
+        }
+    }
+
+
+    /**
+     * Retrieves the naming context bound to a thread.
+     */
+    public static Context getThread()
+        throws NamingException {
+        Context context = threadBindings.get(Thread.currentThread());
+        if (context == null)
+            throw new NamingException
+                (sm.getString("contextBindings.noContextBoundToThread"));
+        return context;
+    }
+
+
+    /**
+     * Retrieves the naming context name bound to a thread.
+     */
+    static Object getThreadName()
+        throws NamingException {
+        Object name = threadNameBindings.get(Thread.currentThread());
+        if (name == null)
+            throw new NamingException
+                (sm.getString("contextBindings.noContextBoundToThread"));
+        return name;
+    }
+
+
+    /**
+     * Tests if current thread is bound to a context.
+     */
+    public static boolean isThreadBound() {
+        return (threadBindings.containsKey(Thread.currentThread()));
+    }
+
+
+    /**
+     * Binds a naming context to a class loader.
+     * 
+     * @param name Name of the context
+     */
+    public static void bindClassLoader(Object name) 
+        throws NamingException {
+        bindClassLoader(name, null);
+    }
+
+
+    /**
+     * Binds a naming context to a thread.
+     * 
+     * @param name Name of the context
+     * @param token Security token
+     */
+    public static void bindClassLoader(Object name, Object token) 
+        throws NamingException {
+        bindClassLoader
+            (name, token, Thread.currentThread().getContextClassLoader());
+    }
+
+
+    /**
+     * Binds a naming context to a thread.
+     * 
+     * @param name Name of the context
+     * @param token Security token
+     */
+    public static void bindClassLoader(Object name, Object token, 
+                                       ClassLoader classLoader) 
+        throws NamingException {
+        if (ContextAccessController.checkSecurityToken(name, token)) {
+            Context context = contextNameBindings.get(name);
+            if (context == null)
+                throw new NamingException
+                    (sm.getString("contextBindings.unknownContext", name));
+            clBindings.put(classLoader, context);
+            clNameBindings.put(classLoader, name);
+        }
+    }
+
+
+    /**
+     * Unbinds a naming context to a class loader.
+     * 
+     * @param name Name of the context
+     */
+    public static void unbindClassLoader(Object name) {
+        unbindClassLoader(name, null);
+    }
+
+
+    /**
+     * Unbinds a naming context to a class loader.
+     * 
+     * @param name Name of the context
+     * @param token Security token
+     */
+    public static void unbindClassLoader(Object name, Object token) {
+        unbindClassLoader(name, token, 
+                          Thread.currentThread().getContextClassLoader());
+    }
+
+
+    /**
+     * Unbinds a naming context to a class loader.
+     * 
+     * @param name Name of the context
+     * @param token Security token
+     */
+    public static void unbindClassLoader(Object name, Object token, 
+                                         ClassLoader classLoader) {
+        if (ContextAccessController.checkSecurityToken(name, token)) {
+            Object n = clNameBindings.get(classLoader);
+            if ((n==null) || !(n.equals(name))) {
+                return;
+            }
+            clBindings.remove(classLoader);
+            clNameBindings.remove(classLoader);
+        }
+    }
+
+
+    /**
+     * Retrieves the naming context bound to a class loader.
+     */
+    public static Context getClassLoader()
+        throws NamingException {
+        ClassLoader cl = Thread.currentThread().getContextClassLoader();
+        Context context = null;
+        do {
+            context = clBindings.get(cl);
+            if (context != null) {
+                return context;
+            }
+        } while ((cl = cl.getParent()) != null);
+        throw new NamingException
+            (sm.getString("contextBindings.noContextBoundToCL"));
+    }
+
+
+    /**
+     * Retrieves the naming context name bound to a class loader.
+     */
+    static Object getClassLoaderName()
+        throws NamingException {
+        ClassLoader cl = Thread.currentThread().getContextClassLoader();
+        Object name = null;
+        do {
+            name = clNameBindings.get(cl);
+            if (name != null) {
+                return name;
+            }
+        } while ((cl = cl.getParent()) != null);
+        throw new NamingException
+            (sm.getString("contextBindings.noContextBoundToCL"));
+    }
+
+
+    /**
+     * Tests if current class loader is bound to a context.
+     */
+    public static boolean isClassLoaderBound() {
+        ClassLoader cl = Thread.currentThread().getContextClassLoader();
+        do {
+            if (clBindings.containsKey(cl)) {
+                return true;
+            }
+        } while ((cl = cl.getParent()) != null);
+        return false;
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/EjbRef.java b/bundles/org.apache.tomcat/src/org/apache/naming/EjbRef.java
new file mode 100644
index 0000000..a6ee761
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/EjbRef.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+
+package org.apache.naming;
+
+import javax.naming.Context;
+import javax.naming.Reference;
+import javax.naming.StringRefAddr;
+
+/**
+ * Represents a reference address to an EJB.
+ *
+ * @author Remy Maucherat
+ * @version $Id: EjbRef.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+
+public class EjbRef extends Reference {
+
+    private static final long serialVersionUID = 1L;
+
+    
+    // -------------------------------------------------------------- Constants
+    /**
+     * Default factory for this reference.
+     */
+    public static final String DEFAULT_FACTORY = 
+        org.apache.naming.factory.Constants.DEFAULT_EJB_FACTORY;
+
+
+    /**
+     * EJB type address type.
+     */
+    public static final String TYPE = "type";
+
+
+    /**
+     * Remote interface classname address type.
+     */
+    public static final String REMOTE = "remote";
+
+
+    /**
+     * Link address type.
+     */
+    public static final String LINK = "link";
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * EJB Reference.
+     * 
+     * @param ejbType EJB type
+     * @param home Home interface classname
+     * @param remote Remote interface classname
+     * @param link EJB link
+     */
+    public EjbRef(String ejbType, String home, String remote, String link) {
+        this(ejbType, home, remote, link, null, null);
+    }
+
+
+    /**
+     * EJB Reference.
+     * 
+     * @param ejbType EJB type
+     * @param home Home interface classname
+     * @param remote Remote interface classname
+     * @param link EJB link
+     */
+    public EjbRef(String ejbType, String home, String remote, String link,
+                  String factory, String factoryLocation) {
+        super(home, factory, factoryLocation);
+        StringRefAddr refAddr = null;
+        if (ejbType != null) {
+            refAddr = new StringRefAddr(TYPE, ejbType);
+            add(refAddr);
+        }
+        if (remote != null) {
+            refAddr = new StringRefAddr(REMOTE, remote);
+            add(refAddr);
+        }
+        if (link != null) {
+            refAddr = new StringRefAddr(LINK, link);
+            add(refAddr);
+        }
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    // -------------------------------------------------------- RefAddr Methods
+
+
+    // ------------------------------------------------------ Reference Methods
+
+
+    /**
+     * Retrieves the class name of the factory of the object to which this 
+     * reference refers.
+     */
+    @Override
+    public String getFactoryClassName() {
+        String factory = super.getFactoryClassName();
+        if (factory != null) {
+            return factory;
+        } else {
+            factory = System.getProperty(Context.OBJECT_FACTORIES);
+            if (factory != null) {
+                return null;
+            } else {
+                return DEFAULT_FACTORY;
+            }
+        }
+    }
+
+
+    // ------------------------------------------------------------- Properties
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/HandlerRef.java b/bundles/org.apache.tomcat/src/org/apache/naming/HandlerRef.java
new file mode 100644
index 0000000..9425b03
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/HandlerRef.java
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+
+package org.apache.naming;
+
+import java.util.Enumeration;
+
+import javax.naming.Context;
+import javax.naming.RefAddr;
+import javax.naming.Reference;
+import javax.naming.StringRefAddr;
+
+/**
+ * Represents a reference handler for a web service.
+ *
+ * @author Fabien Carrion
+ */
+
+public class HandlerRef extends Reference {
+
+    private static final long serialVersionUID = 1L;
+
+    
+    // -------------------------------------------------------------- Constants
+    /**
+     * Default factory for this reference.
+     */
+    public static final String DEFAULT_FACTORY = 
+        org.apache.naming.factory.Constants.DEFAULT_HANDLER_FACTORY;
+
+
+    /**
+     * HandlerName address type.
+     */
+    public static final String HANDLER_NAME  = "handlername";
+
+
+    /**
+     * Handler Classname address type.
+     */
+    public static final String HANDLER_CLASS  = "handlerclass";
+
+
+    /**
+     * Handler Classname address type.
+     */
+    public static final String HANDLER_LOCALPART  = "handlerlocalpart";
+
+
+    /**
+     * Handler Classname address type.
+     */
+    public static final String HANDLER_NAMESPACE  = "handlernamespace";
+
+
+    /**
+     * Handler Classname address type.
+     */
+    public static final String HANDLER_PARAMNAME  = "handlerparamname";
+
+
+    /**
+     * Handler Classname address type.
+     */
+    public static final String HANDLER_PARAMVALUE  = "handlerparamvalue";
+
+
+    /**
+     * Handler SoapRole address type.
+     */
+    public static final String HANDLER_SOAPROLE  = "handlersoaprole";
+
+
+    /**
+     * Handler PortName address type.
+     */
+    public static final String HANDLER_PORTNAME  = "handlerportname";
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    public HandlerRef(String refname, String handlerClass) {
+        this(refname, handlerClass, null, null);
+    }
+
+
+    public HandlerRef(String refname, String handlerClass,
+                    String factory, String factoryLocation) {
+        super(refname, factory, factoryLocation);
+        StringRefAddr refAddr = null;
+        if (refname != null) {
+            refAddr = new StringRefAddr(HANDLER_NAME, refname);
+            add(refAddr);
+        }
+        if (handlerClass != null) {
+            refAddr = new StringRefAddr(HANDLER_CLASS, handlerClass);
+            add(refAddr);
+        }
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    // ------------------------------------------------------ Reference Methods
+
+
+    /**
+     * Retrieves the class name of the factory of the object to which this 
+     * reference refers.
+     */
+    @Override
+    public String getFactoryClassName() {
+        String factory = super.getFactoryClassName();
+        if (factory != null) {
+            return factory;
+        } else {
+            factory = System.getProperty(Context.OBJECT_FACTORIES);
+            if (factory != null) {
+                return null;
+            } else {
+                return DEFAULT_FACTORY;
+            }
+        }
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return a String rendering of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("HandlerRef[");
+        sb.append("className=");
+        sb.append(getClassName());
+        sb.append(",factoryClassLocation=");
+        sb.append(getFactoryClassLocation());
+        sb.append(",factoryClassName=");
+        sb.append(getFactoryClassName());
+        Enumeration<RefAddr> refAddrs = getAll();
+        while (refAddrs.hasMoreElements()) {
+            RefAddr refAddr = refAddrs.nextElement();
+            sb.append(",{type=");
+            sb.append(refAddr.getType());
+            sb.append(",content=");
+            sb.append(refAddr.getContent());
+            sb.append("}");
+        }
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+    // ------------------------------------------------------------- Properties
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/JndiPermission.java b/bundles/org.apache.tomcat/src/org/apache/naming/JndiPermission.java
new file mode 100644
index 0000000..e14a4a0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/JndiPermission.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.naming;
+
+import java.security.BasicPermission;
+
+/**
+ * Java SecurityManager Permission class for JNDI name based file resources
+ * <p>
+ * The JndiPermission extends the BasicPermission.
+ * The permission name is a full or partial jndi resource name.
+ * An * can be used at the end of the name to match all named
+ * resources that start with name.  There are no actions.</p>
+ * <p>
+ * Example that grants permission to read all JNDI file based resources:
+ * <li> permission org.apache.naming.JndiPermission "*";</li>
+ * </p>
+ *
+ * @author Glenn Nielsen
+ * @version $Id: JndiPermission.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+
+public final class JndiPermission extends BasicPermission {
+
+    private static final long serialVersionUID = 1L;
+
+    // ----------------------------------------------------------- Constructors
+
+    /**
+     * Creates a new JndiPermission with no actions
+     *
+     * @param name - JNDI resource path name
+     */
+    public JndiPermission(String name) {
+        super(name);
+    }
+
+    /**
+     * Creates a new JndiPermission with actions
+     *
+     * @param name - JNDI resource path name
+     * @param actions - JNDI actions (none defined)
+     */
+    public JndiPermission(String name, String actions) {
+        super(name,actions);
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/naming/LocalStrings.properties
new file mode 100644
index 0000000..8731375
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/LocalStrings.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.
+
+contextBindings.unknownContext=Unknown context name : {0}
+contextBindings.noContextBoundToThread=No naming context bound to this thread
+contextBindings.noContextBoundToCL=No naming context bound to this class loader
+selectorContext.noJavaUrl=This context must be accessed through a java: URL
+selectorContext.methodUsingName=Call to method ''{0}'' with a Name of ''{1}''
+selectorContext.methodUsingString=Call to method ''{0}'' with a String of ''{1}''
+namingContext.contextExpected=Name is not bound to a Context
+namingContext.failResolvingReference=Unexpected exception resolving reference
+namingContext.nameNotBound=Name {0} is not bound in this Context
+namingContext.readOnly=Context is read only
+namingContext.invalidName=Name is not valid
+namingContext.alreadyBound=Name {0} is already bound in this Context
+namingContext.noAbsoluteName=Can''t generate an absolute name for this namespace
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/naming/LocalStrings_es.properties
new file mode 100644
index 0000000..ac3ad97
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/LocalStrings_es.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.
+# $Id: LocalStrings_es.properties,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+# language es
+# package org.apache.naming
+contextBindings.unknownContext = Contexto {0} desconocido 
+contextBindings.noContextBoundToThread = No hay contexto de nombres asociado a este hilo
+contextBindings.noContextBoundToCL = No hay contexto de nombres asociado a este cargador de clase
+selectorContext.noJavaUrl = Este contexto debe de ser accedido a traves de una URL de tipo java\:
+namingContext.contextExpected = El nombre no esta asociado a ningun Contexto
+namingContext.failResolvingReference = Excepci\u00F3n inesperada resolviendo referencia
+namingContext.nameNotBound = El nombre {0} no este asociado a este contexto
+namingContext.readOnly = El contexto es de solo lectura
+namingContext.invalidName = Nombre no valido
+namingContext.alreadyBound = El nombre {0} este ya asociado en este Contexto
+namingContext.noAbsoluteName = No se puede generar un nombre absoluto para este espacio de nombres
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/org/apache/naming/LocalStrings_fr.properties
new file mode 100644
index 0000000..e1d1f42
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/LocalStrings_fr.properties
@@ -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.
+
+contextBindings.unknownContext=Nom de Contexte inconnu : {0}
+contextBindings.noContextBoundToThread=Aucun Contexte de nommage li\u00e9 \u00e0 ce thread
+contextBindings.noContextBoundToCL=Aucun Contexte de nommage li\u00e9 \u00e0 ce chargeur de classes
+selectorContext.noJavaUrl=Ce Contexte doit \u00eatre acc\u00e9d\u00e9 par une java: URL
+namingContext.contextExpected=Le Nom n''est pas li\u00e9 \u00e0 un Contexte
+namingContext.failResolvingReference=Une erreur s est produite durant la r\u00e9solution de la r\u00e9f\u00e9rence
+namingContext.nameNotBound=Le Nom {0} n''est pas li\u00e9 \u00e0 ce Contexte
+namingContext.readOnly=Le Contexte est en lecture seule
+namingContext.invalidName=Le Nom est invalide
+namingContext.alreadyBound=Le Nom {0} est d\u00e9j\u00e0 li\u00e9 \u00e0 ce Contexte
+namingContext.noAbsoluteName=Impossible de g\u00e9n\u00e9rer un nom absolu pour cet espace de nommage (namespace)
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/LocalStrings_ja.properties b/bundles/org.apache.tomcat/src/org/apache/naming/LocalStrings_ja.properties
new file mode 100644
index 0000000..f8329f7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/LocalStrings_ja.properties
@@ -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.
+
+contextBindings.unknownContext=\u672a\u77e5\u306e\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u540d\u3067\u3059: {0}
+contextBindings.noContextBoundToThread=\u540d\u524d\u4ed8\u3051\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306f\u3053\u306e\u30b9\u30ec\u30c3\u30c9\u306b\u30d0\u30a4\u30f3\u30c9\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+contextBindings.noContextBoundToCL=\u540d\u524d\u4ed8\u3051\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306f\u3053\u306e\u30af\u30e9\u30b9\u30ed\u30fc\u30c0\u306b\u30d0\u30a4\u30f3\u30c9\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+selectorContext.noJavaUrl=\u3053\u306e\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306b\u306fjava: URL\u3092\u7528\u3044\u3066\u30a2\u30af\u30bb\u30b9\u3055\u308c\u306d\u3070\u3044\u3051\u307e\u305b\u3093
+namingContext.contextExpected=\u540d\u524d\u304c\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306b\u30d0\u30a4\u30f3\u30c9\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+namingContext.failResolvingReference=\u53c2\u7167\u306e\u89e3\u6c7a\u4e2d\u306b\u4e88\u6e2c\u3057\u306a\u3044\u4f8b\u5916\u304c\u767a\u751f\u3057\u307e\u3057\u305f
+namingContext.nameNotBound=\u540d\u524d {0} \u306f\u3053\u306e\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306b\u30d0\u30a4\u30f3\u30c9\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+namingContext.readOnly=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306f\u30ea\u30fc\u30c9\u30aa\u30f3\u30ea\u30fc\u3067\u3059
+namingContext.invalidName=\u540d\u524d\u306f\u7121\u52b9\u3067\u3059
+namingContext.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
+namingContext.noAbsoluteName=\u3053\u306e\u540d\u524d\u7a7a\u9593\u306b\u7d76\u5bfe\u540d\u3092\u751f\u6210\u3067\u304d\u307e\u305b\u3093
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/NameParserImpl.java b/bundles/org.apache.tomcat/src/org/apache/naming/NameParserImpl.java
new file mode 100644
index 0000000..227443e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/NameParserImpl.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.naming;
+
+import javax.naming.CompositeName;
+import javax.naming.Name;
+import javax.naming.NameParser;
+import javax.naming.NamingException;
+
+/**
+ * Parses names.
+ *
+ * @author Remy Maucherat
+ * @version $Id: NameParserImpl.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+
+public class NameParserImpl 
+    implements NameParser {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    // ----------------------------------------------------- NameParser Methods
+
+
+    /**
+     * Parses a name into its components.
+     * 
+     * @param name The non-null string name to parse
+     * @return A non-null parsed form of the name using the naming convention 
+     * of this parser.
+     */
+    @Override
+    public Name parse(String name)
+        throws NamingException {
+        return new CompositeName(name);
+    }
+
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/NamingContext.java b/bundles/org.apache.tomcat/src/org/apache/naming/NamingContext.java
new file mode 100644
index 0000000..dff96da
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/NamingContext.java
@@ -0,0 +1,946 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+
+import javax.naming.Binding;
+import javax.naming.CompositeName;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.LinkRef;
+import javax.naming.Name;
+import javax.naming.NameAlreadyBoundException;
+import javax.naming.NameClassPair;
+import javax.naming.NameNotFoundException;
+import javax.naming.NameParser;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.NotContextException;
+import javax.naming.OperationNotSupportedException;
+import javax.naming.Reference;
+import javax.naming.Referenceable;
+import javax.naming.spi.NamingManager;
+
+/**
+ * Catalina JNDI Context implementation.
+ *
+ * @author Remy Maucherat
+ * @version $Id: NamingContext.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+public class NamingContext implements Context {
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    /**
+     * Name parser for this context.
+     */
+    protected static final NameParser nameParser = new NameParserImpl();
+
+
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog(NamingContext.class);
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Builds a naming context using the given environment.
+     */
+    public NamingContext(Hashtable<String,Object> env, String name) 
+        throws NamingException {
+        this.bindings = new HashMap<String,NamingEntry>();
+        this.env = new Hashtable<String,Object>();
+        // FIXME ? Could be put in the environment ?
+        this.name = name;
+        // Populating the environment hashtable
+        if (env != null ) {
+            Enumeration<String> envEntries = env.keys();
+            while (envEntries.hasMoreElements()) {
+                String entryName = envEntries.nextElement();
+                addToEnvironment(entryName, env.get(entryName));
+            }
+        }
+    }
+
+
+    /**
+     * Builds a naming context using the given environment.
+     */
+    public NamingContext(Hashtable<String,Object> env, String name,
+            HashMap<String,NamingEntry> bindings) 
+        throws NamingException {
+        this(env, name);
+        this.bindings = bindings;
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Environment.
+     */
+    protected Hashtable<String,Object> env;
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm = StringManager.getManager(Constants.Package);
+
+
+    /**
+     * Bindings in this Context.
+     */
+    protected HashMap<String,NamingEntry> bindings;
+
+
+    /**
+     * Name of the associated Catalina Context.
+     */
+    protected String name;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    // -------------------------------------------------------- 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 {
+        return lookup(name, true);
+    }
+
+
+    /**
+     * 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 {
+        return lookup(new CompositeName(name), true);
+    }
+
+
+    /**
+     * 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 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, obj, false);
+    }
+
+
+    /**
+     * 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 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(new CompositeName(name), obj);
+    }
+
+
+    /**
+     * 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 {
+        bind(name, obj, true);
+    }
+
+
+    /**
+     * 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(new CompositeName(name), obj);
+    }
+
+
+    /**
+     * 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 {
+        checkWritable();
+        
+        while ((!name.isEmpty()) && (name.get(0).length() == 0))
+            name = name.getSuffix(1);
+        if (name.isEmpty())
+            throw new NamingException
+                (sm.getString("namingContext.invalidName"));
+        
+        NamingEntry entry = bindings.get(name.get(0));
+        
+        if (entry == null) {
+            throw new NameNotFoundException
+                (sm.getString("namingContext.nameNotBound", name.get(0)));
+        }
+        
+        if (name.size() > 1) {
+            if (entry.type == NamingEntry.CONTEXT) {
+                ((Context) entry.value).unbind(name.getSuffix(1));
+            } else {
+                throw new NamingException
+                    (sm.getString("namingContext.contextExpected"));
+            }
+        } else {
+            bindings.remove(name.get(0));
+        }
+        
+    }
+
+
+    /**
+     * 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 {
+        unbind(new CompositeName(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(Name oldName, Name newName)
+        throws NamingException {
+        Object value = lookup(oldName);
+        bind(newName, value);
+        unbind(oldName);
+    }
+
+
+    /**
+     * 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 NameAlreadyBoundException if newName is already bound
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void rename(String oldName, String newName)
+        throws NamingException {
+        rename(new CompositeName(oldName), new CompositeName(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(Name name)
+        throws NamingException {
+        // Removing empty parts
+        while ((!name.isEmpty()) && (name.get(0).length() == 0))
+            name = name.getSuffix(1);
+        if (name.isEmpty()) {
+            return new NamingContextEnumeration(bindings.values().iterator());
+        }
+        
+        NamingEntry entry = bindings.get(name.get(0));
+        
+        if (entry == null) {
+            throw new NameNotFoundException
+                (sm.getString("namingContext.nameNotBound", name.get(0)));
+        }
+        
+        if (entry.type != NamingEntry.CONTEXT) {
+            throw new NamingException
+                (sm.getString("namingContext.contextExpected"));
+        }
+        return ((Context) entry.value).list(name.getSuffix(1));
+    }
+
+
+    /**
+     * 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 list(new CompositeName(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 {
+        // Removing empty parts
+        while ((!name.isEmpty()) && (name.get(0).length() == 0))
+            name = name.getSuffix(1);
+        if (name.isEmpty()) {
+            return new NamingContextBindingsEnumeration(bindings.values().iterator(), this);
+        }
+        
+        NamingEntry entry = bindings.get(name.get(0));
+        
+        if (entry == null) {
+            throw new NameNotFoundException
+                (sm.getString("namingContext.nameNotBound", name.get(0)));
+        }
+        
+        if (entry.type != NamingEntry.CONTEXT) {
+            throw new NamingException
+                (sm.getString("namingContext.contextExpected"));
+        }
+        return ((Context) entry.value).listBindings(name.getSuffix(1));
+    }
+
+
+    /**
+     * 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 listBindings(new CompositeName(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 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 {
+        
+        checkWritable();
+        
+        while ((!name.isEmpty()) && (name.get(0).length() == 0))
+            name = name.getSuffix(1);
+        if (name.isEmpty())
+            throw new NamingException
+                (sm.getString("namingContext.invalidName"));
+        
+        NamingEntry entry = bindings.get(name.get(0));
+        
+        if (entry == null) {
+            throw new NameNotFoundException
+                (sm.getString("namingContext.nameNotBound", name.get(0)));
+        }
+        
+        if (name.size() > 1) {
+            if (entry.type == NamingEntry.CONTEXT) {
+                ((Context) entry.value).destroySubcontext(name.getSuffix(1));
+            } else {
+                throw new NamingException
+                    (sm.getString("namingContext.contextExpected"));
+            }
+        } else {
+            if (entry.type == NamingEntry.CONTEXT) {
+                ((Context) entry.value).close();
+                bindings.remove(name.get(0));
+            } else {
+                throw new NotContextException
+                    (sm.getString("namingContext.contextExpected"));
+            }
+        }
+        
+    }
+
+
+    /**
+     * 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 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 {
+        destroySubcontext(new CompositeName(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 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 {
+        checkWritable();
+        
+        Context newContext = new NamingContext(env, this.name);
+        bind(name, newContext);
+        
+        return newContext;
+    }
+
+
+    /**
+     * 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 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(new CompositeName(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(Name name)
+        throws NamingException {
+        return lookup(name, false);
+    }
+
+
+    /**
+     * 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 lookup(new CompositeName(name), false);
+    }
+
+
+    /**
+     * 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 {
+
+        while ((!name.isEmpty()) && (name.get(0).length() == 0))
+            name = name.getSuffix(1);
+        if (name.isEmpty())
+            return nameParser;
+
+        if (name.size() > 1) {
+            Object obj = bindings.get(name.get(0));
+            if (obj instanceof Context) {
+                return ((Context) obj).getNameParser(name.getSuffix(1));
+            } else {
+                throw new NotContextException
+                    (sm.getString("namingContext.contextExpected"));
+            }
+        }
+
+        return nameParser;
+
+    }
+
+
+    /**
+     * 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 getNameParser(new CompositeName(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 {
+        prefix = (Name) prefix.clone();
+        return prefix.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<?,?> 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 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 {
+        throw  new OperationNotSupportedException
+            (sm.getString("namingContext.noAbsoluteName"));
+        //FIXME ?
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Retrieves the named object.
+     * 
+     * @param name the name of the object to look up
+     * @param resolveLinks If true, the links will be resolved
+     * @return the object bound to name
+     * @exception NamingException if a naming exception is encountered
+     */
+    protected Object lookup(Name name, boolean resolveLinks)
+        throws NamingException {
+
+        // Removing empty parts
+        while ((!name.isEmpty()) && (name.get(0).length() == 0))
+            name = name.getSuffix(1);
+        if (name.isEmpty()) {
+            // If name is empty, a newly allocated naming context is returned
+            return new NamingContext(env, this.name, bindings);
+        }
+        
+        NamingEntry entry = bindings.get(name.get(0));
+        
+        if (entry == null) {
+            throw new NameNotFoundException
+                (sm.getString("namingContext.nameNotBound", name.get(0)));
+        }
+        
+        if (name.size() > 1) {
+            // If the size of the name is greater that 1, then we go through a
+            // number of subcontexts.
+            if (entry.type != NamingEntry.CONTEXT) {
+                throw new NamingException
+                    (sm.getString("namingContext.contextExpected"));
+            }
+            return ((Context) entry.value).lookup(name.getSuffix(1));
+        } else {
+            if ((resolveLinks) && (entry.type == NamingEntry.LINK_REF)) {
+                String link = ((LinkRef) entry.value).getLinkName();
+                if (link.startsWith(".")) {
+                    // Link relative to this context
+                    return lookup(link.substring(1));
+                } else {
+                    return (new InitialContext(env)).lookup(link);
+                }
+            } else if (entry.type == NamingEntry.REFERENCE) {
+                try {
+                    Object obj = NamingManager.getObjectInstance
+                        (entry.value, name, this, env);
+                    if(entry.value instanceof ResourceRef) {
+                        boolean singleton = Boolean.parseBoolean(
+                                    (String) ((ResourceRef) entry.value).get(
+                                        "singleton").getContent());
+                        if (singleton) {
+                            entry.type = NamingEntry.ENTRY;
+                            entry.value = obj;
+                        }
+                    }
+                    return obj; 
+                } catch (NamingException e) {
+                    throw e;
+                } catch (Exception e) {
+                    log.warn(sm.getString
+                             ("namingContext.failResolvingReference"), e);
+                    throw new NamingException(e.getMessage());
+                }
+            } else {
+                return entry.value;
+            }
+        }
+        
+    }
+
+
+    /**
+     * 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
+     * @param rebind if true, then perform a rebind (ie, overwrite)
+     * @exception 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
+     */
+    protected void bind(Name name, Object obj, boolean rebind)
+        throws NamingException {
+        
+        checkWritable();
+        
+        while ((!name.isEmpty()) && (name.get(0).length() == 0))
+            name = name.getSuffix(1);
+        if (name.isEmpty())
+            throw new NamingException
+                (sm.getString("namingContext.invalidName"));
+        
+        NamingEntry entry = bindings.get(name.get(0));
+        
+        if (name.size() > 1) {
+            if (entry == null) {
+                throw new NameNotFoundException
+                    (sm.getString("namingContext.nameNotBound", name.get(0)));
+            }
+            if (entry.type == NamingEntry.CONTEXT) {
+                if (rebind) {
+                    ((Context) entry.value).rebind(name.getSuffix(1), obj);
+                } else {
+                    ((Context) entry.value).bind(name.getSuffix(1), obj);
+                }
+            } else {
+                throw new NamingException
+                    (sm.getString("namingContext.contextExpected"));
+            }
+        } else {
+            if ((!rebind) && (entry != null)) {
+                throw new NameAlreadyBoundException
+                    (sm.getString("namingContext.alreadyBound", name.get(0)));
+            } else {
+                // Getting the type of the object and wrapping it within a new
+                // NamingEntry
+                Object toBind = 
+                    NamingManager.getStateToBind(obj, name, this, env);
+                if (toBind instanceof Context) {
+                    entry = new NamingEntry(name.get(0), toBind, 
+                                            NamingEntry.CONTEXT);
+                } else if (toBind instanceof LinkRef) {
+                    entry = new NamingEntry(name.get(0), toBind, 
+                                            NamingEntry.LINK_REF);
+                } else if (toBind instanceof Reference) {
+                    entry = new NamingEntry(name.get(0), toBind, 
+                                            NamingEntry.REFERENCE);
+                } else if (toBind instanceof Referenceable) {
+                    toBind = ((Referenceable) toBind).getReference();
+                    entry = new NamingEntry(name.get(0), toBind, 
+                                            NamingEntry.REFERENCE);
+                } else {
+                    entry = new NamingEntry(name.get(0), toBind, 
+                                            NamingEntry.ENTRY);
+                }
+                bindings.put(name.get(0), entry);
+            }
+        }
+        
+    }
+
+
+    /**
+     * Returns true if writing is allowed on this context.
+     */
+    protected boolean isWritable() {
+        return ContextAccessController.isWritable(name);
+    }
+
+
+    /**
+     * Throws a naming exception is Context is not writable.
+     */
+    protected void checkWritable() 
+        throws NamingException {
+        if (!isWritable())
+            throw new NamingException(sm.getString("namingContext.readOnly"));
+    }
+
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/NamingContextBindingsEnumeration.java b/bundles/org.apache.tomcat/src/org/apache/naming/NamingContextBindingsEnumeration.java
new file mode 100644
index 0000000..f03c14f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/NamingContextBindingsEnumeration.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.naming;
+
+import java.util.Iterator;
+
+import javax.naming.Binding;
+import javax.naming.CompositeName;
+import javax.naming.Context;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+
+/**
+ * Naming enumeration implementation.
+ *
+ * @author Remy Maucherat
+ * @version $Id: NamingContextBindingsEnumeration.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+
+public class NamingContextBindingsEnumeration 
+    implements NamingEnumeration<Binding> {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    public NamingContextBindingsEnumeration(Iterator<NamingEntry> entries,
+            Context ctx) {
+        iterator = entries;
+        this.ctx = ctx;
+    }
+
+    // -------------------------------------------------------------- Variables
+
+
+    /**
+     * Underlying enumeration.
+     */
+    protected Iterator<NamingEntry> iterator;
+
+    
+    /**
+     * The context for which this enumeration is being generated.
+     */
+    private Context ctx;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Retrieves the next element in the enumeration.
+     */
+    @Override
+    public Binding next()
+        throws NamingException {
+        return nextElementInternal();
+    }
+
+
+    /**
+     * Determines whether there are any more elements in the enumeration.
+     */
+    @Override
+    public boolean hasMore()
+        throws NamingException {
+        return iterator.hasNext();
+    }
+
+
+    /**
+     * Closes this enumeration.
+     */
+    @Override
+    public void close()
+        throws NamingException {
+    }
+
+
+    @Override
+    public boolean hasMoreElements() {
+        return iterator.hasNext();
+    }
+
+
+    @Override
+    public Binding nextElement() {
+        try {
+            return nextElementInternal();
+        } catch (NamingException e) {
+            throw new RuntimeException(e.getMessage(), e);
+        }
+    }
+    
+    private Binding nextElementInternal() throws NamingException {
+        NamingEntry entry = iterator.next();
+        Object value;
+        
+        // If the entry is a reference, resolve it
+        if (entry.type == NamingEntry.REFERENCE
+                || entry.type == NamingEntry.LINK_REF) {
+            try {
+                value = ctx.lookup(new CompositeName(entry.name));
+            } catch (NamingException e) {
+                throw e;
+            } catch (Exception e) {
+                NamingException ne = new NamingException(e.getMessage());
+                ne.initCause(e);
+                throw ne;
+            }
+        } else {
+            value = entry.value;
+        }
+        
+        return new Binding(entry.name, value.getClass().getName(), value, true);
+    }
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/NamingContextEnumeration.java b/bundles/org.apache.tomcat/src/org/apache/naming/NamingContextEnumeration.java
new file mode 100644
index 0000000..dfd6a7d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/NamingContextEnumeration.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 org.apache.naming;
+
+import java.util.Iterator;
+
+import javax.naming.NameClassPair;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+
+/**
+ * Naming enumeration implementation.
+ *
+ * @author Remy Maucherat
+ * @version $Id: NamingContextEnumeration.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+
+public class NamingContextEnumeration 
+    implements NamingEnumeration<NameClassPair> {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    public NamingContextEnumeration(Iterator<NamingEntry> entries) {
+        iterator = entries;
+    }
+
+
+    // -------------------------------------------------------------- Variables
+
+
+    /**
+     * Underlying enumeration.
+     */
+    protected Iterator<NamingEntry> iterator;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Retrieves the next element in the enumeration.
+     */
+    @Override
+    public NameClassPair next()
+        throws NamingException {
+        return nextElement();
+    }
+
+
+    /**
+     * Determines whether there are any more elements in the enumeration.
+     */
+    @Override
+    public boolean hasMore()
+        throws NamingException {
+        return iterator.hasNext();
+    }
+
+
+    /**
+     * Closes this enumeration.
+     */
+    @Override
+    public void close()
+        throws NamingException {
+    }
+
+
+    @Override
+    public boolean hasMoreElements() {
+        return iterator.hasNext();
+    }
+
+
+    @Override
+    public NameClassPair nextElement() {
+        NamingEntry entry = iterator.next();
+        return new NameClassPair(entry.name, entry.value.getClass().getName());
+    }
+
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/NamingEntry.java b/bundles/org.apache.tomcat/src/org/apache/naming/NamingEntry.java
new file mode 100644
index 0000000..1c76d16
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/NamingEntry.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.naming;
+
+
+/**
+ * Represents a binding in a NamingContext.
+ *
+ * @author Remy Maucherat
+ * @version $Id: NamingEntry.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+
+public class NamingEntry {
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    public static final int ENTRY = 0;
+    public static final int LINK_REF = 1;
+    public static final int REFERENCE = 2;
+    
+    public static final int CONTEXT = 10;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    public NamingEntry(String name, Object value, int type) {
+        this.name = name;
+        this.value = value;
+        this.type = type;
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The type instance variable is used to avoid using RTTI when doing
+     * lookups.
+     */
+    public int type;
+    public String name;
+    public Object value;
+
+
+    // --------------------------------------------------------- Object Methods
+
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof NamingEntry) {
+            return name.equals(((NamingEntry) obj).name);
+        } else {
+            return false;
+        }
+    }
+
+
+    @Override
+    public int hashCode() {
+        return name.hashCode();
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/ResourceEnvRef.java b/bundles/org.apache.tomcat/src/org/apache/naming/ResourceEnvRef.java
new file mode 100644
index 0000000..3263416
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/ResourceEnvRef.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import javax.naming.Context;
+import javax.naming.Reference;
+
+/**
+ * Represents a reference address to a resource environment.
+ *
+ * @author Remy Maucherat
+ * @version $Id: ResourceEnvRef.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+
+public class ResourceEnvRef extends Reference {
+
+    private static final long serialVersionUID = 1L;
+
+    // -------------------------------------------------------------- Constants
+
+    /**
+     * Default factory for this reference.
+     */
+    public static final String DEFAULT_FACTORY = 
+        org.apache.naming.factory.Constants.DEFAULT_RESOURCE_ENV_FACTORY;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Resource env reference.
+     * 
+     * @param resourceType Type
+     */
+    public ResourceEnvRef(String resourceType) {
+        super(resourceType);
+    }
+
+
+    /**
+     * Resource env reference.
+     * 
+     * @param resourceType Type
+     * @param factory The factory class
+     * @param factoryLocation The factory location
+     */
+    public ResourceEnvRef(String resourceType, String factory,
+                          String factoryLocation) {
+        super(resourceType, factory, factoryLocation);
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    // ------------------------------------------------------ Reference Methods
+
+
+    /**
+     * Retrieves the class name of the factory of the object to which this 
+     * reference refers.
+     */
+    @Override
+    public String getFactoryClassName() {
+        String factory = super.getFactoryClassName();
+        if (factory != null) {
+            return factory;
+        } else {
+            factory = System.getProperty(Context.OBJECT_FACTORIES);
+            if (factory != null) {
+                return null;
+            } else {
+                return DEFAULT_FACTORY;
+            }
+        }
+    }
+
+
+    // ------------------------------------------------------------- Properties
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/ResourceLinkRef.java b/bundles/org.apache.tomcat/src/org/apache/naming/ResourceLinkRef.java
new file mode 100644
index 0000000..c20c859
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/ResourceLinkRef.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.naming;
+
+import javax.naming.Context;
+import javax.naming.Reference;
+import javax.naming.StringRefAddr;
+
+/**
+ * Represents a reference address to a resource.
+ *
+ * @author Remy Maucherat
+ * @version $Id: ResourceLinkRef.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+
+public class ResourceLinkRef extends Reference {
+
+    private static final long serialVersionUID = 1L;
+
+    // -------------------------------------------------------------- Constants
+
+    /**
+     * Default factory for this reference.
+     */
+    public static final String DEFAULT_FACTORY = 
+        org.apache.naming.factory.Constants.DEFAULT_RESOURCE_LINK_FACTORY;
+
+
+    /**
+     * Description address type.
+     */
+    public static final String GLOBALNAME = "globalName";
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * ResourceLink Reference.
+     * 
+     * @param resourceClass Resource class
+     * @param globalName Global name
+     */
+    public ResourceLinkRef(String resourceClass, String globalName) {
+        this(resourceClass, globalName, null, null);
+    }
+
+
+    /**
+     * ResourceLink Reference.
+     * 
+     * @param resourceClass Resource class
+     * @param globalName Global name
+     */
+    public ResourceLinkRef(String resourceClass, String globalName, 
+                           String factory, String factoryLocation) {
+        super(resourceClass, factory, factoryLocation);
+        StringRefAddr refAddr = null;
+        if (globalName != null) {
+            refAddr = new StringRefAddr(GLOBALNAME, globalName);
+            add(refAddr);
+        }
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    // ------------------------------------------------------ Reference Methods
+
+
+    /**
+     * Retrieves the class name of the factory of the object to which this 
+     * reference refers.
+     */
+    @Override
+    public String getFactoryClassName() {
+        String factory = super.getFactoryClassName();
+        if (factory != null) {
+            return factory;
+        } else {
+            factory = System.getProperty(Context.OBJECT_FACTORIES);
+            if (factory != null) {
+                return null;
+            } else {
+                return DEFAULT_FACTORY;
+            }
+        }
+    }
+
+
+    // ------------------------------------------------------------- Properties
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/ResourceRef.java b/bundles/org.apache.tomcat/src/org/apache/naming/ResourceRef.java
new file mode 100644
index 0000000..a4cc8c9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/ResourceRef.java
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+
+package org.apache.naming;
+
+import java.util.Enumeration;
+
+import javax.naming.Context;
+import javax.naming.RefAddr;
+import javax.naming.Reference;
+import javax.naming.StringRefAddr;
+
+/**
+ * Represents a reference address to a resource.
+ *
+ * @author Remy Maucherat
+ * @version $Id: ResourceRef.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+
+public class ResourceRef extends Reference {
+
+    private static final long serialVersionUID = 1L;
+
+    
+    // -------------------------------------------------------------- Constants
+
+    /**
+     * Default factory for this reference.
+     */
+    public static final String DEFAULT_FACTORY = 
+        org.apache.naming.factory.Constants.DEFAULT_RESOURCE_FACTORY;
+
+
+    /**
+     * Description address type.
+     */
+    public static final String DESCRIPTION = "description";
+
+
+    /**
+     * Scope address type.
+     */
+    public static final String SCOPE = "scope";
+
+
+    /**
+     * Auth address type.
+     */
+    public static final String AUTH = "auth";
+
+
+    /**
+     * Is this resource a singleton
+     */
+    public static final String SINGLETON = "singleton";
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Resource Reference.
+     * 
+     * @param resourceClass Resource class
+     * @param scope Resource scope
+     * @param auth Resource authentication
+     */
+    public ResourceRef(String resourceClass, String description, 
+                       String scope, String auth, boolean singleton) {
+        this(resourceClass, description, scope, auth, singleton, null, null);
+    }
+
+
+    /**
+     * Resource Reference.
+     * 
+     * @param resourceClass Resource class
+     * @param scope Resource scope
+     * @param auth Resource authentication
+     */
+    public ResourceRef(String resourceClass, String description, 
+                       String scope, String auth, boolean singleton,
+                       String factory, String factoryLocation) {
+        super(resourceClass, factory, factoryLocation);
+        StringRefAddr refAddr = null;
+        if (description != null) {
+            refAddr = new StringRefAddr(DESCRIPTION, description);
+            add(refAddr);
+        }
+        if (scope != null) {
+            refAddr = new StringRefAddr(SCOPE, scope);
+            add(refAddr);
+        }
+        if (auth != null) {
+            refAddr = new StringRefAddr(AUTH, auth);
+            add(refAddr);
+        }
+        // singleton is a boolean so slightly different handling
+        refAddr = new StringRefAddr(SINGLETON, Boolean.toString(singleton));
+        add(refAddr);
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    // ------------------------------------------------------ Reference Methods
+
+
+    /**
+     * Retrieves the class name of the factory of the object to which this 
+     * reference refers.
+     */
+    @Override
+    public String getFactoryClassName() {
+        String factory = super.getFactoryClassName();
+        if (factory != null) {
+            return factory;
+        } else {
+            factory = System.getProperty(Context.OBJECT_FACTORIES);
+            if (factory != null) {
+                return null;
+            } else {
+                return DEFAULT_FACTORY;
+            }
+        }
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return a String rendering of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("ResourceRef[");
+        sb.append("className=");
+        sb.append(getClassName());
+        sb.append(",factoryClassLocation=");
+        sb.append(getFactoryClassLocation());
+        sb.append(",factoryClassName=");
+        sb.append(getFactoryClassName());
+        Enumeration<RefAddr> refAddrs = getAll();
+        while (refAddrs.hasMoreElements()) {
+            RefAddr refAddr = refAddrs.nextElement();
+            sb.append(",{type=");
+            sb.append(refAddr.getType());
+            sb.append(",content=");
+            sb.append(refAddr.getContent());
+            sb.append("}");
+        }
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+    // ------------------------------------------------------------- Properties
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/SelectorContext.java b/bundles/org.apache.tomcat/src/org/apache/naming/SelectorContext.java
new file mode 100644
index 0000000..a4906c9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/SelectorContext.java
@@ -0,0 +1,785 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import java.util.Hashtable;
+
+import javax.naming.Binding;
+import javax.naming.Context;
+import javax.naming.Name;
+import javax.naming.NameClassPair;
+import javax.naming.NameParser;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+
+/**
+ * Catalina JNDI Context implementation.
+ *
+ * @author Remy Maucherat
+ * @version $Id: SelectorContext.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+
+public class SelectorContext implements Context {
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    /**
+     * Namespace URL.
+     */
+    public static final String prefix = "java:";
+
+
+    /**
+     * Namespace URL length.
+     */
+    public static final int prefixLength = prefix.length();
+
+
+    /**
+     * Initial context prefix.
+     */
+    public static final String IC_PREFIX = "IC_";
+
+
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog(SelectorContext.class);
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Builds a Catalina selector context using the given environment.
+     */
+    public SelectorContext(Hashtable<String,Object> env) {
+        this.env = env;
+    }
+
+
+    /**
+     * Builds a Catalina selector context using the given environment.
+     */
+    public SelectorContext(Hashtable<String,Object> env,
+            boolean initialContext) {
+        this(env);
+        this.initialContext = initialContext;
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Environment.
+     */
+    protected Hashtable<String,Object> env;
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm = StringManager.getManager(Constants.Package);
+
+
+    /**
+     * Request for an initial context.
+     */
+    protected boolean initialContext = false;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    // -------------------------------------------------------- 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 {
+        
+        if (log.isDebugEnabled()) {
+            log.debug(sm.getString("selectorContext.methodUsingName", "lookup",
+                    name));
+        }
+
+        // Strip the URL header
+        // Find the appropriate NamingContext according to the current bindings
+        // Execute the lookup on that context
+        return getBoundContext().lookup(parseName(name));
+    }
+
+
+    /**
+     * 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 {
+
+        if (log.isDebugEnabled()) {
+            log.debug(sm.getString("selectorContext.methodUsingString", "lookup",
+                    name));
+        }
+
+        // Strip the URL header
+        // Find the appropriate NamingContext according to the current bindings
+        // Execute the lookup on that context
+        return getBoundContext().lookup(parseName(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.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 {
+        getBoundContext().bind(parseName(name), 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.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 {
+        getBoundContext().bind(parseName(name), obj);
+    }
+
+
+    /**
+     * 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.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 {
+        getBoundContext().rebind(parseName(name), 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.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 {
+        getBoundContext().rebind(parseName(name), obj);
+    }
+
+
+    /**
+     * 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 javax.naming NameNotFoundException if an intermediate context
+     * does not exist
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void unbind(Name name)
+        throws NamingException {
+        getBoundContext().unbind(parseName(name));
+    }
+
+
+    /**
+     * Unbinds the named object.
+     * 
+     * @param name the name to bind; may not be empty
+     * @exception javax.naming NameNotFoundException if an intermediate context
+     * does not exist
+     * @exception NamingException if a naming exception is encountered
+     */
+    @Override
+    public void unbind(String name)
+        throws NamingException {
+        getBoundContext().unbind(parseName(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 {
+        getBoundContext().rename(parseName(oldName), parseName(newName));
+    }
+
+
+    /**
+     * 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 {
+        getBoundContext().rename(parseName(oldName), parseName(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(Name name)
+        throws NamingException {
+        
+        if (log.isDebugEnabled()) {
+            log.debug(sm.getString("selectorContext.methodUsingName", "list",
+                    name));
+        }
+
+        return getBoundContext().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 {
+        
+        if (log.isDebugEnabled()) {
+            log.debug(sm.getString("selectorContext.methodUsingString", "list",
+                    name));
+        }
+
+        return getBoundContext().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 {
+
+        if (log.isDebugEnabled()) {
+            log.debug(sm.getString("selectorContext.methodUsingName",
+                    "listBindings", name));
+        }
+
+        return getBoundContext().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 {
+
+        if (log.isDebugEnabled()) {
+            log.debug(sm.getString("selectorContext.methodUsingString",
+                    "listBindings", name));
+        }
+
+        return getBoundContext().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 javax.naming 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 {
+        getBoundContext().destroySubcontext(parseName(name));
+    }
+
+
+    /**
+     * 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 javax.naming 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 {
+        getBoundContext().destroySubcontext(parseName(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.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 getBoundContext().createSubcontext(parseName(name));
+    }
+
+
+    /**
+     * 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.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 getBoundContext().createSubcontext(parseName(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(Name name)
+        throws NamingException {
+
+        if (log.isDebugEnabled()) {
+            log.debug(sm.getString("selectorContext.methodUsingName",
+                    "lookupLink", name));
+        }
+
+        return getBoundContext().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 {
+
+        if (log.isDebugEnabled()) {
+            log.debug(sm.getString("selectorContext.methodUsingString",
+                    "lookupLink", name));
+        }
+
+        return getBoundContext().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 getBoundContext().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 getBoundContext().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 getBoundContext().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 getBoundContext().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 getBoundContext().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 {
+        getBoundContext().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 prefix;
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Get the bound context.
+     */
+    protected Context getBoundContext()
+        throws NamingException {
+
+        if (initialContext) {
+            String ICName = IC_PREFIX;
+            if (ContextBindings.isThreadBound()) {
+                ICName += ContextBindings.getThreadName();
+            } else if (ContextBindings.isClassLoaderBound()) {
+                ICName += ContextBindings.getClassLoaderName();
+            }
+            Context initialContext = ContextBindings.getContext(ICName);
+            if (initialContext == null) {
+                // Allocating a new context and binding it to the appropriate 
+                // name
+                initialContext = new NamingContext(env, ICName);
+                ContextBindings.bindContext(ICName, initialContext);
+            }
+            return initialContext;
+        } else {
+            if (ContextBindings.isThreadBound()) {
+                return ContextBindings.getThread();
+            } else {
+                return ContextBindings.getClassLoader();
+            }
+        }
+
+    }
+
+
+    /**
+     * Strips the URL header.
+     * 
+     * @return the parsed name
+     * @exception NamingException if there is no "java:" header or if no 
+     * naming context has been bound to this thread
+     */
+    protected String parseName(String name) 
+        throws NamingException {
+        
+        if ((!initialContext) && (name.startsWith(prefix))) {
+            return (name.substring(prefixLength));
+        } else {
+            if (initialContext) {
+                return (name);
+            } else {
+                throw new NamingException
+                    (sm.getString("selectorContext.noJavaUrl"));
+            }
+        }
+        
+    }
+
+
+    /**
+     * Strips the URL header.
+     * 
+     * @return the parsed name
+     * @exception NamingException if there is no "java:" header or if no 
+     * naming context has been bound to this thread
+     */
+    protected Name parseName(Name name) 
+        throws NamingException {
+
+        if ((!initialContext) && (!name.isEmpty()) 
+            && (name.get(0).equals(prefix))) {
+            return (name.getSuffix(1));
+        } else {
+            if (initialContext) {
+                return (name);
+            } else {
+                throw new NamingException
+                    (sm.getString("selectorContext.noJavaUrl"));
+            }
+        }
+
+    }
+
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/ServiceRef.java b/bundles/org.apache.tomcat/src/org/apache/naming/ServiceRef.java
new file mode 100644
index 0000000..8835e51
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/ServiceRef.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.naming;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+import javax.naming.Context;
+import javax.naming.RefAddr;
+import javax.naming.Reference;
+import javax.naming.StringRefAddr;
+
+/**
+ * Represents a reference web service.
+ *
+ * @author Fabien Carrion
+ */
+
+public class ServiceRef extends Reference {
+
+    private static final long serialVersionUID = 1L;
+
+    // -------------------------------------------------------------- Constants
+
+    /**
+     * Default factory for this reference.
+     */
+    public static final String DEFAULT_FACTORY = 
+        org.apache.naming.factory.Constants.DEFAULT_SERVICE_FACTORY;
+
+
+    /**
+     * Service Classname address type.
+     */
+    public static final String SERVICE_INTERFACE  = "serviceInterface";
+
+
+    /**
+     * ServiceQname address type.
+     */
+    public static final String SERVICE_NAMESPACE  = "service namespace";
+    public static final String SERVICE_LOCAL_PART = "service local part";
+
+
+    /**
+     * Wsdl Location address type.
+     */
+    public static final String WSDL      = "wsdl";
+
+
+    /**
+     * Jaxrpcmapping address type.
+     */
+    public static final String JAXRPCMAPPING = "jaxrpcmapping";
+
+
+    /**
+     * port-component-ref/port-component-link address type.
+     */
+    public static final String PORTCOMPONENTLINK = "portcomponentlink";
+
+
+    /**
+     * port-component-ref/service-endpoint-interface address type.
+     */
+    public static final String SERVICEENDPOINTINTERFACE = "serviceendpointinterface";
+
+
+    /**
+     * The vector to save the handler Reference objects, because they can't be saved in the addrs vector.
+     */
+    private Vector<HandlerRef> handlers = new Vector<HandlerRef>();
+
+
+    // ----------------------------------------------------------- Constructors
+
+    public ServiceRef(String refname, String serviceInterface, String[] serviceQname, 
+                       String wsdl, String jaxrpcmapping) {
+        this(refname, serviceInterface, serviceQname, wsdl, jaxrpcmapping,
+                        null, null);
+    }
+
+    public ServiceRef(@SuppressWarnings("unused") String refname,
+                       String serviceInterface, String[] serviceQname, 
+                       String wsdl, String jaxrpcmapping,
+                       String factory, String factoryLocation) {
+        super(serviceInterface, factory, factoryLocation);
+        StringRefAddr refAddr = null;
+        if (serviceInterface != null) {
+            refAddr = new StringRefAddr(SERVICE_INTERFACE, serviceInterface);
+            add(refAddr);
+        }
+        if (serviceQname[0] != null) {
+            refAddr = new StringRefAddr(SERVICE_NAMESPACE, serviceQname[0]);
+            add(refAddr);
+        }
+        if (serviceQname[1] != null) {
+            refAddr = new StringRefAddr(SERVICE_LOCAL_PART, serviceQname[1]);
+            add(refAddr);
+        }
+        if (wsdl != null) {
+            refAddr = new StringRefAddr(WSDL, wsdl);
+            add(refAddr);
+        }
+        if (jaxrpcmapping != null) {
+            refAddr = new StringRefAddr(JAXRPCMAPPING, jaxrpcmapping);
+            add(refAddr);
+        }
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    // ------------------------------------------------------ Reference Methods
+
+
+    /**
+     * Add and Get Handlers classes.
+     */
+    public HandlerRef getHandler() {
+        return handlers.remove(0);
+    }
+
+
+    public int getHandlersSize() {
+        return handlers.size();
+    }
+
+
+    public void addHandler(HandlerRef handler) {
+        handlers.add(handler);
+    }
+
+
+    /**
+     * Retrieves the class name of the factory of the object to which this 
+     * reference refers.
+     */
+    @Override
+    public String getFactoryClassName() {
+        String factory = super.getFactoryClassName();
+        if (factory != null) {
+            return factory;
+        } else {
+            factory = System.getProperty(Context.OBJECT_FACTORIES);
+            if (factory != null) {
+                return null;
+            } else {
+                return DEFAULT_FACTORY;
+            }
+        }
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Return a String rendering of this object.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("ServiceRef[");
+        sb.append("className=");
+        sb.append(getClassName());
+        sb.append(",factoryClassLocation=");
+        sb.append(getFactoryClassLocation());
+        sb.append(",factoryClassName=");
+        sb.append(getFactoryClassName());
+        Enumeration<RefAddr> refAddrs = getAll();
+        while (refAddrs.hasMoreElements()) {
+            RefAddr refAddr = refAddrs.nextElement();
+            sb.append(",{type=");
+            sb.append(refAddr.getType());
+            sb.append(",content=");
+            sb.append(refAddr.getContent());
+            sb.append("}");
+        }
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+    // ------------------------------------------------------------- Properties
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/StringManager.java b/bundles/org.apache.tomcat/src/org/apache/naming/StringManager.java
new file mode 100644
index 0000000..5bb032b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/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.naming;
+
+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:08:14 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/naming/TransactionRef.java b/bundles/org.apache.tomcat/src/org/apache/naming/TransactionRef.java
new file mode 100644
index 0000000..70a7fa2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/TransactionRef.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+
+package org.apache.naming;
+
+import javax.naming.Context;
+import javax.naming.Reference;
+
+/**
+ * Represents a reference address to a transaction.
+ *
+ * @author Remy Maucherat
+ * @version $Id: TransactionRef.java,v 1.1 2011/06/28 21:08:14 rherrmann Exp $
+ */
+
+public class TransactionRef extends Reference {
+
+    private static final long serialVersionUID = 1L;
+
+    // -------------------------------------------------------------- Constants
+
+    /**
+     * Default factory for this reference.
+     */
+    public static final String DEFAULT_FACTORY = 
+        org.apache.naming.factory.Constants.DEFAULT_TRANSACTION_FACTORY;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Resource Reference.
+     */
+    public TransactionRef() {
+        this(null, null);
+    }
+
+
+    /**
+     * Resource Reference.
+     *
+     * @param factory The factory class
+     * @param factoryLocation The factory location
+     */
+    public TransactionRef(String factory, String factoryLocation) {
+        super("javax.transaction.UserTransaction", factory, factoryLocation);
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    // ------------------------------------------------------ Reference Methods
+
+
+    /**
+     * Retrieves the class name of the factory of the object to which this 
+     * reference refers.
+     */
+    @Override
+    public String getFactoryClassName() {
+        String factory = super.getFactoryClassName();
+        if (factory != null) {
+            return factory;
+        } else {
+            factory = System.getProperty(Context.OBJECT_FACTORIES);
+            if (factory != null) {
+                return null;
+            } else {
+                return DEFAULT_FACTORY;
+            }
+        }
+    }
+
+
+    // ------------------------------------------------------------- Properties
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/java/javaURLContextFactory.java b/bundles/org.apache.tomcat/src/org/apache/naming/java/javaURLContextFactory.java
new file mode 100644
index 0000000..efc53a8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/java/javaURLContextFactory.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.naming.java;
+
+import java.util.Hashtable;
+
+import javax.naming.Context;
+import javax.naming.Name;
+import javax.naming.NamingException;
+import javax.naming.spi.InitialContextFactory;
+import javax.naming.spi.ObjectFactory;
+
+import org.apache.naming.ContextBindings;
+import org.apache.naming.NamingContext;
+import org.apache.naming.SelectorContext;
+
+/**
+ * Context factory for the "java:" namespace.
+ * <p>
+ * <b>Important note</b> : This factory MUST be associated with the "java" URL
+ * prefix, which can be done by either :
+ * <ul>
+ * <li>Adding a 
+ * java.naming.factory.url.pkgs=org.apache.catalina.util.naming property
+ * to the JNDI properties file</li>
+ * <li>Setting an environment variable named Context.URL_PKG_PREFIXES with 
+ * its value including the org.apache.catalina.util.naming package name. 
+ * More detail about this can be found in the JNDI documentation : 
+ * {@link javax.naming.spi.NamingManager#getURLContext(java.lang.String, java.util.Hashtable)}.</li>
+ * </ul>
+ * 
+ * @author Remy Maucherat
+ * @version $Id: javaURLContextFactory.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ */
+
+public class javaURLContextFactory
+    implements ObjectFactory, InitialContextFactory {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    // -------------------------------------------------------------- Constants
+
+
+    public static final String MAIN = "initialContext";
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Initial context.
+     */
+    protected static volatile Context initialContext = null;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    // -------------------------------------------------- ObjectFactory Methods
+
+
+    /**
+     * Crete a new Context's instance.
+     */
+    @SuppressWarnings("unchecked")
+    @Override
+    public Object getObjectInstance(Object obj, Name name, Context nameCtx,
+                                    Hashtable<?,?> environment)
+        throws NamingException {
+        if ((ContextBindings.isThreadBound()) || 
+            (ContextBindings.isClassLoaderBound())) {
+            return new SelectorContext((Hashtable<String,Object>)environment);
+        }
+        return null;
+    }
+
+
+    /**
+     * Get a new (writable) initial context.
+     */
+    @SuppressWarnings("unchecked")
+    @Override
+    public Context getInitialContext(Hashtable<?,?> environment)
+        throws NamingException {
+        if (ContextBindings.isThreadBound() || 
+            (ContextBindings.isClassLoaderBound())) {
+            // Redirect the request to the bound initial context
+            return new SelectorContext(
+                    (Hashtable<String,Object>)environment, true);
+        }
+
+        // If the thread is not bound, return a shared writable context
+        if (initialContext == null) {
+            synchronized(javaURLContextFactory.class) {
+                if (initialContext == null) {
+                    initialContext = new NamingContext(
+                            (Hashtable<String,Object>)environment, MAIN);
+                }
+            }
+        }
+        return initialContext;
+    }
+
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/java/package.html b/bundles/org.apache.tomcat/src/org/apache/naming/java/package.html
new file mode 100644
index 0000000..d3fcce1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/java/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 the URL context factory for the "java" namespace.</p>
+
+<p></p>
+
+</body>
diff --git a/bundles/org.apache.tomcat/src/org/apache/naming/package.html b/bundles/org.apache.tomcat/src/org/apache/naming/package.html
new file mode 100644
index 0000000..798fb28
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/naming/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 a memory based naming service provider.</p>
+
+<p></p>
+
+</body>
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/InstanceManager.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/InstanceManager.java
new file mode 100644
index 0000000..8ec468d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/InstanceManager.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 org.apache.tomcat;
+
+import java.lang.reflect.InvocationTargetException;
+
+import javax.naming.NamingException;
+
+/**
+ * @version $Id: InstanceManager.java,v 1.1 2011/06/28 21:08:25 rherrmann Exp $
+ */
+public interface InstanceManager {
+
+    public Object newInstance(String className)
+        throws IllegalAccessException, InvocationTargetException, NamingException, 
+            InstantiationException, ClassNotFoundException;
+
+    public Object newInstance(String fqcn, ClassLoader classLoader) 
+        throws IllegalAccessException, InvocationTargetException, NamingException, 
+            InstantiationException, ClassNotFoundException;
+
+    public void newInstance(Object o) 
+        throws IllegalAccessException, InvocationTargetException, NamingException;
+
+    public void destroyInstance(Object o)
+        throws IllegalAccessException, InvocationTargetException;
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/JarScanner.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/JarScanner.java
new file mode 100644
index 0000000..620bb02
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/JarScanner.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;
+
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+
+/**
+ * Scans a web application and classloader hierarchy for JAR files. Uses
+ * include TLD scanning and web-fragment.xml scanning. Uses a call-back
+ * mechanism so the caller can process each JAR found. 
+ */
+public interface JarScanner {
+
+    /**
+     * Scan the provided ServletContext and classloader for JAR files. Each JAR
+     * file found will be passed to the callback handler to be processed.
+     *  
+     * @param context       The ServletContext - used to locate and access
+     *                      WEB-INF/lib
+     * @param classloader   The classloader - used to access JARs not in
+     *                      WEB-INF/lib
+     * @param callback      The handler to process any JARs found
+     * @param jarsToSkip    List of JARs to ignore
+     */
+    public void scan(ServletContext context, ClassLoader classloader,
+            JarScannerCallback callback, Set<String> jarsToSkip);
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/JarScannerCallback.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/JarScannerCallback.java
new file mode 100644
index 0000000..478ba68
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/JarScannerCallback.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.tomcat;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.JarURLConnection;
+
+public interface JarScannerCallback {
+
+    /**
+     * 
+     * @param urlConn
+     * @throws IOException
+     */
+    public void scan(JarURLConnection urlConn) throws IOException;
+    
+    public void scan(File file) throws IOException ;
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/PeriodicEventListener.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/PeriodicEventListener.java
new file mode 100644
index 0000000..2d8c63c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/PeriodicEventListener.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.tomcat;
+
+public interface PeriodicEventListener {
+    /**
+     * Execute a periodic task, such as reloading, etc.
+     */
+    public void periodicEvent();
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Address.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Address.java
new file mode 100644
index 0000000..4f87672
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Address.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.tomcat.jni;
+
+/** Address
+ *
+ * @author Mladen Turk
+ * @version $Id: Address.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public class Address {
+
+    public static final String APR_ANYADDR = "0.0.0.0";
+    /**
+     * Fill the Sockaddr class from apr_sockaddr_t
+     * @param info Sockaddr class to fill
+     * @param sa Structure pointer
+     */
+    public static native boolean fill(Sockaddr info, long sa);
+
+    /**
+     * Create the Sockaddr object from apr_sockaddr_t
+     * @param sa Structure pointer
+     */
+    public static native Sockaddr getInfo(long sa);
+
+    /**
+     * Create apr_sockaddr_t from hostname, address family, and port.
+     * @param hostname The hostname or numeric address string to resolve/parse, or
+     *               NULL to build an address that corresponds to 0.0.0.0 or ::
+     * @param family The address family to use, or APR_UNSPEC if the system should
+     *               decide.
+     * @param port The port number.
+     * @param flags Special processing flags:
+     * <PRE>
+     *       APR_IPV4_ADDR_OK          first query for IPv4 addresses; only look
+     *                                 for IPv6 addresses if the first query failed;
+     *                                 only valid if family is APR_UNSPEC and hostname
+     *                                 isn't NULL; mutually exclusive with
+     *                                 APR_IPV6_ADDR_OK
+     *       APR_IPV6_ADDR_OK          first query for IPv6 addresses; only look
+     *                                 for IPv4 addresses if the first query failed;
+     *                                 only valid if family is APR_UNSPEC and hostname
+     *                                 isn't NULL and APR_HAVE_IPV6; mutually exclusive
+     *                                 with APR_IPV4_ADDR_OK
+     * </PRE>
+     * @param p The pool for the apr_sockaddr_t and associated storage.
+     * @return The new apr_sockaddr_t.
+     */
+    public static native long info(String hostname, int family,
+                                   int port, int flags, long p)
+        throws Exception;
+    /**
+     * Look up the host name from an apr_sockaddr_t.
+     * @param sa The apr_sockaddr_t.
+     * @param flags Special processing flags.
+     * @return The hostname.
+     */
+    public static native String getnameinfo(long sa, int flags);
+
+    /**
+     * Return the IP address (in numeric address string format) in
+     * an APR socket address.  APR will allocate storage for the IP address
+     * string from the pool of the apr_sockaddr_t.
+     * @param sa The socket address to reference.
+     * @return The IP address.
+     */
+    public static native String getip(long sa);
+
+    /**
+     * Given an apr_sockaddr_t and a service name, set the port for the service
+     * @param sockaddr The apr_sockaddr_t that will have its port set
+     * @param servname The name of the service you wish to use
+     * @return APR status code.
+     */
+    public static native int getservbyname(long sockaddr, String servname);
+
+    /**
+     * Return an apr_sockaddr_t from an apr_socket_t
+     * @param which Which interface do we want the apr_sockaddr_t for?
+     * @param sock The socket to use
+     * @return The returned apr_sockaddr_t.
+     */
+    public static native long get(int which, long sock)
+        throws Exception;
+
+    /**
+     * See if the IP addresses in two APR socket addresses are
+     * equivalent.  Appropriate logic is present for comparing
+     * IPv4-mapped IPv6 addresses with IPv4 addresses.
+     *
+     * @param a One of the APR socket addresses.
+     * @param b The other APR socket address.
+     * The return value will be True if the addresses
+     * are equivalent.
+     */
+    public static native boolean equal(long a, long b);
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/BIOCallback.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/BIOCallback.java
new file mode 100644
index 0000000..8290b42
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/BIOCallback.java
@@ -0,0 +1,56 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.jni;
+
+/** Open SSL BIO Callback Interface
+ *
+ * @author Mladen Turk
+ * @version $Id: BIOCallback.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public interface BIOCallback {
+
+    /**
+     * Write data
+     * @param buf containing the bytes to write.
+     * @return Number of characters written.
+     */
+    public int write(byte [] buf);
+
+    /**
+     * Read data
+     * @param buf buffer to store the read bytes.
+     * @return number of bytes read.
+     */
+    public int read(byte [] buf);
+
+    /**
+     * Puts string
+     * @param data String to write
+     * @return Number of characters written
+     */
+    public int puts(String data);
+
+    /**
+     * Read string up to the len or CLRLF
+     * @param len Maximum number of characters to read
+     * @return String with up to len bytes read
+     */
+    public String gets(int len);
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Directory.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Directory.java
new file mode 100644
index 0000000..a593e27
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Directory.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.jni;
+
+/** Directory
+ *
+ * @author Mladen Turk
+ * @version $Id: Directory.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public class Directory {
+
+    /**
+     * Create a new directory on the file system.
+     * @param path the path for the directory to be created. (use / on all systems)
+     * @param perm Permissions for the new directory.
+     * @param pool the pool to use.
+     */
+    public static native int make(String path, int perm, long pool);
+
+    /** Creates a new directory on the file system, but behaves like
+     * 'mkdir -p'. Creates intermediate directories as required. No error
+     * will be reported if PATH already exists.
+     * @param path the path for the directory to be created. (use / on all systems)
+     * @param perm Permissions for the new directory.
+     * @param pool the pool to use.
+     */
+    public static native int makeRecursive(String path, int perm, long pool);
+
+    /**
+     * Remove directory from the file system.
+     * @param path the path for the directory to be removed. (use / on all systems)
+     * @param pool the pool to use.
+     */
+    public static native int remove(String path, long pool);
+
+    /**
+     * Find an existing directory suitable as a temporary storage location.
+     * @param pool The pool to use for any necessary allocations.
+     * @return The temp directory.
+     * 
+     * This function uses an algorithm to search for a directory that an
+     * an application can use for temporary storage.  Once such a
+     * directory is found, that location is cached by the library.  Thus,
+     * callers only pay the cost of this algorithm once if that one time
+     * is successful.
+     *
+     */
+    public static native String tempGet(long pool);
+
+    /**
+     * Open the specified directory.
+     * @param dirname The full path to the directory (use / on all systems)
+     * @param pool The pool to use.
+     * @return The opened directory descriptor.
+     */
+    public static native long open(String dirname, long pool)
+        throws Error;
+
+    /**
+     * close the specified directory.
+     * @param thedir the directory descriptor to close.
+     */
+    public static native int close(long thedir);
+
+    /**
+     * Rewind the directory to the first entry.
+     * @param thedir the directory descriptor to rewind.
+     */
+    public static native int rewind(long thedir);
+
+
+    /**
+     * Read the next entry from the specified directory.
+     * @param finfo the file info structure and filled in by apr_dir_read
+     * @param wanted The desired apr_finfo_t fields, as a bit flag of APR_FINFO_ values
+     * @param thedir the directory descriptor returned from apr_dir_open
+     * No ordering is guaranteed for the entries read.
+     */
+    public static native int read(FileInfo finfo, int wanted, long thedir);
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Error.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Error.java
new file mode 100644
index 0000000..ecdcb4a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Error.java
@@ -0,0 +1,98 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/** Error
+ *
+ * @author Mladen Turk
+ * @version $Id: Error.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public class Error extends Exception {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * APR error type.
+     */
+    private int error;
+
+    /**
+     * A description of the problem.
+     */
+    private String description;
+
+    /**
+     * Construct an APRException.
+     *
+     * @param error one of the value in Error
+     * @param description error message
+     */
+    private Error(int error, String description)
+    {
+        super(error + ": " + description);
+        this.error = error;
+        this.description = description;
+    }
+
+    /**
+     * Get the APR error code of the exception.
+     *
+     * @return error of the Exception
+     */
+    public int getError()
+    {
+        return error;
+    }
+
+    /**
+     * Get the APR description of the exception.
+     *
+     * @return description of the Exception
+     */
+    public String getDescription()
+    {
+        return description;
+    }
+
+    /**
+     * Get the last platform error.
+     * @return apr_status_t the last platform error, folded into apr_status_t, on most platforms
+     * This retrieves errno, or calls a GetLastError() style function, and
+     *      folds it with APR_FROM_OS_ERROR.  Some platforms (such as OS2) have no
+     *      such mechanism, so this call may be unsupported.  Do NOT use this
+     *      call for socket errors from socket, send, recv etc!
+     */
+    public static native int osError();
+
+    /**
+     * Get the last platform socket error.
+     * @return the last socket error, folded into apr_status_t, on all platforms
+     * This retrieves errno or calls a GetLastSocketError() style function,
+     *      and folds it with APR_FROM_OS_ERROR.
+     */
+    public static native int netosError();
+
+    /**
+     * Return a human readable string describing the specified error.
+     * @param statcode The error code the get a string for.
+     * @return The error string.
+    */
+    public static native String strerror(int statcode);
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/FileInfo.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/FileInfo.java
new file mode 100644
index 0000000..4117c40
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/FileInfo.java
@@ -0,0 +1,67 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.jni;
+
+/** Fileinfo
+ *
+ * @author Mladen Turk
+ * @version $Id: FileInfo.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public class FileInfo {
+
+    /** Allocates memory and closes lingering handles in the specified pool */
+    public long pool;
+    /** The bitmask describing valid fields of this apr_finfo_t structure
+     *  including all available 'wanted' fields and potentially more */
+    public int valid;
+    /** The access permissions of the file.  Mimics Unix access rights. */
+    public int protection;
+    /** The type of file.  One of APR_REG, APR_DIR, APR_CHR, APR_BLK, APR_PIPE,
+     * APR_LNK or APR_SOCK.  If the type is undetermined, the value is APR_NOFILE.
+     * If the type cannot be determined, the value is APR_UNKFILE.
+     */
+    public int filetype;
+    /** The user id that owns the file */
+    public int user;
+    /** The group id that owns the file */
+    public int group;
+    /** The inode of the file. */
+    public int inode;
+    /** The id of the device the file is on. */
+    public int device;
+    /** The number of hard links to the file. */
+    public int nlink;
+    /** The size of the file */
+    public long size;
+    /** The storage size consumed by the file */
+    public long csize;
+    /** The time the file was last accessed */
+    public long atime;
+    /** The time the file was last modified */
+    public long mtime;
+    /** The time the file was created, or the inode was last changed */
+    public long ctime;
+    /** The pathname of the file (possibly unrooted) */
+    public String fname;
+    /** The file's name (no path) in filesystem case */
+    public String name;
+    /** The file's handle, if accessed (can be submitted to apr_duphandle) */
+    public long filehand;
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Library.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Library.java
new file mode 100644
index 0000000..16dbceb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Library.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.tomcat.jni;
+
+/** Library
+ *
+ * @author Mladen Turk
+ * @version $Id: Library.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public final class Library {
+
+    /* Default library names */
+    private static String [] NAMES = {"tcnative-1", "libtcnative-1"};
+    /*
+     * A handle to the unique Library singleton instance.
+     */
+    private static Library _instance = null;
+
+    private Library()
+        throws Exception
+    {
+        boolean loaded = false;
+        StringBuilder err = new StringBuilder();
+        for (int i = 0; i < NAMES.length; i++) {
+            try {
+                System.loadLibrary(NAMES[i]);
+                loaded = true;
+            }
+            catch (Throwable t) {
+                if (t instanceof ThreadDeath) {
+                    throw (ThreadDeath) t;
+                }
+                if (t instanceof VirtualMachineError) {
+                    throw (VirtualMachineError) t;
+                }
+                String name = System.mapLibraryName(NAMES[i]);
+                String path = System.getProperty("java.library.path");
+                String sep = System.getProperty("path.separator");
+                String [] paths = path.split(sep);
+                for (int j=0; j<paths.length; j++) {
+                    java.io.File fd = new java.io.File(paths[j] , name);
+                    if (fd.exists()) {
+                        t.printStackTrace();
+                    }
+                }
+                if ( i > 0)
+                    err.append(", ");
+                err.append(t.getMessage());
+            }
+            if (loaded)
+                break;
+        }
+        if (!loaded) {
+            err.append('(');
+            err.append(System.getProperty("java.library.path"));
+            err.append(')');
+            throw new UnsatisfiedLinkError(err.toString());
+        }
+    }
+
+    private Library(String libraryName)
+    {
+        System.loadLibrary(libraryName);
+    }
+
+    /* create global TCN's APR pool
+     * This has to be the first call to TCN library.
+     */
+    private static native boolean initialize();
+    /* destroy global TCN's APR pool
+     * This has to be the last call to TCN library.
+     */
+    public static native void terminate();
+    /* Internal function for loading APR Features */
+    private static native boolean has(int what);
+    /* Internal function for loading APR Features */
+    private static native int version(int what);
+    /* Internal function for loading APR sizes */
+    private static native int size(int what);
+
+    /* TCN_MAJOR_VERSION */
+    public static int TCN_MAJOR_VERSION  = 0;
+    /* TCN_MINOR_VERSION */
+    public static int TCN_MINOR_VERSION  = 0;
+    /* TCN_PATCH_VERSION */
+    public static int TCN_PATCH_VERSION  = 0;
+    /* TCN_IS_DEV_VERSION */
+    public static int TCN_IS_DEV_VERSION = 0;
+    /* APR_MAJOR_VERSION */
+    public static int APR_MAJOR_VERSION  = 0;
+    /* APR_MINOR_VERSION */
+    public static int APR_MINOR_VERSION  = 0;
+    /* APR_PATCH_VERSION */
+    public static int APR_PATCH_VERSION  = 0;
+    /* APR_IS_DEV_VERSION */
+    public static int APR_IS_DEV_VERSION = 0;
+
+    /* TCN_VERSION_STRING */
+    public static native String versionString();
+    /* APR_VERSION_STRING */
+    public static native String aprVersionString();
+
+    /*  APR Feature Macros */
+    public static boolean APR_HAVE_IPV6           = false;
+    public static boolean APR_HAS_SHARED_MEMORY   = false;
+    public static boolean APR_HAS_THREADS         = false;
+    public static boolean APR_HAS_SENDFILE        = false;
+    public static boolean APR_HAS_MMAP            = false;
+    public static boolean APR_HAS_FORK            = false;
+    public static boolean APR_HAS_RANDOM          = false;
+    public static boolean APR_HAS_OTHER_CHILD     = false;
+    public static boolean APR_HAS_DSO             = false;
+    public static boolean APR_HAS_SO_ACCEPTFILTER = false;
+    public static boolean APR_HAS_UNICODE_FS      = false;
+    public static boolean APR_HAS_PROC_INVOKED    = false;
+    public static boolean APR_HAS_USER            = false;
+    public static boolean APR_HAS_LARGE_FILES     = false;
+    public static boolean APR_HAS_XTHREAD_FILES   = false;
+    public static boolean APR_HAS_OS_UUID         = false;
+    /* Are we big endian? */
+    public static boolean APR_IS_BIGENDIAN        = false;
+    /* APR sets APR_FILES_AS_SOCKETS to 1 on systems where it is possible
+     * to poll on files/pipes.
+     */
+    public static boolean APR_FILES_AS_SOCKETS    = false;
+    /* This macro indicates whether or not EBCDIC is the native character set.
+     */
+    public static boolean APR_CHARSET_EBCDIC      = false;
+    /* Is the TCP_NODELAY socket option inherited from listening sockets?
+     */
+    public static boolean APR_TCP_NODELAY_INHERITED = false;
+    /* Is the O_NONBLOCK flag inherited from listening sockets?
+     */
+    public static boolean APR_O_NONBLOCK_INHERITED  = false;
+
+
+    public static int APR_SIZEOF_VOIDP;
+    public static int APR_PATH_MAX;
+    public static int APRMAXHOSTLEN;
+    public static int APR_MAX_IOVEC_SIZE;
+    public static int APR_MAX_SECS_TO_LINGER;
+    public static int APR_MMAP_THRESHOLD;
+    public static int APR_MMAP_LIMIT;
+
+    /* return global TCN's APR pool */
+    public static native long globalPool();
+
+    /**
+     * Setup any APR internal data structures.  This MUST be the first function
+     * called for any APR library.
+     * @param libraryName the name of the library to load
+     */
+    public static boolean initialize(String libraryName)
+        throws Exception
+    {
+        if (_instance == null) {
+            if (libraryName == null)
+                _instance = new Library();
+            else
+                _instance = new Library(libraryName);
+            TCN_MAJOR_VERSION  = version(0x01);
+            TCN_MINOR_VERSION  = version(0x02);
+            TCN_PATCH_VERSION  = version(0x03);
+            TCN_IS_DEV_VERSION = version(0x04);
+            APR_MAJOR_VERSION  = version(0x11);
+            APR_MINOR_VERSION  = version(0x12);
+            APR_PATCH_VERSION  = version(0x13);
+            APR_IS_DEV_VERSION = version(0x14);
+
+            APR_SIZEOF_VOIDP        = size(1);
+            APR_PATH_MAX            = size(2);
+            APRMAXHOSTLEN           = size(3);
+            APR_MAX_IOVEC_SIZE      = size(4);
+            APR_MAX_SECS_TO_LINGER  = size(5);
+            APR_MMAP_THRESHOLD      = size(6);
+            APR_MMAP_LIMIT          = size(7);
+
+            APR_HAVE_IPV6           = has(0);
+            APR_HAS_SHARED_MEMORY   = has(1);
+            APR_HAS_THREADS         = has(2);
+            APR_HAS_SENDFILE        = has(3);
+            APR_HAS_MMAP            = has(4);
+            APR_HAS_FORK            = has(5);
+            APR_HAS_RANDOM          = has(6);
+            APR_HAS_OTHER_CHILD     = has(7);
+            APR_HAS_DSO             = has(8);
+            APR_HAS_SO_ACCEPTFILTER = has(9);
+            APR_HAS_UNICODE_FS      = has(10);
+            APR_HAS_PROC_INVOKED    = has(11);
+            APR_HAS_USER            = has(12);
+            APR_HAS_LARGE_FILES     = has(13);
+            APR_HAS_XTHREAD_FILES   = has(14);
+            APR_HAS_OS_UUID         = has(15);
+            APR_IS_BIGENDIAN        = has(16);
+            APR_FILES_AS_SOCKETS    = has(17);
+            APR_CHARSET_EBCDIC      = has(18);
+            APR_TCP_NODELAY_INHERITED = has(19);
+            APR_O_NONBLOCK_INHERITED  = has(20);
+            if (APR_MAJOR_VERSION < 1) {
+                throw new UnsatisfiedLinkError("Unsupported APR Version (" +
+                                               aprVersionString() + ")");
+            }
+            if (!APR_HAS_THREADS) {
+                throw new UnsatisfiedLinkError("Missing APR_HAS_THREADS");
+            }
+        }
+        return initialize();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Local.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Local.java
new file mode 100644
index 0000000..a0b2d61
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Local.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.tomcat.jni;
+
+/** Local socket
+ *
+ * @author Mladen Turk
+ * @version $Id: Local.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public class Local {
+
+    /**
+     * Create a socket.
+     * @param path The address of the new socket.
+     * @param cont The parent pool to use
+     * @return The new socket that has been set up.
+     */
+    public static native long create(String path, long cont)
+        throws Exception;
+
+    /**
+     * Bind the socket to its associated port
+     * @param sock The socket to bind
+     * @param sa The socket address to bind to
+     * This may be where we will find out if there is any other process
+     *      using the selected port.
+     */
+    public static native int bind(long sock, long sa);
+
+    /**
+     * Listen to a bound socket for connections.
+     * @param sock The socket to listen on
+     * @param backlog The number of outstanding connections allowed in the sockets
+     *                listen queue.  If this value is less than zero, for NT pipes
+     *                the number of instances is unlimited.
+     *
+     */
+    public static native int listen(long sock, int backlog);
+
+    /**
+     * Accept a new connection request
+     * @param sock The socket we are listening on.
+     * @return  A copy of the socket that is connected to the socket that
+     *          made the connection request.  This is the socket which should
+     *          be used for all future communication.
+     */
+    public static native long accept(long sock)
+        throws Exception;
+
+    /**
+     * Issue a connection request to a socket either on the same machine
+     * or a different one.
+     * @param sock The socket we wish to use for our side of the connection
+     * @param sa The address of the machine we wish to connect to.
+     *           Unused for NT Pipes.
+     */
+    public static native int connect(long sock, long sa);
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Lock.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Lock.java
new file mode 100644
index 0000000..c9649eb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Lock.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.jni;
+
+/** Lock
+ *
+ * @author Mladen Turk
+ * @version $Id: Lock.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public class Lock {
+
+    /**
+     * Enumerated potential types for APR process locking methods
+     * <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.
+     */
+
+    public static final int APR_LOCK_FCNTL        = 0; /** fcntl() */
+    public static final int APR_LOCK_FLOCK        = 1; /** flock() */
+    public static final int APR_LOCK_SYSVSEM      = 2; /** System V Semaphores */
+    public static final int APR_LOCK_PROC_PTHREAD = 3; /** POSIX pthread process-based locking */
+    public static final int APR_LOCK_POSIXSEM     = 4; /** POSIX semaphore process-based locking */
+    public static final int APR_LOCK_DEFAULT      = 5; /** Use the default process lock */
+
+    /**
+     * Create and initialize a mutex that can be used to synchronize processes.
+     * <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.
+     * This function must be called to maintain portability, even
+     * if the underlying lock mechanism does not require it.
+     * @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.
+     * @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);
+
+    /**
+     * Return the name of the lockfile for the mutex, or NULL
+     * if the mutex doesn't use a lock file
+     */
+    public static native String lockfile(long mutex);
+
+    /**
+     * Display the name of the mutex, as it relates to the actual method used.
+     * This matches the valid options for Apache's AcceptMutex directive
+     * @param mutex the name of the mutex
+     */
+    public static native String name(long mutex);
+
+    /**
+     * Display the name of the default mutex: APR_LOCK_DEFAULT
+     */
+    public static native String defname();
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Mmap.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Mmap.java
new file mode 100644
index 0000000..c5c874d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Mmap.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.tomcat.jni;
+
+/** Mmap
+ *
+ * @author Mladen Turk
+ * @version $Id: Mmap.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public class Mmap {
+    /** MMap opened for reading */
+    public static final int APR_MMAP_READ  = 1;
+    /** MMap opened for writing */
+    public static final int APR_MMAP_WRITE = 2;
+
+
+    /**
+     * Create a new mmap'ed file out of an existing APR file.
+     * @param file The file turn into an mmap.
+     * @param offset The offset into the file to start the data pointer at.
+     * @param size The size of the file
+     * @param flag bit-wise or of:
+     * <PRE>
+     * APR_MMAP_READ       MMap opened for reading
+     * APR_MMAP_WRITE      MMap opened for writing
+     * </PRE>
+     * @param pool The pool to use when creating the mmap.
+     * @return The newly created mmap'ed file.
+     */
+    public static native long create(long file, long offset, long size, int flag, long pool)
+        throws Error;
+
+    /**
+     * Duplicate the specified MMAP.
+     * @param mmap The mmap to duplicate.
+     * @param pool The pool to use for new_mmap.
+     * @return Duplicated mmap'ed file.
+     */
+    public static native long dup(long mmap, long pool)
+        throws Error;
+
+    /**
+     * Remove a mmap'ed.
+     * @param mm The mmap'ed file.
+     */
+    public static native int delete(long mm);
+
+    /**
+     * Move the pointer into the mmap'ed file to the specified offset.
+     * @param mm The mmap'ed file.
+     * @param offset The offset to move to.
+     * @return The pointer to the offset specified.
+     */
+    public static native long offset(long mm, long offset)
+        throws Error;
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Multicast.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Multicast.java
new file mode 100644
index 0000000..5e325ba
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Multicast.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.tomcat.jni;
+
+/** Multicast
+ *
+ * @author Mladen Turk
+ * @version $Id: Multicast.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public class Multicast {
+
+    /**
+     * Join a Multicast Group
+     * @param sock The socket to join a multicast group
+     * @param join The address of the multicast group to join
+     * @param iface Address of the interface to use.  If NULL is passed, the
+     *              default multicast interface will be used. (OS Dependent)
+     * @param source Source Address to accept transmissions from (non-NULL
+     *               implies Source-Specific Multicast)
+     */
+    public static native int join(long sock, long join,
+                                  long iface, long source);
+
+    /**
+     * Leave a Multicast Group.  All arguments must be the same as
+     * apr_mcast_join.
+     * @param sock The socket to leave a multicast group
+     * @param addr The address of the multicast group to leave
+     * @param iface Address of the interface to use.  If NULL is passed, the
+     *              default multicast interface will be used. (OS Dependent)
+     * @param source Source Address to accept transmissions from (non-NULL
+     *               implies Source-Specific Multicast)
+     */
+    public static native int leave(long sock, long addr,
+                                   long iface, long source);
+
+    /**
+     * Set the Multicast Time to Live (ttl) for a multicast transmission.
+     * @param sock The socket to set the multicast ttl
+     * @param ttl Time to live to Assign. 0-255, default=1
+     * <br /><b>Remark :</b> If the TTL is 0, packets will only be seen
+     * by sockets on the local machine,
+     * and only when multicast loopback is enabled.
+     */
+    public static native int hops(long sock, int ttl);
+
+    /**
+     * Toggle IP Multicast Loopback
+     * @param sock The socket to set multicast loopback
+     * @param opt false=disable, true=enable
+     */
+    public static native int loopback(long sock, boolean opt);
+
+
+    /**
+     * Set the Interface to be used for outgoing Multicast Transmissions.
+     * @param sock The socket to set the multicast interface on
+     * @param iface Address of the interface to use for Multicast
+     */
+    public static native int ointerface(long sock, long iface);
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/OS.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/OS.java
new file mode 100644
index 0000000..19449c5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/OS.java
@@ -0,0 +1,130 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/** OS
+ *
+ * @author Mladen Turk
+ * @version $Id: OS.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public class OS {
+
+    /* OS Enums */
+    private static final int UNIX      = 1;
+    private static final int NETWARE   = 2;
+    private static final int WIN32     = 3;
+    private static final int WIN64     = 4;
+    private static final int LINUX     = 5;
+    private static final int SOLARIS   = 6;
+    private static final int BSD       = 7;
+    private static final int MACOSX    = 8;
+
+    public static final int LOG_EMERG  = 1;
+    public static final int LOG_ERROR  = 2;
+    public static final int LOG_NOTICE = 3;
+    public static final int LOG_WARN   = 4;
+    public static final int LOG_INFO   = 5;
+    public static final int LOG_DEBUG  = 6;
+
+    /**
+     * Check for OS type.
+     * @param type OS type to test.
+     */
+    private static native boolean is(int type);
+
+    public static final boolean IS_UNIX    = is(UNIX);
+    public static final boolean IS_NETWARE = is(NETWARE);
+    public static final boolean IS_WIN32   = is(WIN32);
+    public static final boolean IS_WIN64   = is(WIN64);
+    public static final boolean IS_LINUX   = is(LINUX);
+    public static final boolean IS_SOLARIS = is(SOLARIS);
+    public static final boolean IS_BSD     = is(BSD);
+    public static final boolean IS_MACOSX  = is(MACOSX);
+
+    /**
+     * Get the name of the system default character set.
+     * @param pool the pool to allocate the name from, if needed
+     */
+    public static native String defaultEncoding(long pool);
+
+    /**
+     * Get the name of the current locale character set.
+     * Defers to apr_os_default_encoding if the current locale's
+     * data can't be retrieved on this system.
+     * @param pool the pool to allocate the name from, if needed
+     */
+    public static native String localeEncoding(long pool);
+
+    /**
+     * Generate random bytes.
+     * @param buf Buffer to fill with random bytes
+     * @param len Length of buffer in bytes
+     */
+    public static native int random(byte [] buf, int len);
+
+    /**
+     * Gather system info.
+     * <PRE>
+     * On exit the inf array will be filled with:
+     * inf[0]  - Total usable main memory size
+     * inf[1]  - Available memory size
+     * inf[2]  - Total page file/swap space size
+     * inf[3]  - Page file/swap space still available
+     * inf[4]  - Amount of shared memory
+     * inf[5]  - Memory used by buffers
+     * inf[6]  - Memory Load
+     *
+     * inf[7]  - Idle Time in microseconds
+     * inf[8]  - Kernel Time in microseconds
+     * inf[9]  - User Time in microseconds
+     *
+     * inf[10] - Process creation time (apr_time_t)
+     * inf[11] - Process Kernel Time in microseconds
+     * inf[12] - Process User Time in microseconds
+     *
+     * inf[13] - Current working set size.
+     * inf[14] - Peak working set size.
+     * inf[15] - Number of page faults.
+     * </PRE>
+     * @param inf array that will be filled with system information.
+     *            Array length must be at least 16.
+     */
+    public static native int info(long [] inf);
+
+    /**
+     * Expand environment variables.
+     * @param str String to expand
+     * @return Expanded string with replaced environment variables.
+     */
+    public static native String expand(String str);
+
+    /**
+     * Initialize system logging.
+     * @param domain String that will be prepended to every message
+     */
+    public static native void sysloginit(String domain);
+
+    /**
+     * Log message.
+     * @param level Log message severity. See LOG_XXX enums.
+     * @param message Message to log
+     */
+    public static native void syslog(int level, String message);
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/PasswordCallback.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/PasswordCallback.java
new file mode 100644
index 0000000..d1f3634
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/PasswordCallback.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.tomcat.jni;
+
+/** PasswordCallback Interface
+ *
+ * @author Mladen Turk
+ * @version $Id: PasswordCallback.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public interface PasswordCallback {
+
+    /**
+     * Called when the password is required
+     * @param prompt Password prompt
+     * @return Valid password or null
+     */
+    public String callback(String prompt);
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Poll.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Poll.java
new file mode 100644
index 0000000..60ae181
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Poll.java
@@ -0,0 +1,157 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.jni;
+
+/** Poll
+ *
+ * @author Mladen Turk
+ * @version $Id: Poll.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public class Poll {
+
+    /**
+     * Poll options
+     */
+    public static final int APR_POLLIN   = 0x001; /** Can read without blocking */
+    public static final int APR_POLLPRI  = 0x002; /** Priority data available */
+    public static final int APR_POLLOUT  = 0x004; /** Can write without blocking */
+    public static final int APR_POLLERR  = 0x010; /** Pending error */
+    public static final int APR_POLLHUP  = 0x020; /** Hangup occurred */
+    public static final int APR_POLLNVAL = 0x040; /** Descriptor invalid */
+
+    /**
+     * Pollset Flags
+     */
+    /** Adding or Removing a Descriptor is thread safe */
+    public static final int APR_POLLSET_THREADSAFE = 0x001;
+
+
+    /** Used in apr_pollfd_t to determine what the apr_descriptor is
+     * apr_datatype_e enum
+     */
+    public static final int APR_NO_DESC       = 0; /** nothing here */
+    public static final int APR_POLL_SOCKET   = 1; /** descriptor refers to a socket */
+    public static final int APR_POLL_FILE     = 2; /** descriptor refers to a file */
+    public static final int APR_POLL_LASTDESC = 3; /** descriptor is the last one in the list */
+
+    /**
+     * Setup a pollset object.
+     * If flags equals APR_POLLSET_THREADSAFE, then a pollset is
+     * created on which it is safe to make concurrent calls to
+     * apr_pollset_add(), apr_pollset_remove() and apr_pollset_poll() from
+     * separate threads.  This feature is only supported on some
+     * platforms; the apr_pollset_create() call will fail with
+     * APR_ENOTIMPL on platforms where it is not supported.
+     * @param size The maximum number of descriptors that this pollset can hold
+     * @param p The pool from which to allocate the pollset
+     * @param flags Optional flags to modify the operation of the pollset.
+     * @param ttl Maximum time to live for a particular socket.
+     * @return  The pointer in which to return the newly created object
+     */
+    public static native long create(int size, long p, int flags, long ttl)
+        throws Error;
+    /**
+     * Destroy a pollset object
+     * @param pollset The pollset to destroy
+     */
+    public static native int destroy(long pollset);
+
+    /**
+     * Add a socket or to a pollset
+     * If you set client_data in the descriptor, that value
+     * will be returned in the client_data field whenever this
+     * descriptor is signaled in apr_pollset_poll().
+     * @param pollset The pollset to which to add the descriptor
+     * @param sock The sockets to add
+     * @param reqevents requested events
+     */
+    public static native int add(long pollset, long sock,
+                                 int reqevents);
+
+    /**
+     * Remove a descriptor from a pollset
+     * @param pollset The pollset from which to remove the descriptor
+     * @param sock The socket to remove
+     */
+    public static native int remove(long pollset, long sock);
+
+    /**
+     * Block for activity on the descriptor(s) in a pollset
+     * @param pollset The pollset to use
+     * @param timeout Timeout in microseconds
+     * @param descriptors Array of signaled descriptors (output parameter)
+     *        The descriptor array must be two times the size of pollset.
+     *        and are populated as follows:
+     * <PRE>
+     * descriptors[2n + 0] -> returned events
+     * descriptors[2n + 1] -> socket
+     * </PRE>
+     * @param remove Remove signaled descriptors from pollset
+     * @return Number of signaled descriptors (output parameter)
+     *         or negative APR error code.
+     */
+    public static native int poll(long pollset, long timeout,
+                                  long [] descriptors, boolean remove);
+
+    /**
+     * Maintain on the descriptor(s) in a pollset
+     * @param pollset The pollset to use
+     * @param descriptors Array of signaled descriptors (output parameter)
+     *        The descriptor array must be the size of pollset.
+     *        and are populated as follows:
+     * <PRE>
+     * descriptors[n] -> socket
+     * </PRE>
+     * @param remove Remove signaled descriptors from pollset
+     * @return Number of signaled descriptors (output parameter)
+     *         or negative APR error code.
+     */
+    public static native int maintain(long pollset, long [] descriptors,
+                                      boolean remove);
+
+    /**
+     * Set the socket time to live.
+     * @param pollset The pollset to use
+     * @param ttl Timeout in microseconds
+     */
+    public static native void setTtl(long pollset, long ttl);
+
+    /**
+     * Get the socket time to live.
+     * @param pollset The pollset to use
+     * @return Timeout in microseconds
+     */
+    public static native long getTtl(long pollset);
+
+    /**
+     * Return all descriptor(s) in a pollset
+     * @param pollset The pollset to use
+     * @param descriptors Array of descriptors (output parameter)
+     *        The descriptor array must be two times the size of pollset.
+     *        and are populated as follows:
+     * <PRE>
+     * descriptors[2n + 0] -> returned events
+     * descriptors[2n + 1] -> socket
+     * </PRE>
+     * @return Number of descriptors (output parameter) in the Poll
+     *         or negative APR error code.
+     */
+    public static native int pollset(long pollset, long [] descriptors);
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Pool.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Pool.java
new file mode 100644
index 0000000..b4c6a9a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Pool.java
@@ -0,0 +1,164 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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 java.nio.ByteBuffer;
+
+/** Pool
+ *
+ * @author Mladen Turk
+ * @version $Id: Pool.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public class Pool {
+
+    /**
+     * Create a new pool.
+     * @param parent The parent pool.  If this is 0, the new pool is a root
+     * pool.  If it is non-zero, the new pool will inherit all
+     * of its parent pool's attributes, except the apr_pool_t will
+     * be a sub-pool.
+     * @return The pool we have just created.
+    */
+    public static native long create(long parent);
+
+    /**
+     * Clear all memory in the pool and run all the cleanups. This also destroys all
+     * subpools.
+     * @param pool The pool to clear
+     * This does not actually free the memory, it just allows the pool
+     *         to re-use this memory for the next allocation.
+     */
+    public static native void clear(long pool);
+
+    /**
+     * Destroy the pool. This takes similar action as apr_pool_clear() and then
+     * frees all the memory.
+     * This will actually free the memory
+     * @param pool The pool to destroy
+     */
+    public static native void destroy(long pool);
+
+    /**
+     * Get the parent pool of the specified pool.
+     * @param pool The pool for retrieving the parent pool.
+     * @return The parent of the given pool.
+     */
+    public static native long parentGet(long pool);
+
+    /**
+     * Determine if pool a is an ancestor of pool b
+     * @param a The pool to search
+     * @param b The pool to search for
+     * @return True if a is an ancestor of b, NULL is considered an ancestor
+     * of all pools.
+     */
+    public static native boolean isAncestor(long a, long b);
+
+
+    /*
+     * Cleanup
+     *
+     * Cleanups are performed in the reverse order they were registered.  That is:
+     * Last In, First Out.  A cleanup function can safely allocate memory from
+     * the pool that is being cleaned up. It can also safely register additional
+     * cleanups which will be run LIFO, directly after the current cleanup
+     * terminates.  Cleanups have to take caution in calling functions that
+     * create subpools. Subpools, created during cleanup will NOT automatically
+     * be cleaned up.  In other words, cleanups are to clean up after themselves.
+     */
+
+    /**
+     * Register a function to be called when a pool is cleared or destroyed
+     * @param pool The pool register the cleanup with
+     * @param o The object to call when the pool is cleared
+     *                      or destroyed
+     * @return The cleanup handler.
+     */
+    public static native long cleanupRegister(long pool, Object o);
+
+    /**
+     * Remove a previously registered cleanup function
+     * @param pool The pool remove the cleanup from
+     * @param data The cleanup handler to remove from cleanup
+     */
+    public static native void cleanupKill(long pool, long data);
+
+    /**
+     * Register a process to be killed when a pool dies.
+     * @param a The pool to use to define the processes lifetime
+     * @param proc The process to register
+     * @param how How to kill the process, one of:
+     * <PRE>
+     * APR_KILL_NEVER         -- process is never sent any signals
+     * APR_KILL_ALWAYS        -- process is sent SIGKILL on apr_pool_t cleanup
+     * APR_KILL_AFTER_TIMEOUT -- SIGTERM, wait 3 seconds, SIGKILL
+     * APR_JUST_WAIT          -- wait forever for the process to complete
+     * APR_KILL_ONLY_ONCE     -- send SIGTERM and then wait
+     * </PRE>
+     */
+    public static native void noteSubprocess(long a, long proc, int how);
+
+    /**
+     * Allocate a block of memory from a pool
+     * @param p The pool to allocate from
+     * @param size The amount of memory to allocate
+     * @return The ByteBuffer with allocated memory
+     */
+    public static native ByteBuffer alloc(long p, int size);
+
+    /**
+     * Allocate a block of memory from a pool and set all of the memory to 0
+     * @param p The pool to allocate from
+     * @param size The amount of memory to allocate
+     * @return The ByteBuffer with allocated memory
+     */
+    public static native ByteBuffer calloc(long p, int size);
+
+    /*
+     * User data management
+     */
+
+    /**
+     * Set the data associated with the current pool
+     * @param data The user data associated with the pool.
+     * @param key The key to use for association
+     * @param pool The current pool
+     * <br /><b>Warning :</b>
+     * The data to be attached to the pool should have a life span
+     * at least as long as the pool it is being attached to.
+     * Object attached to the pool will be globally referenced
+     * until the pool is cleared or dataSet is called with the null data.
+     * @return APR Status code.
+     */
+     public static native int dataSet(long pool, String key, Object data);
+
+    /**
+     * Return the data associated with the current pool.
+     * @param key The key for the data to retrieve
+     * @param pool The current pool.
+     */
+     public static native Object dataGet(long pool, String key);
+
+    /**
+     * Run all of the child_cleanups, so that any unnecessary files are
+     * closed because we are about to exec a new program
+     */
+    public static native void cleanupForExec();
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/PoolCallback.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/PoolCallback.java
new file mode 100644
index 0000000..71e44ad
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/PoolCallback.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.jni;
+
+/** PoolCallback Interface
+ *
+ * @author Mladen Turk
+ * @version $Id: PoolCallback.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public interface PoolCallback {
+
+    /**
+     * Called when the pool is destroyed or cleared
+     * @return Function must return APR_SUCCESS
+     */
+    public int callback();
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Proc.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Proc.java
new file mode 100644
index 0000000..ef6e125
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Proc.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.tomcat.jni;
+
+/** Proc
+ *
+ * @author Mladen Turk
+ * @version $Id: Proc.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public class Proc {
+
+    /*
+     * apr_cmdtype_e enum
+     */
+    public static final int APR_SHELLCM      = 0; /** use the shell to invoke the program */
+    public static final int APR_PROGRAM      = 1; /** invoke the program directly, no copied env */
+    public static final int APR_PROGRAM_ENV  = 2; /** invoke the program, replicating our environment */
+    public static final int APR_PROGRAM_PATH = 3; /** find program on PATH, use our environment */
+    public static final int APR_SHELLCMD_ENV = 4; /** use the shell to invoke the program,
+                                                   *   replicating our environment
+                                                   */
+
+    /*
+     * apr_wait_how_e enum
+     */
+    public static final int APR_WAIT   = 0; /** wait for the specified process to finish */
+    public static final int APR_NOWAIT = 1; /** do not wait -- just see if it has finished */
+
+    /*
+     * apr_exit_why_e enum
+     */
+    public static final int APR_PROC_EXIT        = 1; /** process exited normally */
+    public static final int APR_PROC_SIGNAL      = 2; /** process exited due to a signal */
+    public static final int APR_PROC_SIGNAL_CORE = 4; /** process exited and dumped a core file */
+
+    public static final int APR_NO_PIPE       = 0;
+    public static final int APR_FULL_BLOCK    = 1;
+    public static final int APR_FULL_NONBLOCK = 2;
+    public static final int APR_PARENT_BLOCK  = 3;
+    public static final int APR_CHILD_BLOCK   = 4;
+
+    public static final int APR_LIMIT_CPU     = 0;
+    public static final int APR_LIMIT_MEM     = 1;
+    public static final int APR_LIMIT_NPROC   = 2;
+    public static final int APR_LIMIT_NOFILE  = 3;
+
+
+    /** child has died, caller must call unregister still */
+    public static final int APR_OC_REASON_DEATH      = 0;
+    /** write_fd is unwritable */
+    public static final int APR_OC_REASON_UNWRITABLE = 1;
+    /** a restart is occurring, perform any necessary cleanup (including
+     * sending a special signal to child)
+     */
+    public static final int APR_OC_REASON_RESTART    = 2;
+    /** unregister has been called, do whatever is necessary (including
+     * kill the child)
+     */
+    public static final int APR_OC_REASON_UNREGISTER = 3;
+    /** somehow the child exited without us knowing ... buggy os? */
+    public static final int APR_OC_REASON_LOST       = 4;
+    /** a health check is occurring, for most maintenance functions
+     * this is a no-op.
+     */
+    public static final int APR_OC_REASON_RUNNING    = 5;
+
+    /* apr_kill_conditions_e enumeration */
+    /** process is never sent any signals */
+    public static final int APR_KILL_NEVER         = 0;
+    /** process is sent SIGKILL on apr_pool_t cleanup */
+    public static final int APR_KILL_ALWAYS        = 1;
+    /** SIGTERM, wait 3 seconds, SIGKILL */
+    public static final int APR_KILL_AFTER_TIMEOUT = 2;
+    /** wait forever for the process to complete */
+    public static final int APR_JUST_WAIT          = 3;
+    /** send SIGTERM and then wait */
+    public static final int APR_KILL_ONLY_ONCE     = 4;
+
+    public static final int APR_PROC_DETACH_FOREGROUND = 0; /** Do not detach */
+    public static final int APR_PROC_DETACH_DAEMONIZE  = 1; /** Detach */
+
+    /* Maximum number of arguments for create process call */
+    public static final int MAX_ARGS_SIZE          = 1024;
+    /* Maximum number of environment variables for create process call */
+    public static final int MAX_ENV_SIZE           = 1024;
+
+    /**
+     * Allocate apr_proc_t structure from pool
+     * This is not an apr function.
+     * @param cont The pool to use.
+     */
+    public static native long alloc(long cont);
+
+    /**
+     * This is currently the only non-portable call in APR.  This executes
+     * a standard unix fork.
+     * @param proc The resulting process handle.
+     * @param cont The pool to use.
+     * @return APR_INCHILD for the child, and APR_INPARENT for the parent
+     * or an error.
+     */
+    public static native int fork(long [] proc, long cont);
+
+    /**
+     * Create a new process and execute a new program within that process.
+     * This function returns without waiting for the new process to terminate;
+     * use apr_proc_wait for that.
+     * @param progname The program to run
+     * @param args The arguments to pass to the new program.  The first
+     *             one should be the program name.
+     * @param env The new environment table for the new process.  This
+     *            should be a list of NULL-terminated strings. This argument
+     *            is ignored for APR_PROGRAM_ENV, APR_PROGRAM_PATH, and
+     *            APR_SHELLCMD_ENV types of commands.
+     * @param attr The procattr we should use to determine how to create the new
+     * process
+     * @param pool The pool to use.
+     * @return The resulting process handle.
+     */
+    public static native int create(long proc, String progname,
+                                    String [] args, String [] env,
+                                    long attr, long pool);
+
+    /**
+     * Wait for a child process to die
+     * @param proc The process handle that corresponds to the desired child process
+     * @param exit exit[0] The returned exit status of the child, if a child process
+     *                dies, or the signal that caused the child to die.
+     *                On platforms that don't support obtaining this information,
+     *                the status parameter will be returned as APR_ENOTIMPL.
+     * exit[1] Why the child died, the bitwise or of:
+     * <PRE>
+     * APR_PROC_EXIT         -- process terminated normally
+     * APR_PROC_SIGNAL       -- process was killed by a signal
+     * APR_PROC_SIGNAL_CORE  -- process was killed by a signal, and
+     *                          generated a core dump.
+     * </PRE>
+     * @param waithow How should we wait.  One of:
+     * <PRE>
+     * APR_WAIT   -- block until the child process dies.
+     * APR_NOWAIT -- return immediately regardless of if the
+     *               child is dead or not.
+     * </PRE>
+     * @return The childs status is in the return code to this process.  It is one of:
+     * <PRE>
+     * APR_CHILD_DONE     -- child is no longer running.
+     * APR_CHILD_NOTDONE  -- child is still running.
+     * </PRE>
+     */
+    public static native int wait(long proc, int [] exit, int waithow);
+
+    /**
+     * Wait for any current child process to die and return information
+     * about that child.
+     * @param proc Pointer to NULL on entry, will be filled out with child's
+     *             information
+     * @param exit exit[0] The returned exit status of the child, if a child process
+     *                dies, or the signal that caused the child to die.
+     *                On platforms that don't support obtaining this information,
+     *                the status parameter will be returned as APR_ENOTIMPL.
+     * exit[1] Why the child died, the bitwise or of:
+     * <PRE>
+     * APR_PROC_EXIT         -- process terminated normally
+     * APR_PROC_SIGNAL       -- process was killed by a signal
+     * APR_PROC_SIGNAL_CORE  -- process was killed by a signal, and
+     *                          generated a core dump.
+     * </PRE>
+     * @param waithow How should we wait.  One of:
+     * <PRE>
+     * APR_WAIT   -- block until the child process dies.
+     * APR_NOWAIT -- return immediately regardless of if the
+     *               child is dead or not.
+     * </PRE>
+     * @param pool Pool to allocate child information out of.
+     */
+    public static native int waitAllProcs(long proc, int [] exit,
+                                          int waithow, long pool);
+
+     /**
+     * Detach the process from the controlling terminal.
+     * @param daemonize set to non-zero if the process should daemonize
+     *                  and become a background process, else it will
+     *                  stay in the foreground.
+     */
+    public static native int detach(int daemonize);
+
+    /**
+     * Terminate a process.
+     * @param proc The process to terminate.
+     * @param sig How to kill the process.
+     */
+    public static native int kill(long proc, int sig);
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/ProcErrorCallback.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/ProcErrorCallback.java
new file mode 100644
index 0000000..eb765d7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/ProcErrorCallback.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.jni;
+
+/** ProcErrorCallback Interface
+ *
+ * @author Mladen Turk
+ * @version $Id: ProcErrorCallback.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public interface ProcErrorCallback {
+
+    /**
+     * Called in the child process if APR encounters an error
+     * in the child prior to running the specified program.
+     * @param pool Pool associated with the apr_proc_t.  If your child
+     *             error function needs user data, associate it with this
+     *             pool.
+     * @param err APR error code describing the error
+     * @param description Text description of type of processing which failed
+     */
+    public void callback(long pool, int err, String description);
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Procattr.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Procattr.java
new file mode 100644
index 0000000..13d4416
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Procattr.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.tomcat.jni;
+
+/** Procattr
+ *
+ * @author Mladen Turk
+ * @version $Id: Procattr.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public class Procattr {
+
+    /**
+     * Create and initialize a new procattr variable
+     * @param cont The pool to use
+     * @return The newly created procattr.
+     */
+    public static native long create(long cont)
+        throws Error;
+
+    /**
+     * Determine if any of stdin, stdout, or stderr should be linked to pipes
+     * when starting a child process.
+     * @param attr The procattr we care about.
+     * @param in Should stdin be a pipe back to the parent?
+     * @param out Should stdout be a pipe back to the parent?
+     * @param err Should stderr be a pipe back to the parent?
+     */
+    public static native int ioSet(long attr, int in, int out, int err);
+    /**
+     * Set the child_in and/or parent_in values to existing apr_file_t values.
+     * <br />
+     * This is NOT a required initializer function. This is
+     * useful if you have already opened a pipe (or multiple files)
+     * that you wish to use, perhaps persistently across multiple
+     * process invocations - such as a log file. You can save some
+     * extra function calls by not creating your own pipe since this
+     * creates one in the process space for you.
+     * @param attr The procattr we care about.
+     * @param in apr_file_t value to use as child_in. Must be a valid file.
+     * @param parent apr_file_t value to use as parent_in. Must be a valid file.
+     */
+    public static native int childInSet(long attr, long in, long parent);
+
+    /**
+     * Set the child_out and parent_out values to existing apr_file_t values.
+     * <br />
+     * This is NOT a required initializer function. This is
+     * useful if you have already opened a pipe (or multiple files)
+     * that you wish to use, perhaps persistently across multiple
+     * process invocations - such as a log file.
+     * @param attr The procattr we care about.
+     * @param out apr_file_t value to use as child_out. Must be a valid file.
+     * @param parent apr_file_t value to use as parent_out. Must be a valid file.
+     */
+    public static native int childOutSet(long attr, long out, long parent);
+
+    /**
+     * Set the child_err and parent_err values to existing apr_file_t values.
+     * <br />
+     * This is NOT a required initializer function. This is
+     * useful if you have already opened a pipe (or multiple files)
+     * that you wish to use, perhaps persistently across multiple
+     * process invocations - such as a log file.
+     * @param attr The procattr we care about.
+     * @param err apr_file_t value to use as child_err. Must be a valid file.
+     * @param parent apr_file_t value to use as parent_err. Must be a valid file.
+     */
+    public static native int childErrSet(long attr, long err, long parent);
+
+    /**
+     * Set which directory the child process should start executing in.
+     * @param attr The procattr we care about.
+     * @param dir Which dir to start in.  By default, this is the same dir as
+     *            the parent currently resides in, when the createprocess call
+     *            is made.
+     */
+    public static native int dirSet(long attr, String dir);
+
+    /**
+     * Set what type of command the child process will call.
+     * @param attr The procattr we care about.
+     * @param cmd The type of command.  One of:
+     * <PRE>
+     * APR_SHELLCMD     --  Anything that the shell can handle
+     * APR_PROGRAM      --  Executable program   (default)
+     * APR_PROGRAM_ENV  --  Executable program, copy environment
+     * APR_PROGRAM_PATH --  Executable program on PATH, copy env
+     * </PRE>
+     */
+    public static native int cmdtypeSet(long attr, int cmd);
+
+    /**
+     * Determine if the child should start in detached state.
+     * @param attr The procattr we care about.
+     * @param detach Should the child start in detached state?  Default is no.
+     */
+    public static native int detachSet(long attr, int detach);
+
+    /**
+     * Specify that apr_proc_create() should do whatever it can to report
+     * failures to the caller of apr_proc_create(), rather than find out in
+     * the child.
+     * @param attr The procattr describing the child process to be created.
+     * @param chk Flag to indicate whether or not extra work should be done
+     *            to try to report failures to the caller.
+     * <br />
+     * This flag only affects apr_proc_create() on platforms where
+     * fork() is used.  This leads to extra overhead in the calling
+     * process, but that may help the application handle such
+     * errors more gracefully.
+     */
+    public static native int errorCheckSet(long attr, int chk);
+
+    /**
+     * Determine if the child should start in its own address space or using the
+     * current one from its parent
+     * @param attr The procattr we care about.
+     * @param addrspace Should the child start in its own address space?  Default
+     * is no on NetWare and yes on other platforms.
+     */
+    public static native int addrspaceSet(long attr, int addrspace);
+
+    /**
+     * Specify an error function to be called in the child process if APR
+     * encounters an error in the child prior to running the specified program.
+     * @param attr The procattr describing the child process to be created.
+     * @param pool The the pool to use.
+     * @param o The Object to call in the child process.
+     * <br />
+     * At the present time, it will only be called from apr_proc_create()
+     * on platforms where fork() is used.  It will never be called on other
+     * platforms, on those platforms apr_proc_create() will return the error
+     * in the parent process rather than invoke the callback in the now-forked
+     * child process.
+     */
+    public static native void errfnSet(long attr, long pool, Object o);
+
+    /**
+     * Set the username used for running process
+     * @param attr The procattr we care about.
+     * @param username The username used
+     * @param password User password if needed. Password is needed on WIN32
+     *                 or any other platform having
+     *                 APR_PROCATTR_USER_SET_REQUIRES_PASSWORD set.
+     */
+    public static native int userSet(long attr, String username, String password);
+
+    /**
+     * Set the group used for running process
+     * @param attr The procattr we care about.
+     * @param groupname The group name  used
+     */
+    public static native int groupSet(long attr, String groupname);
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Registry.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Registry.java
new file mode 100644
index 0000000..2baf5f8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Registry.java
@@ -0,0 +1,235 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/** Windows Registy support
+ *
+ * @author Mladen Turk
+ * @version $Id: Registry.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public class Registry {
+
+    /* Registry Enums */
+    public static final int HKEY_CLASSES_ROOT       = 1;
+    public static final int HKEY_CURRENT_CONFIG     = 2;
+    public static final int HKEY_CURRENT_USER       = 3;
+    public static final int HKEY_LOCAL_MACHINE      = 4;
+    public static final int HKEY_USERS              = 5;
+
+    public static final int KEY_ALL_ACCESS          = 0x0001;
+    public static final int KEY_CREATE_LINK         = 0x0002;
+    public static final int KEY_CREATE_SUB_KEY      = 0x0004;
+    public static final int KEY_ENUMERATE_SUB_KEYS  = 0x0008;
+    public static final int KEY_EXECUTE             = 0x0010;
+    public static final int KEY_NOTIFY              = 0x0020;
+    public static final int KEY_QUERY_VALUE         = 0x0040;
+    public static final int KEY_READ                = 0x0080;
+    public static final int KEY_SET_VALUE           = 0x0100;
+    public static final int KEY_WOW64_64KEY         = 0x0200;
+    public static final int KEY_WOW64_32KEY         = 0x0400;
+    public static final int KEY_WRITE               = 0x0800;
+
+    public static final int REG_BINARY              = 1;
+    public static final int REG_DWORD               = 2;
+    public static final int REG_EXPAND_SZ           = 3;
+    public static final int REG_MULTI_SZ            = 4;
+    public static final int REG_QWORD               = 5;
+    public static final int REG_SZ                  = 6;
+
+     /**
+     * Create or open a Registry Key.
+     * @param name Registry Subkey to open
+     * @param root Root key, one of HKEY_*
+     * @param sam Access mask that specifies the access rights for the key.
+     * @param pool Pool used for native memory allocation
+     * @return Opened Registry key
+     */
+    public static native long create(int root, String name, int sam, long pool)
+        throws Error;
+
+     /**
+     * Opens the specified Registry Key.
+     * @param name Registry Subkey to open
+     * @param root Root key, one of HKEY_*
+     * @param sam Access mask that specifies the access rights for the key.
+     * @param pool Pool used for native memory allocation
+     * @return Opened Registry key
+     */
+    public static native long open(int root, String name, int sam, long pool)
+        throws Error;
+
+    /**
+     * Close the specified Registry key.
+     * @param key The Registry key descriptor to close.
+     */
+    public static native int close(long key);
+
+    /**
+     * Get the Registry key type.
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to query
+     * @return Value type or negative error value
+     */
+    public static native int getType(long key, String name);
+
+    /**
+     * Get the Registry value for REG_DWORD
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to query
+     * @return Registry key value
+     */
+    public static native int getValueI(long key, String name)
+        throws Error;
+
+    /**
+     * Get the Registry value for REG_QWORD or REG_DWORD
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to query
+     * @return Registry key value
+     */
+    public static native long getValueJ(long key, String name)
+        throws Error;
+
+    /**
+     * Get the Registry key length.
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to query
+     * @return Value size or negative error value
+     */
+    public static native int getSize(long key, String name);
+
+    /**
+     * Get the Registry value for REG_SZ or REG_EXPAND_SZ
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to query
+     * @return Registry key value
+     */
+    public static native String getValueS(long key, String name)
+        throws Error;
+
+    /**
+     * Get the Registry value for REG_MULTI_SZ
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to query
+     * @return Registry key value
+     */
+    public static native String[] getValueA(long key, String name)
+        throws Error;
+
+    /**
+     * Get the Registry value for REG_BINARY
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to query
+     * @return Registry key value
+     */
+    public static native byte[] getValueB(long key, String name)
+        throws Error;
+
+
+    /**
+     * Set the Registry value for REG_DWORD
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to set
+     * @param val The the value to set
+     * @return If the function succeeds, the return value is 0
+     */
+    public static native int setValueI(long key, String name, int val);
+
+    /**
+     * Set the Registry value for REG_QWORD
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to set
+     * @param val The the value to set
+     * @return If the function succeeds, the return value is 0
+     */
+    public static native int setValueJ(long key, String name, long val);
+
+    /**
+     * Set the Registry value for REG_SZ
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to set
+     * @param val The the value to set
+     * @return If the function succeeds, the return value is 0
+     */
+    public static native int setValueS(long key, String name, String val);
+
+    /**
+     * Set the Registry value for REG_EXPAND_SZ
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to set
+     * @param val The the value to set
+     * @return If the function succeeds, the return value is 0
+     */
+    public static native int setValueE(long key, String name, String val);
+
+     /**
+     * Set the Registry value for REG_MULTI_SZ
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to set
+     * @param val The the value to set
+     * @return If the function succeeds, the return value is 0
+     */
+    public static native int setValueA(long key, String name, String[] val);
+
+     /**
+     * Set the Registry value for REG_BINARY
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to set
+     * @param val The the value to set
+     * @return If the function succeeds, the return value is 0
+     */
+    public static native int setValueB(long key, String name, byte[] val);
+
+    /**
+     * Enumerate the Registry subkeys
+     * @param key The Registry key descriptor to use.
+     * @return Array of all subkey names
+     */
+    public static native String[] enumKeys(long key)
+        throws Error;
+
+    /**
+     * Enumerate the Registry values
+     * @param key The Registry key descriptor to use.
+     * @return Array of all value names
+     */
+    public static native String[] enumValues(long key)
+        throws Error;
+
+     /**
+     * Delete the Registry value
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to delete
+     * @return If the function succeeds, the return value is 0
+     */
+    public static native int deleteValue(long key, String name);
+
+     /**
+     * Delete the Registry subkey
+     * @param root Root key, one of HKEY_*
+     * @param name Subkey to delete
+     * @param onlyIfEmpty If true will not delete a key if
+     *                    it contains any subkeys or values
+     * @return If the function succeeds, the return value is 0
+     */
+    public static native int deleteKey(int root, String name,
+                                       boolean onlyIfEmpty);
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/SSL.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/SSL.java
new file mode 100644
index 0000000..9b11460
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/SSL.java
@@ -0,0 +1,344 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/** SSL
+ *
+ * @author Mladen Turk
+ * @version $Id: SSL.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public final class SSL {
+
+    /*
+     * Type definitions mostly from mod_ssl
+     */
+    public static final int UNSET            = -1;
+    /*
+     * Define the certificate algorithm types
+     */
+    public static final int SSL_ALGO_UNKNOWN = 0;
+    public static final int SSL_ALGO_RSA     = (1<<0);
+    public static final int SSL_ALGO_DSA     = (1<<1);
+    public static final int SSL_ALGO_ALL     = (SSL_ALGO_RSA|SSL_ALGO_DSA);
+
+    public static final int SSL_AIDX_RSA     = 0;
+    public static final int SSL_AIDX_DSA     = 1;
+    public static final int SSL_AIDX_MAX     = 2;
+    /*
+     * Define IDs for the temporary RSA keys and DH params
+     */
+
+    public static final int SSL_TMP_KEY_RSA_512  = 0;
+    public static final int SSL_TMP_KEY_RSA_1024 = 1;
+    public static final int SSL_TMP_KEY_RSA_2048 = 2;
+    public static final int SSL_TMP_KEY_RSA_4096 = 3;
+    public static final int SSL_TMP_KEY_DH_512   = 4;
+    public static final int SSL_TMP_KEY_DH_1024  = 5;
+    public static final int SSL_TMP_KEY_DH_2048  = 6;
+    public static final int SSL_TMP_KEY_DH_4096  = 7;
+    public static final int SSL_TMP_KEY_MAX      = 8;
+
+    /*
+     * Define the SSL options
+     */
+    public static final int SSL_OPT_NONE           = 0;
+    public static final int SSL_OPT_RELSET         = (1<<0);
+    public static final int SSL_OPT_STDENVVARS     = (1<<1);
+    public static final int SSL_OPT_EXPORTCERTDATA = (1<<3);
+    public static final int SSL_OPT_FAKEBASICAUTH  = (1<<4);
+    public static final int SSL_OPT_STRICTREQUIRE  = (1<<5);
+    public static final int SSL_OPT_OPTRENEGOTIATE = (1<<6);
+    public static final int SSL_OPT_ALL            = (SSL_OPT_STDENVVARS|SSL_OPT_EXPORTCERTDATA|SSL_OPT_FAKEBASICAUTH|SSL_OPT_STRICTREQUIRE|SSL_OPT_OPTRENEGOTIATE);
+
+    /*
+     * Define the SSL Protocol options
+     */
+    public static final int SSL_PROTOCOL_NONE  = 0;
+    public static final int SSL_PROTOCOL_SSLV2 = (1<<0);
+    public static final int SSL_PROTOCOL_SSLV3 = (1<<1);
+    public static final int SSL_PROTOCOL_TLSV1 = (1<<2);
+    public static final int SSL_PROTOCOL_ALL   = (SSL_PROTOCOL_SSLV2|SSL_PROTOCOL_SSLV3|SSL_PROTOCOL_TLSV1);
+
+    /*
+     * Define the SSL verify levels
+     */
+    public static final int SSL_CVERIFY_UNSET          = UNSET;
+    public static final int SSL_CVERIFY_NONE           = 0;
+    public static final int SSL_CVERIFY_OPTIONAL       = 1;
+    public static final int SSL_CVERIFY_REQUIRE        = 2;
+    public static final int SSL_CVERIFY_OPTIONAL_NO_CA = 3;
+
+    /* Use either SSL_VERIFY_NONE or SSL_VERIFY_PEER, the last 2 options
+     * are 'ored' with SSL_VERIFY_PEER if they are desired
+     */
+    public static final int SSL_VERIFY_NONE                 = 0;
+    public static final int SSL_VERIFY_PEER                 = 1;
+    public static final int SSL_VERIFY_FAIL_IF_NO_PEER_CERT = 2;
+    public static final int SSL_VERIFY_CLIENT_ONCE          = 4;
+    public static final int SSL_VERIFY_PEER_STRICT          = (SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT);
+
+    public static final int SSL_OP_MICROSOFT_SESS_ID_BUG            = 0x00000001;
+    public static final int SSL_OP_NETSCAPE_CHALLENGE_BUG           = 0x00000002;
+    public static final int SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG = 0x00000008;
+    public static final int SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG      = 0x00000010;
+    public static final int SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER       = 0x00000020;
+    public static final int SSL_OP_MSIE_SSLV2_RSA_PADDING           = 0x00000040;
+    public static final int SSL_OP_SSLEAY_080_CLIENT_DH_BUG         = 0x00000080;
+    public static final int SSL_OP_TLS_D5_BUG                       = 0x00000100;
+    public static final int SSL_OP_TLS_BLOCK_PADDING_BUG            = 0x00000200;
+
+    /* Disable SSL 3.0/TLS 1.0 CBC vulnerability workaround that was added
+     * in OpenSSL 0.9.6d.  Usually (depending on the application protocol)
+     * the workaround is not needed.  Unfortunately some broken SSL/TLS
+     * implementations cannot handle it at all, which is why we include
+     * it in SSL_OP_ALL. */
+    public static final int SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS      = 0x00000800;
+
+    /* SSL_OP_ALL: various bug workarounds that should be rather harmless.
+     *             This used to be 0x000FFFFFL before 0.9.7. */
+    public static final int SSL_OP_ALL                              = 0x00000FFF;
+    /* As server, disallow session resumption on renegotiation */
+    public static final int SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION = 0x00010000;
+    /* Permit unsafe legacy renegotiation */
+    public static final int SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION      = 0x00040000;
+    /* If set, always create a new key when using tmp_eddh parameters */
+    public static final int SSL_OP_SINGLE_ECDH_USE                  = 0x00080000;
+    /* If set, always create a new key when using tmp_dh parameters */
+    public static final int SSL_OP_SINGLE_DH_USE                    = 0x00100000;
+    /* Set to always use the tmp_rsa key when doing RSA operations,
+     * even when this violates protocol specs */
+    public static final int SSL_OP_EPHEMERAL_RSA                    = 0x00200000;
+    /* Set on servers to choose the cipher according to the server's
+     * preferences */
+    public static final int SSL_OP_CIPHER_SERVER_PREFERENCE         = 0x00400000;
+    /* If set, a server will allow a client to issue a SSLv3.0 version number
+     * as latest version supported in the premaster secret, even when TLSv1.0
+     * (version 3.1) was announced in the client hello. Normally this is
+     * forbidden to prevent version rollback attacks. */
+    public static final int SSL_OP_TLS_ROLLBACK_BUG                 = 0x00800000;
+
+    public static final int SSL_OP_NO_SSLv2                         = 0x01000000;
+    public static final int SSL_OP_NO_SSLv3                         = 0x02000000;
+    public static final int SSL_OP_NO_TLSv1                         = 0x04000000;
+
+    /* The next flag deliberately changes the ciphertest, this is a check
+     * for the PKCS#1 attack */
+    public static final int SSL_OP_PKCS1_CHECK_1                    = 0x08000000;
+    public static final int SSL_OP_PKCS1_CHECK_2                    = 0x10000000;
+    public static final int SSL_OP_NETSCAPE_CA_DN_BUG               = 0x20000000;
+    public static final int SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG  = 0x40000000;
+
+    public static final int SSL_CRT_FORMAT_UNDEF    = 0;
+    public static final int SSL_CRT_FORMAT_ASN1     = 1;
+    public static final int SSL_CRT_FORMAT_TEXT     = 2;
+    public static final int SSL_CRT_FORMAT_PEM      = 3;
+    public static final int SSL_CRT_FORMAT_NETSCAPE = 4;
+    public static final int SSL_CRT_FORMAT_PKCS12   = 5;
+    public static final int SSL_CRT_FORMAT_SMIME    = 6;
+    public static final int SSL_CRT_FORMAT_ENGINE   = 7;
+
+    public static final int SSL_MODE_CLIENT         = 0;
+    public static final int SSL_MODE_SERVER         = 1;
+    public static final int SSL_MODE_COMBINED       = 2;
+
+    public static final int SSL_SHUTDOWN_TYPE_UNSET    = 0;
+    public static final int SSL_SHUTDOWN_TYPE_STANDARD = 1;
+    public static final int SSL_SHUTDOWN_TYPE_UNCLEAN  = 2;
+    public static final int SSL_SHUTDOWN_TYPE_ACCURATE = 3;
+
+    public static final int SSL_INFO_SESSION_ID                = 0x0001;
+    public static final int SSL_INFO_CIPHER                    = 0x0002;
+    public static final int SSL_INFO_CIPHER_USEKEYSIZE         = 0x0003;
+    public static final int SSL_INFO_CIPHER_ALGKEYSIZE         = 0x0004;
+    public static final int SSL_INFO_CIPHER_VERSION            = 0x0005;
+    public static final int SSL_INFO_CIPHER_DESCRIPTION        = 0x0006;
+    public static final int SSL_INFO_PROTOCOL                  = 0x0007;
+
+    /* To obtain the CountryName of the Client Certificate Issuer
+     * use the SSL_INFO_CLIENT_I_DN + SSL_INFO_DN_COUNTRYNAME
+     */
+    public static final int SSL_INFO_CLIENT_S_DN               = 0x0010;
+    public static final int SSL_INFO_CLIENT_I_DN               = 0x0020;
+    public static final int SSL_INFO_SERVER_S_DN               = 0x0040;
+    public static final int SSL_INFO_SERVER_I_DN               = 0x0080;
+
+    public static final int SSL_INFO_DN_COUNTRYNAME            = 0x0001;
+    public static final int SSL_INFO_DN_STATEORPROVINCENAME    = 0x0002;
+    public static final int SSL_INFO_DN_LOCALITYNAME           = 0x0003;
+    public static final int SSL_INFO_DN_ORGANIZATIONNAME       = 0x0004;
+    public static final int SSL_INFO_DN_ORGANIZATIONALUNITNAME = 0x0005;
+    public static final int SSL_INFO_DN_COMMONNAME             = 0x0006;
+    public static final int SSL_INFO_DN_TITLE                  = 0x0007;
+    public static final int SSL_INFO_DN_INITIALS               = 0x0008;
+    public static final int SSL_INFO_DN_GIVENNAME              = 0x0009;
+    public static final int SSL_INFO_DN_SURNAME                = 0x000A;
+    public static final int SSL_INFO_DN_DESCRIPTION            = 0x000B;
+    public static final int SSL_INFO_DN_UNIQUEIDENTIFIER       = 0x000C;
+    public static final int SSL_INFO_DN_EMAILADDRESS           = 0x000D;
+
+    public static final int SSL_INFO_CLIENT_M_VERSION          = 0x0101;
+    public static final int SSL_INFO_CLIENT_M_SERIAL           = 0x0102;
+    public static final int SSL_INFO_CLIENT_V_START            = 0x0103;
+    public static final int SSL_INFO_CLIENT_V_END              = 0x0104;
+    public static final int SSL_INFO_CLIENT_A_SIG              = 0x0105;
+    public static final int SSL_INFO_CLIENT_A_KEY              = 0x0106;
+    public static final int SSL_INFO_CLIENT_CERT               = 0x0107;
+    public static final int SSL_INFO_CLIENT_V_REMAIN           = 0x0108;
+
+    public static final int SSL_INFO_SERVER_M_VERSION          = 0x0201;
+    public static final int SSL_INFO_SERVER_M_SERIAL           = 0x0202;
+    public static final int SSL_INFO_SERVER_V_START            = 0x0203;
+    public static final int SSL_INFO_SERVER_V_END              = 0x0204;
+    public static final int SSL_INFO_SERVER_A_SIG              = 0x0205;
+    public static final int SSL_INFO_SERVER_A_KEY              = 0x0206;
+    public static final int SSL_INFO_SERVER_CERT               = 0x0207;
+    /* Return client certificate chain.
+     * Add certificate chain number to that flag (0 ... verify depth)
+     */
+    public static final int SSL_INFO_CLIENT_CERT_CHAIN         = 0x0400;
+    /* Return OpenSSL version number */
+    public static native int version();
+
+    /* Return OpenSSL version string */
+    public static native String versionString();
+
+    /**
+     * Initialize OpenSSL support.
+     * This function needs to be called once for the
+     * lifetime of JVM. Library.init() has to be called before.
+     * @param engine Support for external a Crypto Device ("engine"),
+     *                usually
+     * a hardware accelerator card for crypto operations.
+     * @return APR status code
+     */
+    public static native int initialize(String engine);
+
+    /**
+     * Add content of the file to the PRNG
+     * @param filename Filename containing random data.
+     *        If null the default file will be tested.
+     *        The seed file is $RANDFILE if that environment variable is
+     *        set, $HOME/.rnd otherwise.
+     *        In case both files are unavailable builtin
+     *        random seed generator is used.
+     */
+    public static native boolean randLoad(String filename);
+
+    /**
+     * Writes a number of random bytes (currently 1024) to
+     * file <code>filename</code> which can be used to initialize the PRNG
+     * by calling randLoad in a later session.
+     * @param filename Filename to save the data
+     */
+    public static native boolean randSave(String filename);
+
+    /**
+     * Creates random data to filename
+     * @param filename Filename to save the data
+     * @param len The length of random sequence in bytes
+     * @param base64 Output the data in Base64 encoded format
+     */
+    public static native boolean randMake(String filename, int len,
+                                          boolean base64);
+
+    /**
+     * Sets global random filename.
+     * @param filename Filename to use.
+     *        If set it will be used for SSL initialization
+     *        and all contexts where explicitly not set.
+     */
+    public static native void randSet(String filename);
+
+    /**
+     * Initialize new BIO
+     * @param pool The pool to use.
+     * @param callback BIOCallback to use
+     * @return New BIO handle
+     */
+     public static native long newBIO(long pool, BIOCallback callback)
+            throws Exception;
+
+    /**
+     * Close BIO and dereference callback object
+     * @param bio BIO to close and destroy.
+     * @return APR Status code
+     */
+     public static native int closeBIO(long bio);
+
+    /**
+     * Set global Password callback for obtaining passwords.
+     * @param callback PasswordCallback implementation to use.
+     */
+     public static native void setPasswordCallback(PasswordCallback callback);
+
+    /**
+     * Set global Password for decrypting certificates and keys.
+     * @param password Password to use.
+     */
+     public static native void setPassword(String password);
+
+    /**
+     * Generate temporary RSA key.
+     * <br />
+     * Index can be one of:
+     * <PRE>
+     * SSL_TMP_KEY_RSA_512
+     * SSL_TMP_KEY_RSA_1024
+     * SSL_TMP_KEY_RSA_2048
+     * SSL_TMP_KEY_RSA_4096
+     * </PRE>
+     * By default 512 and 1024 keys are generated on startup.
+     * You can use a low priority thread to generate them on the fly.
+     * @param idx temporary key index.
+     */
+    public static native boolean generateRSATempKey(int idx);
+
+    /**
+     * Load temporary DSA key from file
+     * <br />
+     * Index can be one of:
+     * <PRE>
+     * SSL_TMP_KEY_DH_512
+     * SSL_TMP_KEY_DH_1024
+     * SSL_TMP_KEY_DH_2048
+     * SSL_TMP_KEY_DH_4096
+     * </PRE>
+     * @param idx temporary key index.
+     * @param file File containing DH params.
+     */
+    public static native boolean loadDSATempKey(int idx, String file);
+
+    /**
+     * Return last SSL error string
+     */
+    public static native String getLastError();
+
+    /**
+     * Return true if SSL_OP_ if defined.
+     * <p>
+     * Currently used for testing weather the
+     * SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION is supported by OpenSSL.
+     * <p>
+     * @param op SSL_OP to test.
+     * @return true if SSL_OP is supported by OpenSSL library.
+     */
+    public static native boolean hasOp(int op);
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/SSLContext.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/SSLContext.java
new file mode 100644
index 0000000..966c683
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/SSLContext.java
@@ -0,0 +1,284 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.jni;
+
+/** SSL Context
+ *
+ * @author Mladen Turk
+ * @version $Id: SSLContext.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public final class SSLContext {
+
+
+    /**
+     * Initialize new SSL context
+     * @param pool The pool to use.
+     * @param protocol The SSL protocol to use. It can be one of:
+     * <PRE>
+     * SSL_PROTOCOL_SSLV2
+     * SSL_PROTOCOL_SSLV3
+     * SSL_PROTOCOL_SSLV2 | SSL_PROTOCOL_SSLV3
+     * SSL_PROTOCOL_TLSV1
+     * SSL_PROTOCOL_ALL
+     * </PRE>
+     * @param mode SSL mode to use
+     * <PRE>
+     * SSL_MODE_CLIENT
+     * SSL_MODE_SERVER
+     * SSL_MODE_COMBINED
+     * </PRE>
+     */
+    public static native long make(long pool, int protocol, int mode)
+        throws Exception;
+
+    /**
+     * Free the resources used by the Context
+     * @param ctx Server or Client context to free.
+     * @return APR Status code.
+     */
+    public static native int free(long ctx);
+
+    /**
+     * Set Session context id. Usually host:port combination.
+     * @param ctx Context to use.
+     * @param id  String that uniquely identifies this context.
+     */
+    public static native void setContextId(long ctx, String id);
+
+    /**
+     * Associate BIOCallback for input or output data capture.
+     * <br />
+     * First word in the output string will contain error
+     * level in the form:
+     * <PRE>
+     * [ERROR]  -- Critical error messages
+     * [WARN]   -- Warning messages
+     * [INFO]   -- Informational messages
+     * [DEBUG]  -- Debugging messaged
+     * </PRE>
+     * Callback can use that word to determine application logging level
+     * by intercepting <b>write</b> call.
+     * If the <b>bio</b> is set to 0 no error messages will be displayed.
+     * Default is to use the stderr output stream.
+     * @param ctx Server or Client context to use.
+     * @param bio BIO handle to use, created with SSL.newBIO
+     * @param dir BIO direction (1 for input 0 for output).
+     */
+    public static native void setBIO(long ctx, long bio, int dir);
+
+    /**
+     * Set OpenSSL Option.
+     * @param ctx Server or Client context to use.
+     * @param options  See SSL.SSL_OP_* for option flags.
+     */
+    public static native void setOptions(long ctx, int options);
+
+    /**
+     * Sets the "quiet shutdown" flag for <b>ctx</b> to be
+     * <b>mode</b>. SSL objects created from <b>ctx</b> inherit the
+     * <b>mode</b> valid at the time and may be 0 or 1.
+     * <br />
+     * Normally when a SSL connection is finished, the parties must send out
+     * "close notify" alert messages using L<SSL_shutdown(3)|SSL_shutdown(3)>
+     * for a clean shutdown.
+     * <br />
+     * When setting the "quiet shutdown" flag to 1, <b>SSL.shutdown</b>
+     * will set the internal flags to SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN.
+     * (<b>SSL_shutdown</b> then behaves like called with
+     * SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN.)
+     * The session is thus considered to be shutdown, but no "close notify" alert
+     * is sent to the peer. This behaviour violates the TLS standard.
+     * The default is normal shutdown behaviour as described by the TLS standard.
+     * @param ctx Server or Client context to use.
+     * @param mode True to set the quiet shutdown.
+     */
+    public static native void setQuietShutdown(long ctx, boolean mode);
+
+    /**
+     * Cipher Suite available for negotiation in SSL handshake.
+     * <br />
+     * This complex directive uses a colon-separated cipher-spec string consisting
+     * of OpenSSL cipher specifications to configure the Cipher Suite the client
+     * is permitted to negotiate in the SSL handshake phase. Notice that this
+     * directive can be used both in per-server and per-directory context.
+     * In per-server context it applies to the standard SSL handshake when a
+     * connection is established. In per-directory context it forces a SSL
+     * renegotiation with the reconfigured Cipher Suite after the HTTP request
+     * was read but before the HTTP response is sent.
+     * @param ctx Server or Client context to use.
+     * @param ciphers An SSL cipher specification.
+     */
+    public static native boolean setCipherSuite(long ctx, String ciphers)
+        throws Exception;
+
+    /**
+     * Set File of concatenated PEM-encoded CA CRLs or
+     * directory of PEM-encoded CA Certificates for Client Auth
+     * <br />
+     * This directive sets the all-in-one file where you can assemble the
+     * Certificate Revocation Lists (CRL) of Certification Authorities (CA)
+     * whose clients you deal with. These are used for Client Authentication.
+     * Such a file is simply the concatenation of the various PEM-encoded CRL
+     * files, in order of preference.
+     * <br />
+     * The files in this directory have to be PEM-encoded and are accessed through
+     * hash filenames. So usually you can't just place the Certificate files there:
+     * you also have to create symbolic links named hash-value.N. And you should
+     * always make sure this directory contains the appropriate symbolic links.
+     * Use the Makefile which comes with mod_ssl to accomplish this task.
+     * @param ctx Server or Client context to use.
+     * @param file File of concatenated PEM-encoded CA CRLs for Client Auth.
+     * @param path Directory of PEM-encoded CA Certificates for Client Auth.
+     */
+    public static native boolean setCARevocation(long ctx, String file,
+                                                 String path)
+        throws Exception;
+
+    /**
+     * Set File of PEM-encoded Server CA Certificates
+     * <br />
+     * This directive sets the optional all-in-one file where you can assemble the
+     * certificates of Certification Authorities (CA) which form the certificate
+     * chain of the server certificate. This starts with the issuing CA certificate
+     * of of the server certificate and can range up to the root CA certificate.
+     * Such a file is simply the concatenation of the various PEM-encoded CA
+     * Certificate files, usually in certificate chain order.
+     * <br />
+     * But be careful: Providing the certificate chain works only if you are using
+     * a single (either RSA or DSA) based server certificate. If you are using a
+     * coupled RSA+DSA certificate pair, this will work only if actually both
+     * certificates use the same certificate chain. Else the browsers will be
+     * confused in this situation.
+     * @param ctx Server or Client context to use.
+     * @param file File of PEM-encoded Server CA Certificates.
+     * @param skipfirst Skip first certificate if chain file is inside
+     *                  certificate file.
+     */
+    public static native boolean setCertificateChainFile(long ctx, String file,
+                                                         boolean skipfirst);
+
+    /**
+     * Set Certificate
+     * <br />
+     * Point setCertificateFile at a PEM encoded certificate.  If
+     * the certificate is encrypted, then you will be prompted for a
+     * pass phrase.  Note that a kill -HUP will prompt again. A test
+     * certificate can be generated with `make certificate' under
+     * built time. Keep in mind that if you've both a RSA and a DSA
+     * certificate you can configure both in parallel (to also allow
+     * the use of DSA ciphers, etc.)
+     * <br />
+     * If the key is not combined with the certificate, use key param
+     * to point at the key file.  Keep in mind that if
+     * you've both a RSA and a DSA private key you can configure
+     * both in parallel (to also allow the use of DSA ciphers, etc.)
+     * @param ctx Server or Client context to use.
+     * @param cert Certificate file.
+     * @param key Private Key file to use if not in cert.
+     * @param password Certificate password. If null and certificate
+     *                 is encrypted, password prompt will be displayed.
+     * @param idx Certificate index SSL_AIDX_RSA or SSL_AIDX_DSA.
+     */
+    public static native boolean setCertificate(long ctx, String cert,
+                                                String key, String password,
+                                                int idx)
+        throws Exception;
+
+    /**
+     * Set File and Directory of concatenated PEM-encoded CA Certificates
+     * for Client Auth
+     * <br />
+     * This directive sets the all-in-one file where you can assemble the
+     * Certificates of Certification Authorities (CA) whose clients you deal with.
+     * These are used for Client Authentication. Such a file is simply the
+     * concatenation of the various PEM-encoded Certificate files, in order of
+     * preference. This can be used alternatively and/or additionally to
+     * path.
+     * <br />
+     * The files in this directory have to be PEM-encoded and are accessed through
+     * hash filenames. So usually you can't just place the Certificate files there:
+     * you also have to create symbolic links named hash-value.N. And you should
+     * always make sure this directory contains the appropriate symbolic links.
+     * Use the Makefile which comes with mod_ssl to accomplish this task.
+     * @param ctx Server or Client context to use.
+     * @param file File of concatenated PEM-encoded CA Certificates for
+     *             Client Auth.
+     * @param path Directory of PEM-encoded CA Certificates for Client Auth.
+     */
+    public static native boolean setCACertificate(long ctx, String file,
+                                                  String path)
+        throws Exception;
+
+    /**
+     * Set file for randomness
+     * @param ctx Server or Client context to use.
+     * @param file random file.
+     */
+    public static native void setRandom(long ctx, String file);
+
+    /**
+     * Set SSL connection shutdown type
+     * <br />
+     * The following levels are available for level:
+     * <PRE>
+     * SSL_SHUTDOWN_TYPE_STANDARD
+     * SSL_SHUTDOWN_TYPE_UNCLEAN
+     * SSL_SHUTDOWN_TYPE_ACCURATE
+     * </PRE>
+     * @param ctx Server or Client context to use.
+     * @param type Shutdown type to use.
+     */
+    public static native void setShutdownType(long ctx, int type);
+
+    /**
+     * Set Type of Client Certificate verification and Maximum depth of CA Certificates
+     * in Client Certificate verification.
+     * <br />
+     * This directive sets the Certificate verification level for the Client
+     * Authentication. Notice that this directive can be used both in per-server
+     * and per-directory context. In per-server context it applies to the client
+     * authentication process used in the standard SSL handshake when a connection
+     * is established. In per-directory context it forces a SSL renegotiation with
+     * the reconfigured client verification level after the HTTP request was read
+     * but before the HTTP response is sent.
+     * <br />
+     * The following levels are available for level:
+     * <PRE>
+     * SSL_CVERIFY_NONE           - No client Certificate is required at all
+     * SSL_CVERIFY_OPTIONAL       - The client may present a valid Certificate
+     * SSL_CVERIFY_REQUIRE        - The client has to present a valid Certificate
+     * SSL_CVERIFY_OPTIONAL_NO_CA - The client may present a valid Certificate
+     *                              but it need not to be (successfully) verifiable
+     * </PRE>
+     * <br />
+     * The depth actually is the maximum number of intermediate certificate issuers,
+     * i.e. the number of CA certificates which are max allowed to be followed while
+     * verifying the client certificate. A depth of 0 means that self-signed client
+     * certificates are accepted only, the default depth of 1 means the client
+     * certificate can be self-signed or has to be signed by a CA which is directly
+     * known to the server (i.e. the CA's certificate is under
+     * <code>setCACertificatePath</code>), etc.
+     * @param ctx Server or Client context to use.
+     * @param level Type of Client Certificate verification.
+     * @param depth Maximum depth of CA Certificates in Client Certificate
+     *              verification.
+     */
+    public static native void setVerify(long ctx, int level, int depth);
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/SSLSocket.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/SSLSocket.java
new file mode 100644
index 0000000..f6d3452
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/SSLSocket.java
@@ -0,0 +1,112 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.jni;
+
+/** SSL Socket
+ *
+ * @author Mladen Turk
+ * @version $Id: SSLSocket.java,v 1.1 2011/06/28 21:08:25 rherrmann Exp $
+ */
+
+public class SSLSocket {
+
+    /**
+     * Attach APR socket on a SSL connection.
+     * @param ctx SSLContext to use.
+     * @param sock APR Socket that already did physical connect or accept.
+     * @return APR_STATUS code.
+     */
+    public static native int attach(long ctx, long sock)
+        throws Exception;
+
+    /**
+     * Do a SSL handshake.
+     * @param thesocket The socket to use
+     */
+    public static native int handshake(long thesocket);
+
+    /**
+     * Do a SSL renegotiation.
+     * SSL supports per-directory re-configuration of SSL parameters.
+     * This is implemented by performing an SSL renegotiation of the
+     * re-configured parameters after the request is read, but before the
+     * response is sent. In more detail: the renegotiation happens after the
+     * request line and MIME headers were read, but _before_ the attached
+     * request body is read. The reason simply is that in the HTTP protocol
+     * usually there is no acknowledgment step between the headers and the
+     * body (there is the 100-continue feature and the chunking facility
+     * only), so Apache has no API hook for this step.
+     *
+     * @param thesocket The socket to use
+     */
+    public static native int renegotiate(long thesocket);
+
+    /**
+     * Set Type of Client Certificate verification and Maximum depth of CA
+     * Certificates in Client Certificate verification.
+     * <br />
+     * This is used to change the verification level for a connection prior to
+     * starting a re-negotiation.
+     * <br />
+     * The following levels are available for level:
+     * <PRE>
+     * SSL_CVERIFY_NONE           - No client Certificate is required at all
+     * SSL_CVERIFY_OPTIONAL       - The client may present a valid Certificate
+     * SSL_CVERIFY_REQUIRE        - The client has to present a valid
+     *                              Certificate
+     * SSL_CVERIFY_OPTIONAL_NO_CA - The client may present a valid Certificate
+     *                              but it need not to be (successfully)
+     *                              verifiable
+     * </PRE>
+     * <br />
+     * @param sock  The socket to change.
+     * @param level Type of Client Certificate verification.
+     */
+    public static native void setVerify(long sock, int level, int depth);
+    
+    /**    
+     * Return SSL Info parameter as byte array.
+     *
+     * @param sock The socket to read the data from.
+     * @param id Parameter id.
+     * @return Byte array containing info id value.
+     */
+    public static native byte[] getInfoB(long sock, int id)
+        throws Exception;
+
+    /**
+     * Return SSL Info parameter as String.
+     *
+     * @param sock The socket to read the data from.
+     * @param id Parameter id.
+     * @return String containing info id value.
+     */
+    public static native String getInfoS(long sock, int id)
+        throws Exception;
+
+    /**
+     * Return SSL Info parameter as integer.
+     *
+     * @param sock The socket to read the data from.
+     * @param id Parameter id.
+     * @return Integer containing info id value or -1 on error.
+     */
+    public static native int getInfoI(long sock, int id)
+        throws Exception;
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Shm.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Shm.java
new file mode 100644
index 0000000..8a9f1e2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Shm.java
@@ -0,0 +1,124 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.jni;
+
+import java.nio.ByteBuffer;
+
+/** Shm
+ *
+ * @author Mladen Turk
+ * @version $Id: Shm.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public class Shm {
+
+    /**
+     * Create and make accessible a shared memory segment.
+     * <br />
+     * A note about Anonymous vs. Named shared memory segments:<br />
+     *         Not all platforms support anonymous shared memory segments, but in
+     *         some cases it is preferred over other types of shared memory
+     *         implementations. Passing a NULL 'file' parameter to this function
+     *         will cause the subsystem to use anonymous shared memory segments.
+     *         If such a system is not available, APR_ENOTIMPL is returned.
+     * <br />
+     * A note about allocation sizes:<br />
+     *         On some platforms it is necessary to store some metainformation
+     *         about the segment within the actual segment. In order to supply
+     *         the caller with the requested size it may be necessary for the
+     *         implementation to request a slightly greater segment length
+     *         from the subsystem. In all cases, the apr_shm_baseaddr_get()
+     *         function will return the first usable byte of memory.
+     * @param reqsize The desired size of the segment.
+     * @param filename The file to use for shared memory on platforms that
+     *        require it.
+     * @param pool the pool from which to allocate the shared memory
+     *        structure.
+     * @return The created shared memory structure.
+     *
+     */
+    public static native long create(long reqsize, String filename, long pool)
+        throws Error;
+
+    /**
+     * Remove shared memory segment associated with a filename.
+     * <br />
+     * This function is only supported on platforms which support
+     * name-based shared memory segments, and will return APR_ENOTIMPL on
+     * platforms without such support.
+     * @param filename The filename associated with shared-memory segment which
+     *        needs to be removed
+     * @param pool The pool used for file operations
+     */
+    public static native int remove(String filename, long pool);
+
+    /**
+     * Destroy a shared memory segment and associated memory.
+     * @param m The shared memory segment structure to destroy.
+     */
+    public static native int destroy(long m);
+
+    /**
+     * Attach to a shared memory segment that was created
+     * by another process.
+     * @param filename The file used to create the original segment.
+     *        (This MUST match the original filename.)
+     * @param pool the pool from which to allocate the shared memory
+     *        structure for this process.
+     * @return The created shared memory structure.
+     */
+    public static native long attach(String filename, long pool)
+        throws Error;
+
+    /**
+     * Detach from a shared memory segment without destroying it.
+     * @param m The shared memory structure representing the segment
+     *        to detach from.
+     */
+    public static native int detach(long m);
+
+    /**
+     * Retrieve the base address of the shared memory segment.
+     * NOTE: This address is only usable within the callers address
+     * space, since this API does not guarantee that other attaching
+     * processes will maintain the same address mapping.
+     * @param m The shared memory segment from which to retrieve
+     *        the base address.
+     * @return address, aligned by APR_ALIGN_DEFAULT.
+     */
+    public static native long baseaddr(long m);
+
+    /**
+     * Retrieve the length of a shared memory segment in bytes.
+     * @param m The shared memory segment from which to retrieve
+     *        the segment length.
+     */
+    public static native long size(long m);
+
+    /**
+     * Retrieve new ByteBuffer base address of the shared memory segment.
+     * NOTE: This address is only usable within the callers address
+     * space, since this API does not guarantee that other attaching
+     * processes will maintain the same address mapping.
+     * @param m The shared memory segment from which to retrieve
+     *        the base address.
+     * @return address, aligned by APR_ALIGN_DEFAULT.
+     */
+    public static native ByteBuffer buffer(long m);
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Sockaddr.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Sockaddr.java
new file mode 100644
index 0000000..b0f420a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Sockaddr.java
@@ -0,0 +1,42 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.jni;
+
+/** Sockaddr
+ *
+ * @author Mladen Turk
+ * @version $Id: Sockaddr.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public class Sockaddr {
+
+   /** The pool to use... */
+    public long pool;
+    /** The hostname */
+    public String hostname;
+    /** Either a string of the port number or the service name for the port */
+    public String servname;
+    /** The numeric port */
+    public int port;
+    /** The family */
+    public int family;
+    /** If multiple addresses were found by apr_sockaddr_info_get(), this
+     *  points to a representation of the next address. */
+    public long next;
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Socket.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Socket.java
new file mode 100644
index 0000000..97c3d26
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Socket.java
@@ -0,0 +1,586 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/** Socket
+ *
+ * @author Mladen Turk
+ * @version $Id: Socket.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public class Socket {
+
+    /* Standard socket defines */
+    public static final int SOCK_STREAM = 0;
+    public static final int SOCK_DGRAM  = 1;
+    /*
+     * apr_sockopt Socket option definitions
+     */
+    public static final int APR_SO_LINGER       = 1;    /** Linger */
+    public static final int APR_SO_KEEPALIVE    = 2;    /** Keepalive */
+    public static final int APR_SO_DEBUG        = 4;    /** Debug */
+    public static final int APR_SO_NONBLOCK     = 8;    /** Non-blocking IO */
+    public static final int APR_SO_REUSEADDR    = 16;   /** Reuse addresses */
+    public static final int APR_SO_SNDBUF       = 64;   /** Send buffer */
+    public static final int APR_SO_RCVBUF       = 128;  /** Receive buffer */
+    public static final int APR_SO_DISCONNECTED = 256;  /** Disconnected */
+    /** For SCTP sockets, this is mapped to STCP_NODELAY internally. */
+    public static final int APR_TCP_NODELAY     = 512;
+    public static final int APR_TCP_NOPUSH      = 1024; /** No push */
+    /** This flag is ONLY set internally when we set APR_TCP_NOPUSH with
+     * APR_TCP_NODELAY set to tell us that APR_TCP_NODELAY should be turned on
+     * again when NOPUSH is turned off
+     */
+    public static final int APR_RESET_NODELAY   = 2048;
+    /** Set on non-blocking sockets (timeout != 0) on which the
+     * previous read() did not fill a buffer completely.  the next
+     * apr_socket_recv()  will first call select()/poll() rather than
+     * going straight into read().  (Can also be set by an application to
+     * force a select()/poll() call before the next read, in cases where
+     * the app expects that an immediate read would fail.)
+     */
+    public static final int APR_INCOMPLETE_READ = 4096;
+    /** like APR_INCOMPLETE_READ, but for write
+     */
+    public static final int APR_INCOMPLETE_WRITE = 8192;
+    /** Don't accept IPv4 connections on an IPv6 listening socket.
+     */
+    public static final int APR_IPV6_V6ONLY      = 16384;
+    /** Delay accepting of new connections until data is available.
+     */
+    public static final int APR_TCP_DEFER_ACCEPT = 32768;
+
+    /** Define what type of socket shutdown should occur.
+     * apr_shutdown_how_e enum
+     */
+    public static final int APR_SHUTDOWN_READ      = 0; /** no longer allow read request */
+    public static final int APR_SHUTDOWN_WRITE     = 1; /** no longer allow write requests */
+    public static final int APR_SHUTDOWN_READWRITE = 2; /** no longer allow read or write requests */
+
+    public static final int APR_IPV4_ADDR_OK = 0x01;
+    public static final int APR_IPV6_ADDR_OK = 0x02;
+
+    /* TODO: Missing:
+     * APR_INET
+     * APR_UNSPEC
+     * APR_INET6
+     */
+    public static final int APR_UNSPEC = 0;
+    public static final int APR_INET   = 1;
+    public static final int APR_INET6  = 2;
+
+    public static final int APR_PROTO_TCP  =   6; /** TCP  */
+    public static final int APR_PROTO_UDP  =  17; /** UDP  */
+    public static final int APR_PROTO_SCTP = 132; /** SCTP */
+
+    /**
+     * Enum to tell us if we're interested in remote or local socket
+     * apr_interface_e
+     */
+    public static final int APR_LOCAL  = 0;
+    public static final int APR_REMOTE = 1;
+
+    /* Socket.get types */
+    public static final int SOCKET_GET_POOL = 0;
+    public static final int SOCKET_GET_IMPL = 1;
+    public static final int SOCKET_GET_APRS = 2;
+    public static final int SOCKET_GET_TYPE = 3;
+
+    /**
+     * Create a socket.
+     * @param family The address family of the socket (e.g., APR_INET).
+     * @param type The type of the socket (e.g., SOCK_STREAM).
+     * @param protocol The protocol of the socket (e.g., APR_PROTO_TCP).
+     * @param cont The parent pool to use
+     * @return The new socket that has been set up.
+     */
+    public static native long create(int family, int type,
+                                     int protocol, long cont)
+        throws Exception;
+
+
+    /**
+     * Shutdown either reading, writing, or both sides of a socket.
+     * <br />
+     * This does not actually close the socket descriptor, it just
+     *      controls which calls are still valid on the socket.
+     * @param thesocket The socket to close
+     * @param how How to shutdown the socket.  One of:
+     * <PRE>
+     * APR_SHUTDOWN_READ         no longer allow read requests
+     * APR_SHUTDOWN_WRITE        no longer allow write requests
+     * APR_SHUTDOWN_READWRITE    no longer allow read or write requests
+     * </PRE>
+     */
+    public static native int shutdown(long thesocket, int how);
+
+    /**
+     * Close a socket.
+     * @param thesocket The socket to close
+     */
+    public static native int close(long thesocket);
+
+    /**
+     * Destroy a pool associated with socket
+     * @param thesocket The destroy
+     */
+    public static native void destroy(long thesocket);
+
+    /**
+     * Bind the socket to its associated port
+     * @param sock The socket to bind
+     * @param sa The socket address to bind to
+     * This may be where we will find out if there is any other process
+     *      using the selected port.
+     */
+    public static native int bind(long sock, long sa);
+
+    /**
+     * Listen to a bound socket for connections.
+     * @param sock The socket to listen on
+     * @param backlog The number of outstanding connections allowed in the sockets
+     *                listen queue.  If this value is less than zero, the listen
+     *                queue size is set to zero.
+     */
+    public static native int listen(long sock, int backlog);
+
+    /**
+     * Accept a new connection request
+     * @param sock The socket we are listening on.
+     * @param pool The pool for the new socket.
+     * @return  A copy of the socket that is connected to the socket that
+     *          made the connection request.  This is the socket which should
+     *          be used for all future communication.
+     */
+    public static native long acceptx(long sock, long pool)
+        throws Exception;
+
+    /**
+     * Accept a new connection request
+     * @param sock The socket we are listening on.
+     * @return  A copy of the socket that is connected to the socket that
+     *          made the connection request.  This is the socket which should
+     *          be used for all future communication.
+     */
+    public static native long accept(long sock)
+        throws Exception;
+
+    /**
+     * Set an OS level accept filter.
+     * @param sock The socket to put the accept filter on.
+     * @param name The accept filter
+     * @param args Any extra args to the accept filter.  Passing NULL here removes
+     *             the accept filter.
+     */
+    public static native int acceptfilter(long sock, String name, String args);
+
+    /**
+     * Query the specified socket if at the OOB/Urgent data mark
+     * @param sock The socket to query
+     * @return True if socket is at the OOB/urgent mark,
+     *         otherwise return false.
+     */
+    public static native boolean atmark(long sock);
+
+    /**
+     * Issue a connection request to a socket either on the same machine
+     * or a different one.
+     * @param sock The socket we wish to use for our side of the connection
+     * @param sa The address of the machine we wish to connect to.
+     */
+    public static native int connect(long sock, long sa);
+
+    /**
+     * Send data over a network.
+     * <PRE>
+     * This functions acts like a blocking write by default.  To change
+     * this behavior, use apr_socket_timeout_set() or the APR_SO_NONBLOCK
+     * socket option.
+     *
+     * It is possible for both bytes to be sent and an error to be returned.
+     *
+     * APR_EINTR is never returned.
+     * </PRE>
+     * @param sock The socket to send the data over.
+     * @param buf The buffer which contains the data to be sent.
+     * @param offset Offset in the byte buffer.
+     * @param len The number of bytes to write; (-1) for full array.
+     * @return The number of bytes send.
+     *
+     */
+    public static native int send(long sock, byte[] buf, int offset, int len);
+
+    /**
+     * Send data over a network.
+     * <PRE>
+     * This functions acts like a blocking write by default.  To change
+     * this behavior, use apr_socket_timeout_set() or the APR_SO_NONBLOCK
+     * socket option.
+     *
+     * It is possible for both bytes to be sent and an error to be returned.
+     *
+     * APR_EINTR is never returned.
+     * </PRE>
+     * @param sock The socket to send the data over.
+     * @param buf The Byte buffer which contains the data to be sent.
+     * @param offset The offset within the buffer array of the first buffer from
+     *               which bytes are to be retrieved; must be non-negative
+     *               and no larger than buf.length
+     * @param len The maximum number of buffers to be accessed; must be non-negative
+     *            and no larger than buf.length - offset
+     * @return The number of bytes send.
+     *
+     */
+    public static native int sendb(long sock, ByteBuffer buf,
+                                   int offset, int len);
+
+    /**
+     * Send data over a network without retry
+     * <PRE>
+     * This functions acts like a blocking write by default.  To change
+     * this behavior, use apr_socket_timeout_set() or the APR_SO_NONBLOCK
+     * socket option.
+     *
+     * It is possible for both bytes to be sent and an error to be returned.
+     *
+     * </PRE>
+     * @param sock The socket to send the data over.
+     * @param buf The Byte buffer which contains the data to be sent.
+     * @param offset The offset within the buffer array of the first buffer from
+     *               which bytes are to be retrieved; must be non-negative
+     *               and no larger than buf.length
+     * @param len The maximum number of buffers to be accessed; must be non-negative
+     *            and no larger than buf.length - offset
+     * @return The number of bytes send.
+     *
+     */
+    public static native int sendib(long sock, ByteBuffer buf,
+                                    int offset, int len);
+
+    /**
+     * Send data over a network using internally set ByteBuffer
+     */
+    public static native int sendbb(long sock,
+                                   int offset, int len);
+
+    /**
+     * Send data over a network using internally set ByteBuffer
+     * without internal retry.
+     */
+    public static native int sendibb(long sock,
+                                     int offset, int len);
+
+    /**
+     * Send multiple packets of data over a network.
+     * <PRE>
+     * This functions acts like a blocking write by default.  To change
+     * this behavior, use apr_socket_timeout_set() or the APR_SO_NONBLOCK
+     * socket option.
+     * The number of bytes actually sent is stored in argument 3.
+     *
+     * It is possible for both bytes to be sent and an error to be returned.
+     *
+     * APR_EINTR is never returned.
+     * </PRE>
+     * @param sock The socket to send the data over.
+     * @param vec The array from which to get the data to send.
+     *
+     */
+    public static native int sendv(long sock, byte[][] vec);
+
+    /**
+     * @param sock The socket to send from
+     * @param where The apr_sockaddr_t describing where to send the data
+     * @param flags The flags to use
+     * @param buf  The data to send
+     * @param offset Offset in the byte buffer.
+     * @param len  The length of the data to send
+     */
+    public static native int sendto(long sock, long where, int flags,
+                                    byte[] buf, int offset, int len);
+
+    /**
+     * Read data from a network.
+     *
+     * <PRE>
+     * This functions acts like a blocking read by default.  To change
+     * this behavior, use apr_socket_timeout_set() or the APR_SO_NONBLOCK
+     * socket option.
+     * The number of bytes actually received is stored in argument 3.
+     *
+     * It is possible for both bytes to be received and an APR_EOF or
+     * other error to be returned.
+     *
+     * APR_EINTR is never returned.
+     * </PRE>
+     * @param sock The socket to read the data from.
+     * @param buf The buffer to store the data in.
+     * @param offset Offset in the byte buffer.
+     * @param nbytes The number of bytes to read (-1) for full array.
+     * @return the number of bytes received.
+     */
+    public static native int recv(long sock, byte[] buf, int offset, int nbytes);
+
+    /**
+     * Read data from a network with timeout.
+     *
+     * <PRE>
+     * This functions acts like a blocking read by default.  To change
+     * this behavior, use apr_socket_timeout_set() or the APR_SO_NONBLOCK
+     * socket option.
+     * The number of bytes actually received is stored in argument 3.
+     *
+     * It is possible for both bytes to be received and an APR_EOF or
+     * other error to be returned.
+     *
+     * APR_EINTR is never returned.
+     * </PRE>
+     * @param sock The socket to read the data from.
+     * @param buf The buffer to store the data in.
+     * @param offset Offset in the byte buffer.
+     * @param nbytes The number of bytes to read (-1) for full array.
+     * @param timeout The socket timeout in microseconds.
+     * @return the number of bytes received.
+     */
+    public static native int recvt(long sock, byte[] buf, int offset,
+                                   int nbytes, long timeout);
+
+    /**
+     * Read data from a network.
+     *
+     * <PRE>
+     * This functions acts like a blocking read by default.  To change
+     * this behavior, use apr_socket_timeout_set() or the APR_SO_NONBLOCK
+     * socket option.
+     * The number of bytes actually received is stored in argument 3.
+     *
+     * It is possible for both bytes to be received and an APR_EOF or
+     * other error to be returned.
+     *
+     * APR_EINTR is never returned.
+     * </PRE>
+     * @param sock The socket to read the data from.
+     * @param buf The buffer to store the data in.
+     * @param offset Offset in the byte buffer.
+     * @param nbytes The number of bytes to read (-1) for full array.
+     * @return the number of bytes received.
+     */
+    public static native int recvb(long sock, ByteBuffer buf,
+                                   int offset, int nbytes);
+    /**
+     * Read data from a network using internally set ByteBuffer
+     */
+    public static native int recvbb(long sock,
+                                    int offset, int nbytes);
+    /**
+     * Read data from a network with timeout.
+     *
+     * <PRE>
+     * This functions acts like a blocking read by default.  To change
+     * this behavior, use apr_socket_timeout_set() or the APR_SO_NONBLOCK
+     * socket option.
+     * The number of bytes actually received is stored in argument 3.
+     *
+     * It is possible for both bytes to be received and an APR_EOF or
+     * other error to be returned.
+     *
+     * APR_EINTR is never returned.
+     * </PRE>
+     * @param sock The socket to read the data from.
+     * @param buf The buffer to store the data in.
+     * @param offset Offset in the byte buffer.
+     * @param nbytes The number of bytes to read (-1) for full array.
+     * @param timeout The socket timeout in microseconds.
+     * @return the number of bytes received.
+     */
+    public static native int recvbt(long sock, ByteBuffer buf,
+                                    int offset, int nbytes, long timeout);
+    /**
+     * Read data from a network with timeout using internally set ByteBuffer
+     */
+    public static native int recvbbt(long sock,
+                                     int offset, int nbytes, long timeout);
+
+    /**
+     * @param from The apr_sockaddr_t to fill in the recipient info
+     * @param sock The socket to use
+     * @param flags The flags to use
+     * @param buf  The buffer to use
+     * @param offset Offset in the byte buffer.
+     * @param nbytes The number of bytes to read (-1) for full array.
+     * @return the number of bytes received.
+     */
+    public static native int recvfrom(long from, long sock, int flags,
+                                      byte[] buf, int offset, int nbytes);
+
+    /**
+     * Setup socket options for the specified socket
+     * @param sock The socket to set up.
+     * @param opt The option we would like to configure.  One of:
+     * <PRE>
+     * APR_SO_DEBUG      --  turn on debugging information
+     * APR_SO_KEEPALIVE  --  keep connections active
+     * APR_SO_LINGER     --  lingers on close if data is present
+     * APR_SO_NONBLOCK   --  Turns blocking on/off for socket
+     *                       When this option is enabled, use
+     *                       the APR_STATUS_IS_EAGAIN() macro to
+     *                       see if a send or receive function
+     *                       could not transfer data without
+     *                       blocking.
+     * APR_SO_REUSEADDR  --  The rules used in validating addresses
+     *                       supplied to bind should allow reuse
+     *                       of local addresses.
+     * APR_SO_SNDBUF     --  Set the SendBufferSize
+     * APR_SO_RCVBUF     --  Set the ReceiveBufferSize
+     * </PRE>
+     * @param on Value for the option.
+     */
+    public static native int optSet(long sock, int opt, int on);
+
+    /**
+     * Query socket options for the specified socket
+     * @param sock The socket to query
+     * @param opt The option we would like to query.  One of:
+     * <PRE>
+     * APR_SO_DEBUG      --  turn on debugging information
+     * APR_SO_KEEPALIVE  --  keep connections active
+     * APR_SO_LINGER     --  lingers on close if data is present
+     * APR_SO_NONBLOCK   --  Turns blocking on/off for socket
+     * APR_SO_REUSEADDR  --  The rules used in validating addresses
+     *                       supplied to bind should allow reuse
+     *                       of local addresses.
+     * APR_SO_SNDBUF     --  Set the SendBufferSize
+     * APR_SO_RCVBUF     --  Set the ReceiveBufferSize
+     * APR_SO_DISCONNECTED -- Query the disconnected state of the socket.
+     *                       (Currently only used on Windows)
+     * </PRE>
+     * @return Socket option returned on the call.
+     */
+    public static native int optGet(long sock, int opt)
+        throws Exception;
+
+    /**
+     * Setup socket timeout for the specified socket
+     * @param sock The socket to set up.
+     * @param t Value for the timeout in microseconds.
+     * <PRE>
+     * t > 0  -- read and write calls return APR_TIMEUP if specified time
+     *           elapses with no data read or written
+     * t == 0 -- read and write calls never block
+     * t < 0  -- read and write calls block
+     * </PRE>
+     */
+    public static native int timeoutSet(long sock, long t);
+
+    /**
+     * Query socket timeout for the specified socket
+     * @param sock The socket to query
+     * @return Socket timeout returned from the query.
+     */
+    public static native long timeoutGet(long sock)
+        throws Exception;
+
+    /**
+     * Send a file from an open file descriptor to a socket, along with
+     * optional headers and trailers.
+     * <br />
+     * This functions acts like a blocking write by default.  To change
+     *         this behavior, use apr_socket_timeout_set() or the
+     *         APR_SO_NONBLOCK socket option.
+     * The number of bytes actually sent is stored in the len parameter.
+     * The offset parameter is passed by reference for no reason; its
+     * value will never be modified by the apr_socket_sendfile() function.
+     * @param sock The socket to which we're writing
+     * @param file The open file from which to read
+     * @param headers Array containing the headers to send
+     * @param trailers Array containing the trailers to send
+     * @param offset Offset into the file where we should begin writing
+     * @param len Number of bytes to send from the file
+     * @param flags APR flags that are mapped to OS specific flags
+     * @return Number of bytes actually sent, including headers,
+     *         file, and trailers
+     *
+     */
+    public static native long sendfile(long sock, long file, byte [][] headers,
+                                       byte[][] trailers, long offset,
+                                       long len, int flags);
+
+    /**
+     * Send a file without header and trailer arrays.
+     */
+    public static native long sendfilen(long sock, long file, long offset,
+                                        long len, int flags);
+
+    /**
+     * Create a child pool from associated socket pool.
+     * @param thesocket The socket to use
+     */
+    public static native long pool(long thesocket)
+        throws Exception;
+
+    /**
+     * Private method for getting the socket struct members
+     * @param socket The socket to use
+     * @param what Struct member to obtain
+     * <PRE>
+     * SOCKET_GET_POOL  - The socket pool
+     * SOCKET_GET_IMPL  - The socket implementation object
+     * SOCKET_GET_APRS  - APR socket
+     * SOCKET_GET_TYPE  - Socket type
+     * </PRE>
+     * @return The structure member address
+     */
+    private static native long get(long socket, int what);
+
+    /**
+     * Set internal send ByteBuffer.
+     * This function will preset internal Java ByteBuffer for
+     * consecutive sendbb calls.
+     * @param sock The socket to use
+     * @param buf The ByteBuffer
+     */
+    public static native void setsbb(long sock, ByteBuffer buf);
+
+    /**
+     * Set internal receive ByteBuffer.
+     * This function will preset internal Java ByteBuffer for
+     * consecutive revcvbb/recvbbt calls.
+     * @param sock The socket to use
+     * @param buf The ByteBuffer
+     */
+    public static native void setrbb(long sock, ByteBuffer buf);
+
+    /**
+     * Set the data associated with the current socket.
+     * @param sock The currently open socket.
+     * @param data The user data to associate with the socket.
+     * @param key The key to associate with the data.
+     */
+      public static native int dataSet(long sock, String key, Object data);
+
+    /**
+     * Return the data associated with the current socket
+     * @param key The key to associate with the user data.
+     * @param sock The currently open socket.
+     * @return Data or null in case of error.
+     */
+     public static native Object dataGet(long sock, String key);
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Status.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Status.java
new file mode 100644
index 0000000..bbdec2d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Status.java
@@ -0,0 +1,265 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/** Status
+ *
+ * @author Mladen Turk
+ * @version $Id: Status.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public class Status {
+
+    /**
+     * APR_OS_START_ERROR is where the APR specific error values start.
+     */
+     public static final int APR_OS_START_ERROR   = 20000;
+    /**
+     * APR_OS_ERRSPACE_SIZE is the maximum number of errors you can fit
+     *    into one of the error/status ranges below -- except for
+     *    APR_OS_START_USERERR, which see.
+     */
+     public static final int APR_OS_ERRSPACE_SIZE = 50000;
+    /**
+     * APR_OS_START_STATUS is where the APR specific status codes start.
+     */
+     public static final int APR_OS_START_STATUS  = (APR_OS_START_ERROR + APR_OS_ERRSPACE_SIZE);
+
+    /**
+     * APR_OS_START_USERERR are reserved for applications that use APR that
+     *     layer their own error codes along with APR's.  Note that the
+     *     error immediately following this one is set ten times farther
+     *     away than usual, so that users of apr have a lot of room in
+     *     which to declare custom error codes.
+     */
+    public static final int APR_OS_START_USERERR  = (APR_OS_START_STATUS + APR_OS_ERRSPACE_SIZE);
+    /**
+     * APR_OS_START_USEERR is obsolete, defined for compatibility only.
+     * Use APR_OS_START_USERERR instead.
+     */
+    public static final int APR_OS_START_USEERR    = APR_OS_START_USERERR;
+    /**
+     * APR_OS_START_CANONERR is where APR versions of errno values are defined
+     *     on systems which don't have the corresponding errno.
+     */
+    public static final int APR_OS_START_CANONERR  = (APR_OS_START_USERERR + (APR_OS_ERRSPACE_SIZE * 10));
+
+    /**
+     * APR_OS_START_EAIERR folds EAI_ error codes from getaddrinfo() into
+     *     apr_status_t values.
+     */
+    public static final int APR_OS_START_EAIERR  = (APR_OS_START_CANONERR + APR_OS_ERRSPACE_SIZE);
+    /**
+     * APR_OS_START_SYSERR folds platform-specific system error values into
+     *     apr_status_t values.
+     */
+    public static final int APR_OS_START_SYSERR  = (APR_OS_START_EAIERR + APR_OS_ERRSPACE_SIZE);
+
+    /** no error. */
+    public static final int APR_SUCCESS = 0;
+
+    /**
+     * APR Error Values
+     * <PRE>
+     * <b>APR ERROR VALUES</b>
+     * APR_ENOSTAT      APR was unable to perform a stat on the file
+     * APR_ENOPOOL      APR was not provided a pool with which to allocate memory
+     * APR_EBADDATE     APR was given an invalid date
+     * APR_EINVALSOCK   APR was given an invalid socket
+     * APR_ENOPROC      APR was not given a process structure
+     * APR_ENOTIME      APR was not given a time structure
+     * APR_ENODIR       APR was not given a directory structure
+     * APR_ENOLOCK      APR was not given a lock structure
+     * APR_ENOPOLL      APR was not given a poll structure
+     * APR_ENOSOCKET    APR was not given a socket
+     * APR_ENOTHREAD    APR was not given a thread structure
+     * APR_ENOTHDKEY    APR was not given a thread key structure
+     * APR_ENOSHMAVAIL  There is no more shared memory available
+     * APR_EDSOOPEN     APR was unable to open the dso object.  For more
+     *                  information call apr_dso_error().
+     * APR_EGENERAL     General failure (specific information not available)
+     * APR_EBADIP       The specified IP address is invalid
+     * APR_EBADMASK     The specified netmask is invalid
+     * APR_ESYMNOTFOUND Could not find the requested symbol
+     * </PRE>
+     *
+     */
+    public static final int APR_ENOSTAT       = (APR_OS_START_ERROR + 1);
+    public static final int APR_ENOPOOL       = (APR_OS_START_ERROR + 2);
+    public static final int APR_EBADDATE      = (APR_OS_START_ERROR + 4);
+    public static final int APR_EINVALSOCK    = (APR_OS_START_ERROR + 5);
+    public static final int APR_ENOPROC       = (APR_OS_START_ERROR + 6);
+    public static final int APR_ENOTIME       = (APR_OS_START_ERROR + 7);
+    public static final int APR_ENODIR        = (APR_OS_START_ERROR + 8);
+    public static final int APR_ENOLOCK       = (APR_OS_START_ERROR + 9);
+    public static final int APR_ENOPOLL       = (APR_OS_START_ERROR + 10);
+    public static final int APR_ENOSOCKET     = (APR_OS_START_ERROR + 11);
+    public static final int APR_ENOTHREAD     = (APR_OS_START_ERROR + 12);
+    public static final int APR_ENOTHDKEY     = (APR_OS_START_ERROR + 13);
+    public static final int APR_EGENERAL      = (APR_OS_START_ERROR + 14);
+    public static final int APR_ENOSHMAVAIL   = (APR_OS_START_ERROR + 15);
+    public static final int APR_EBADIP        = (APR_OS_START_ERROR + 16);
+    public static final int APR_EBADMASK      = (APR_OS_START_ERROR + 17);
+    public static final int APR_EDSOOPEN      = (APR_OS_START_ERROR + 19);
+    public static final int APR_EABSOLUTE     = (APR_OS_START_ERROR + 20);
+    public static final int APR_ERELATIVE     = (APR_OS_START_ERROR + 21);
+    public static final int APR_EINCOMPLETE   = (APR_OS_START_ERROR + 22);
+    public static final int APR_EABOVEROOT    = (APR_OS_START_ERROR + 23);
+    public static final int APR_EBADPATH      = (APR_OS_START_ERROR + 24);
+    public static final int APR_EPATHWILD     = (APR_OS_START_ERROR + 25);
+    public static final int APR_ESYMNOTFOUND  = (APR_OS_START_ERROR + 26);
+    public static final int APR_EPROC_UNKNOWN = (APR_OS_START_ERROR + 27);
+    public static final int APR_ENOTENOUGHENTROPY = (APR_OS_START_ERROR + 28);
+
+    /** APR Status Values
+     * <PRE>
+     * <b>APR STATUS VALUES</b>
+     * APR_INCHILD        Program is currently executing in the child
+     * APR_INPARENT       Program is currently executing in the parent
+     * APR_DETACH         The thread is detached
+     * APR_NOTDETACH      The thread is not detached
+     * APR_CHILD_DONE     The child has finished executing
+     * APR_CHILD_NOTDONE  The child has not finished executing
+     * APR_TIMEUP         The operation did not finish before the timeout
+     * APR_INCOMPLETE     The operation was incomplete although some processing
+     *                    was performed and the results are partially valid
+     * APR_BADCH          Getopt found an option not in the option string
+     * APR_BADARG         Getopt found an option that is missing an argument
+     *                    and an argument was specified in the option string
+     * APR_EOF            APR has encountered the end of the file
+     * APR_NOTFOUND       APR was unable to find the socket in the poll structure
+     * APR_ANONYMOUS      APR is using anonymous shared memory
+     * APR_FILEBASED      APR is using a file name as the key to the shared memory
+     * APR_KEYBASED       APR is using a shared key as the key to the shared memory
+     * APR_EINIT          Ininitalizer value.  If no option has been found, but
+     *                    the status variable requires a value, this should be used
+     * APR_ENOTIMPL       The APR function has not been implemented on this
+     *                    platform, either because nobody has gotten to it yet,
+     *                    or the function is impossible on this platform.
+     * APR_EMISMATCH      Two passwords do not match.
+     * APR_EBUSY          The given lock was busy.
+     * </PRE>
+     *
+     */
+    public static final int APR_INCHILD       = (APR_OS_START_STATUS + 1);
+    public static final int APR_INPARENT      = (APR_OS_START_STATUS + 2);
+    public static final int APR_DETACH        = (APR_OS_START_STATUS + 3);
+    public static final int APR_NOTDETACH     = (APR_OS_START_STATUS + 4);
+    public static final int APR_CHILD_DONE    = (APR_OS_START_STATUS + 5);
+    public static final int APR_CHILD_NOTDONE = (APR_OS_START_STATUS + 6);
+    public static final int APR_TIMEUP        = (APR_OS_START_STATUS + 7);
+    public static final int APR_INCOMPLETE    = (APR_OS_START_STATUS + 8);
+    public static final int APR_BADCH         = (APR_OS_START_STATUS + 12);
+    public static final int APR_BADARG        = (APR_OS_START_STATUS + 13);
+    public static final int APR_EOF           = (APR_OS_START_STATUS + 14);
+    public static final int APR_NOTFOUND      = (APR_OS_START_STATUS + 15);
+    public static final int APR_ANONYMOUS     = (APR_OS_START_STATUS + 19);
+    public static final int APR_FILEBASED     = (APR_OS_START_STATUS + 20);
+    public static final int APR_KEYBASED      = (APR_OS_START_STATUS + 21);
+    public static final int APR_EINIT         = (APR_OS_START_STATUS + 22);
+    public static final int APR_ENOTIMPL      = (APR_OS_START_STATUS + 23);
+    public static final int APR_EMISMATCH     = (APR_OS_START_STATUS + 24);
+    public static final int APR_EBUSY         = (APR_OS_START_STATUS + 25);
+
+    public static final int TIMEUP            = (APR_OS_START_USERERR + 1);
+    public static final int EAGAIN            = (APR_OS_START_USERERR + 2);
+    public static final int EINTR             = (APR_OS_START_USERERR + 3);
+    public static final int EINPROGRESS       = (APR_OS_START_USERERR + 4);
+    public static final int ETIMEDOUT         = (APR_OS_START_USERERR + 5);
+
+    private static native boolean is(int err, int idx);
+    /**
+     * APR_STATUS_IS Status Value Tests
+     * <br /><b>Warning :</b> For any particular error condition, more than one of these tests
+     *      may match. This is because platform-specific error codes may not
+     *      always match the semantics of the POSIX codes these tests (and the
+     *      corresponding APR error codes) are named after. A notable example
+     *      are the APR_STATUS_IS_ENOENT and APR_STATUS_IS_ENOTDIR tests on
+     *      Win32 platforms. The programmer should always be aware of this and
+     *      adjust the order of the tests accordingly.
+     *
+     */
+    public static final boolean APR_STATUS_IS_ENOSTAT(int s)    { return is(s, 1); }
+    public static final boolean APR_STATUS_IS_ENOPOOL(int s)    { return is(s, 2); }
+    /* empty slot: +3 */
+    public static final boolean APR_STATUS_IS_EBADDATE(int s)   { return is(s, 4); }
+    public static final boolean APR_STATUS_IS_EINVALSOCK(int s) { return is(s, 5); }
+    public static final boolean APR_STATUS_IS_ENOPROC(int s)    { return is(s, 6); }
+    public static final boolean APR_STATUS_IS_ENOTIME(int s)    { return is(s, 7); }
+    public static final boolean APR_STATUS_IS_ENODIR(int s)     { return is(s, 8); }
+    public static final boolean APR_STATUS_IS_ENOLOCK(int s)    { return is(s, 9); }
+    public static final boolean APR_STATUS_IS_ENOPOLL(int s)    { return is(s, 10); }
+    public static final boolean APR_STATUS_IS_ENOSOCKET(int s)  { return is(s, 11); }
+    public static final boolean APR_STATUS_IS_ENOTHREAD(int s)  { return is(s, 12); }
+    public static final boolean APR_STATUS_IS_ENOTHDKEY(int s)  { return is(s, 13); }
+    public static final boolean APR_STATUS_IS_EGENERAL(int s)   { return is(s, 14); }
+    public static final boolean APR_STATUS_IS_ENOSHMAVAIL(int s){ return is(s, 15); }
+    public static final boolean APR_STATUS_IS_EBADIP(int s)     { return is(s, 16); }
+    public static final boolean APR_STATUS_IS_EBADMASK(int s)   { return is(s, 17); }
+    /* empty slot: +18 */
+    public static final boolean APR_STATUS_IS_EDSOPEN(int s)    { return is(s, 19); }
+    public static final boolean APR_STATUS_IS_EABSOLUTE(int s)  { return is(s, 20); }
+    public static final boolean APR_STATUS_IS_ERELATIVE(int s)  { return is(s, 21); }
+    public static final boolean APR_STATUS_IS_EINCOMPLETE(int s){ return is(s, 22); }
+    public static final boolean APR_STATUS_IS_EABOVEROOT(int s) { return is(s, 23); }
+    public static final boolean APR_STATUS_IS_EBADPATH(int s)   { return is(s, 24); }
+    public static final boolean APR_STATUS_IS_EPATHWILD(int s)  { return is(s, 25); }
+    public static final boolean APR_STATUS_IS_ESYMNOTFOUND(int s)      { return is(s, 26); }
+    public static final boolean APR_STATUS_IS_EPROC_UNKNOWN(int s)     { return is(s, 27); }
+    public static final boolean APR_STATUS_IS_ENOTENOUGHENTROPY(int s) { return is(s, 28); }
+
+    /*
+     * APR_Error
+     */
+    public static final boolean APR_STATUS_IS_INCHILD(int s)    { return is(s, 51); }
+    public static final boolean APR_STATUS_IS_INPARENT(int s)   { return is(s, 52); }
+    public static final boolean APR_STATUS_IS_DETACH(int s)     { return is(s, 53); }
+    public static final boolean APR_STATUS_IS_NOTDETACH(int s)  { return is(s, 54); }
+    public static final boolean APR_STATUS_IS_CHILD_DONE(int s) { return is(s, 55); }
+    public static final boolean APR_STATUS_IS_CHILD_NOTDONE(int s)  { return is(s, 56); }
+    public static final boolean APR_STATUS_IS_TIMEUP(int s)     { return is(s, 57); }
+    public static final boolean APR_STATUS_IS_INCOMPLETE(int s) { return is(s, 58); }
+    /* empty slot: +9 */
+    /* empty slot: +10 */
+    /* empty slot: +11 */
+    public static final boolean APR_STATUS_IS_BADCH(int s)      { return is(s, 62); }
+    public static final boolean APR_STATUS_IS_BADARG(int s)     { return is(s, 63); }
+    public static final boolean APR_STATUS_IS_EOF(int s)        { return is(s, 64); }
+    public static final boolean APR_STATUS_IS_NOTFOUND(int s)   { return is(s, 65); }
+    /* empty slot: +16 */
+    /* empty slot: +17 */
+    /* empty slot: +18 */
+    public static final boolean APR_STATUS_IS_ANONYMOUS(int s)  { return is(s, 69); }
+    public static final boolean APR_STATUS_IS_FILEBASED(int s)  { return is(s, 70); }
+    public static final boolean APR_STATUS_IS_KEYBASED(int s)   { return is(s, 71); }
+    public static final boolean APR_STATUS_IS_EINIT(int s)      { return is(s, 72); }
+    public static final boolean APR_STATUS_IS_ENOTIMPL(int s)   { return is(s, 73); }
+    public static final boolean APR_STATUS_IS_EMISMATCH(int s)  { return is(s, 74); }
+    public static final boolean APR_STATUS_IS_EBUSY(int s)      { return is(s, 75); }
+
+    /* Socket errors */
+    public static final boolean APR_STATUS_IS_EAGAIN(int s)     { return is(s, 90); }
+    public static final boolean APR_STATUS_IS_ETIMEDOUT(int s)  { return is(s, 91); }
+    public static final boolean APR_STATUS_IS_ECONNABORTED(int s) { return is(s, 92); }
+    public static final boolean APR_STATUS_IS_ECONNRESET(int s)   { return is(s, 93); }
+    public static final boolean APR_STATUS_IS_EINPROGRESS(int s)  { return is(s, 94); }
+    public static final boolean APR_STATUS_IS_EINTR(int s)      { return is(s, 95); }
+    public static final boolean APR_STATUS_IS_ENOTSOCK(int s)   { return is(s, 96); }
+    public static final boolean APR_STATUS_IS_EINVAL(int s)     { return is(s, 97); }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Stdlib.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Stdlib.java
new file mode 100644
index 0000000..adb4da5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Stdlib.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.tomcat.jni;
+
+/** Stdlib
+ *
+ * @author Mladen Turk
+ * @version $Id: Stdlib.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public class Stdlib {
+
+    /**
+     * Read from plain memory
+     * @param dst Destination byte array
+     * @param src Source memory address
+     * @param sz Number of bytes to copy.
+     */
+    public static native boolean memread(byte [] dst, long src, int sz);
+
+    /**
+     * Write to plain memory
+     * @param dst Destination memory address
+     * @param src Source byte array
+     * @param sz Number of bytes to copy.
+     */
+    public static native boolean memwrite(long dst, byte [] src, int sz);
+
+    /**
+     * Sets buffers to a specified character
+     * @param dst Destination memory address
+     * @param c Character to set.
+     * @param sz Number of characters.
+     */
+    public static native boolean memset(long dst, int c, int sz);
+
+    /**
+     * Allocates memory blocks.
+     * @param sz Bytes to allocate.
+     */
+    public static native long malloc(int sz);
+
+    /**
+     * Reallocate memory blocks.
+     * @param mem Pointer to previously allocated memory block.
+     * @param sz New size in bytes.
+     */
+    public static native long realloc(long mem, int sz);
+
+    /**
+     * Allocates an array in memory with elements initialized to 0.
+     * @param num Number of elements.
+     * @param sz Length in bytes of each element.
+     */
+    public static native long calloc(int num, int sz);
+
+    /**
+     * Deallocates or frees a memory block.
+     * @param mem Previously allocated memory block to be freed.
+     */
+    public static native void free(long mem);
+
+    /**
+     * Get current process pid.
+     * @return current pid or < 1 in case of error.
+     */
+    public static native int getpid();
+
+    /**
+     * Get current process parent pid.
+     * @return parent pid or < 1 in case of error.
+     */
+    public static native int getppid();
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Time.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Time.java
new file mode 100644
index 0000000..b9f3442
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/Time.java
@@ -0,0 +1,74 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.jni;
+
+/** Time
+ *
+ * @author Mladen Turk
+ * @version $Id: Time.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public class Time {
+
+    /** number of microseconds per second */
+    public static final long APR_USEC_PER_SEC  = 1000000L;
+    /** number of milliseconds per microsecond */
+    public static final long APR_MSEC_PER_USEC = 1000L;
+
+    /** @return apr_time_t as a second */
+    public static long sec(long t)
+    {
+        return t / APR_USEC_PER_SEC;
+    }
+
+    /** @return apr_time_t as a msec */
+    public static long msec(long t)
+    {
+        return t / APR_MSEC_PER_USEC;
+    }
+
+    /**
+     * number of microseconds since 00:00:00 January 1, 1970 UTC
+     * @return the current time
+     */
+    public static native long now();
+
+    /**
+     * Formats dates in the RFC822
+     * format in an efficient manner.
+     * @param t the time to convert
+     */
+    public static native String rfc822(long t);
+
+    /**
+     * Formats dates in the ctime() format
+     * in an efficient manner.
+     * Unlike ANSI/ISO C ctime(), apr_ctime() does not include
+     * a \n at the end of the string.
+     * @param t the time to convert
+     */
+    public static native String ctime(long t);
+
+    /**
+     * Sleep for the specified number of micro-seconds.
+     * <br /><b>Warning :</b> May sleep for longer than the specified time.
+     * @param t desired amount of time to sleep.
+     */
+    public static native void sleep(long t);
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/User.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/User.java
new file mode 100644
index 0000000..c7ebb5d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/jni/User.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.tomcat.jni;
+
+/** User
+ *
+ * @author Mladen Turk
+ * @version $Id: User.java,v 1.1 2011/06/28 21:08:26 rherrmann Exp $
+ */
+
+public class User {
+
+    /**
+     * Get the userid (and groupid) of the calling process
+     * This function is available only if APR_HAS_USER is defined.
+     * @param p The pool from which to allocate working space
+     * @return Returns the user id
+     */
+     public static native long uidCurrent(long p)
+        throws Error;
+
+    /**
+     * Get the groupid of the calling process
+     * This function is available only if APR_HAS_USER is defined.
+     * @param p The pool from which to allocate working space
+     * @return Returns the group id
+     */
+     public static native long gidCurrent(long p)
+        throws Error;
+
+
+    /**
+     * Get the userid for the specified username
+     * This function is available only if APR_HAS_USER is defined.
+     * @param username The username to lookup
+     * @param p The pool from which to allocate working space
+     * @return Returns the user id
+     */
+     public static native long uid(String username, long p)
+        throws Error;
+
+    /**
+     * Get the groupid for the specified username
+     * This function is available only if APR_HAS_USER is defined.
+     * @param username The username to lookup
+     * @param p The pool from which to allocate working space
+     * @return  Returns the user's group id
+     */
+     public static native long usergid(String username, long p)
+        throws Error;
+
+    /**
+     * Get the groupid for a specified group name
+     * This function is available only if APR_HAS_USER is defined.
+     * @param groupname The group name to look up
+     * @param p The pool from which to allocate working space
+     * @return  Returns the user's group id
+     */
+     public static native long gid(String groupname, long p)
+        throws Error;
+
+    /**
+     * Get the user name for a specified userid
+     * This function is available only if APR_HAS_USER is defined.
+     * @param userid The userid
+     * @param p The pool from which to allocate the string
+     * @return New string containing user name
+     */
+     public static native String username(long userid, long p)
+        throws Error;
+
+    /**
+     * Get the group name for a specified groupid
+     * This function is available only if APR_HAS_USER is defined.
+     * @param groupid The groupid
+     * @param p The pool from which to allocate the string
+     * @return New string containing group name
+     */
+     public static native String groupname(long groupid, long p)
+        throws Error;
+
+    /**
+     * Compare two user identifiers for equality.
+     * This function is available only if APR_HAS_USER is defined.
+     * @param left One uid to test
+     * @param right Another uid to test
+     * @return APR_SUCCESS if the apr_uid_t structures identify the same user,
+     * APR_EMISMATCH if not, APR_BADARG if an apr_uid_t is invalid.
+     */
+     public static native int uidcompare(long left, long right);
+
+    /**
+     * Compare two group identifiers for equality.
+     * This function is available only if APR_HAS_USER is defined.
+     * @param left One gid to test
+     * @param right Another gid to test
+     * @return APR_SUCCESS if the apr_gid_t structures identify the same group,
+     * APR_EMISMATCH if not, APR_BADARG if an apr_gid_t is invalid.
+     */
+     public static native int gidcompare(long left, long right);
+
+    /**
+     * Get the home directory for the named user
+     * This function is available only if APR_HAS_USER is defined.
+     * @param username The named user
+     * @param p The pool from which to allocate the string
+     * @return New string containing directory name
+     */
+     public static native String homepath(String username, long p)
+        throws Error;
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/Constants.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/Constants.java
new file mode 100644
index 0000000..ac89014
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/Constants.java
@@ -0,0 +1,553 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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;
+
+/**
+ * Constants for the project, mostly defined in the JVM specification.
+ *
+ * @version $Id: Constants.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ */
+public interface Constants {
+
+  /** One of the access flags for fields, methods, or classes.
+   */
+  public static final short ACC_FINAL        = 0x0010;
+
+  /** One of the access flags for fields, methods, or classes.
+   */
+  public static final short ACC_INTERFACE    = 0x0200;
+
+  /** One of the access flags for fields, methods, or classes.
+   */
+  public static final short ACC_ABSTRACT     = 0x0400;
+
+  /** One of the access flags for fields, methods, or classes.
+   */
+  public static final short ACC_ENUM         = 0x4000;
+
+  // Applies to classes compiled by new compilers only
+  /** One of the access flags for fields, methods, or classes.
+   */
+  public static final short ACC_SUPER        = 0x0020;
+
+  /** One of the access flags for fields, methods, or classes.
+   */
+  public static final short MAX_ACC_FLAG     = ACC_ENUM;
+
+  /** The names of the access flags. */
+  public static final String[] ACCESS_NAMES = {
+    "public", "private", "protected", "static", "final", "synchronized",
+    "volatile", "transient", "native", "interface", "abstract", "strictfp",
+    "synthetic", "annotation", "enum"
+  };
+
+  /** Marks a constant pool entry as type UTF-8.  */
+  public static final byte CONSTANT_Utf8               = 1;
+
+  /** Marks a constant pool entry as type Integer.  */
+  public static final byte CONSTANT_Integer            = 3;
+
+  /** Marks a constant pool entry as type Float.  */
+  public static final byte CONSTANT_Float              = 4;
+
+  /** Marks a constant pool entry as type Long.  */
+  public static final byte CONSTANT_Long               = 5;
+
+  /** Marks a constant pool entry as type Double.  */
+  public static final byte CONSTANT_Double             = 6;
+
+  /** Marks a constant pool entry as a Class.  */
+  public static final byte CONSTANT_Class              = 7;
+
+  /** Marks a constant pool entry as a Field Reference.  */
+  public static final byte CONSTANT_Fieldref           = 9;
+
+  /** Marks a constant pool entry as type String.  */
+  public static final byte CONSTANT_String             = 8;
+
+  /** Marks a constant pool entry as a Method Reference.  */
+  public static final byte CONSTANT_Methodref          = 10;
+
+  /** Marks a constant pool entry as an Interface Method Reference.  */
+  public static final byte CONSTANT_InterfaceMethodref = 11;
+
+  /** Marks a constant pool entry as a name and type.  */
+  public static final byte CONSTANT_NameAndType        = 12;
+
+  /** The names of the types of entries in a constant pool. */
+  public static final String[] CONSTANT_NAMES = {
+    "", "CONSTANT_Utf8", "", "CONSTANT_Integer",
+    "CONSTANT_Float", "CONSTANT_Long", "CONSTANT_Double",
+    "CONSTANT_Class", "CONSTANT_String", "CONSTANT_Fieldref",
+    "CONSTANT_Methodref", "CONSTANT_InterfaceMethodref",
+    "CONSTANT_NameAndType" };
+  
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short LDC              = 18;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short LDC_W            = 19;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short LDC2_W           = 20;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short ILOAD            = 21;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short LLOAD            = 22;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short FLOAD            = 23;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short DLOAD            = 24;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short ALOAD            = 25;
+
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short ISTORE           = 54;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short LSTORE           = 55;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short FSTORE           = 56;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short DSTORE           = 57;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short ASTORE           = 58;
+  
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short IINC             = 132;
+  
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short IFEQ             = 153;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short IFNE             = 154;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short IFLT             = 155;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short IFGE             = 156;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short IFGT             = 157;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short IFLE             = 158;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short IF_ICMPEQ        = 159;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short IF_ICMPNE        = 160;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short IF_ICMPLT        = 161;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short IF_ICMPGE        = 162;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short IF_ICMPGT        = 163;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short IF_ICMPLE        = 164;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short IF_ACMPEQ        = 165;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short IF_ACMPNE        = 166;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short GOTO             = 167;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short JSR              = 168;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short RET              = 169;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short TABLESWITCH      = 170;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short LOOKUPSWITCH     = 171;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short GETSTATIC        = 178;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short PUTSTATIC        = 179;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short GETFIELD         = 180;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short PUTFIELD         = 181;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short INVOKEVIRTUAL    = 182;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short INVOKESPECIAL    = 183;
+  
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short INVOKESTATIC     = 184;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short INVOKEINTERFACE  = 185;
+  
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short NEW              = 187;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short NEWARRAY         = 188;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short ANEWARRAY        = 189;
+  
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short CHECKCAST        = 192;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short INSTANCEOF       = 193;
+  
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short WIDE             = 196;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short MULTIANEWARRAY   = 197;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short IFNULL           = 198;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short IFNONNULL        = 199;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short GOTO_W           = 200;
+  /** Java VM opcode.
+   * @see <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc.html">Opcode definitions in The Java Virtual Machine Specification</a> */
+  public static final short JSR_W            = 201;
+
+  /** Illegal opcode. */
+  public static final short  UNDEFINED      = -1;
+  /** Illegal opcode. */
+  public static final short  UNPREDICTABLE  = -2;
+  /** Illegal opcode. */
+  public static final short  RESERVED       = -3;
+  /** Mnemonic for an illegal opcode. */
+  public static final String ILLEGAL_OPCODE = "<illegal opcode>";
+  /** Mnemonic for an illegal type. */
+  public static final String ILLEGAL_TYPE   = "<illegal type>";
+
+  /** Byte data type. */
+  public static final byte T_BYTE    = 8;
+  /** Short data type. */
+  public static final byte T_SHORT   = 9;
+  /** Int data type. */
+  public static final byte T_INT     = 10;
+  
+  
+  /** Unknown data type. */
+  public static final byte T_UNKNOWN   = 15;
+
+  /** The primitive type names corresponding to the T_XX constants,
+   * e.g., TYPE_NAMES[T_INT] = "int"
+   */
+  public static final String[] TYPE_NAMES = {
+    ILLEGAL_TYPE, ILLEGAL_TYPE,  ILLEGAL_TYPE, ILLEGAL_TYPE,
+    "boolean", "char", "float", "double", "byte", "short", "int", "long",
+    "void", "array", "object", "unknown", "address"
+  };
+
+
+  /**
+   * Number of byte code operands for each opcode, i.e., number of bytes after the tag byte
+   * itself.  Indexed by opcode, so NO_OF_OPERANDS[BIPUSH] = the number of operands for a bipush
+   * instruction.
+   */
+  public static final short[] NO_OF_OPERANDS = {
+    0/*nop*/, 0/*aconst_null*/, 0/*iconst_m1*/, 0/*iconst_0*/,
+    0/*iconst_1*/, 0/*iconst_2*/, 0/*iconst_3*/, 0/*iconst_4*/,
+    0/*iconst_5*/, 0/*lconst_0*/, 0/*lconst_1*/, 0/*fconst_0*/,
+    0/*fconst_1*/, 0/*fconst_2*/, 0/*dconst_0*/, 0/*dconst_1*/,
+    1/*bipush*/, 2/*sipush*/, 1/*ldc*/, 2/*ldc_w*/, 2/*ldc2_w*/,
+    1/*iload*/, 1/*lload*/, 1/*fload*/, 1/*dload*/, 1/*aload*/,
+    0/*iload_0*/, 0/*iload_1*/, 0/*iload_2*/, 0/*iload_3*/,
+    0/*lload_0*/, 0/*lload_1*/, 0/*lload_2*/, 0/*lload_3*/,
+    0/*fload_0*/, 0/*fload_1*/, 0/*fload_2*/, 0/*fload_3*/,
+    0/*dload_0*/, 0/*dload_1*/, 0/*dload_2*/, 0/*dload_3*/,
+    0/*aload_0*/, 0/*aload_1*/, 0/*aload_2*/, 0/*aload_3*/,
+    0/*iaload*/, 0/*laload*/, 0/*faload*/, 0/*daload*/,
+    0/*aaload*/, 0/*baload*/, 0/*caload*/, 0/*saload*/,
+    1/*istore*/, 1/*lstore*/, 1/*fstore*/, 1/*dstore*/,
+    1/*astore*/, 0/*istore_0*/, 0/*istore_1*/, 0/*istore_2*/,
+    0/*istore_3*/, 0/*lstore_0*/, 0/*lstore_1*/, 0/*lstore_2*/,
+    0/*lstore_3*/, 0/*fstore_0*/, 0/*fstore_1*/, 0/*fstore_2*/,
+    0/*fstore_3*/, 0/*dstore_0*/, 0/*dstore_1*/, 0/*dstore_2*/,
+    0/*dstore_3*/, 0/*astore_0*/, 0/*astore_1*/, 0/*astore_2*/,
+    0/*astore_3*/, 0/*iastore*/, 0/*lastore*/, 0/*fastore*/,
+    0/*dastore*/, 0/*aastore*/, 0/*bastore*/, 0/*castore*/,
+    0/*sastore*/, 0/*pop*/, 0/*pop2*/, 0/*dup*/, 0/*dup_x1*/,
+    0/*dup_x2*/, 0/*dup2*/, 0/*dup2_x1*/, 0/*dup2_x2*/, 0/*swap*/,
+    0/*iadd*/, 0/*ladd*/, 0/*fadd*/, 0/*dadd*/, 0/*isub*/,
+    0/*lsub*/, 0/*fsub*/, 0/*dsub*/, 0/*imul*/, 0/*lmul*/,
+    0/*fmul*/, 0/*dmul*/, 0/*idiv*/, 0/*ldiv*/, 0/*fdiv*/,
+    0/*ddiv*/, 0/*irem*/, 0/*lrem*/, 0/*frem*/, 0/*drem*/,
+    0/*ineg*/, 0/*lneg*/, 0/*fneg*/, 0/*dneg*/, 0/*ishl*/,
+    0/*lshl*/, 0/*ishr*/, 0/*lshr*/, 0/*iushr*/, 0/*lushr*/,
+    0/*iand*/, 0/*land*/, 0/*ior*/, 0/*lor*/, 0/*ixor*/, 0/*lxor*/,
+    2/*iinc*/, 0/*i2l*/, 0/*i2f*/, 0/*i2d*/, 0/*l2i*/, 0/*l2f*/,
+    0/*l2d*/, 0/*f2i*/, 0/*f2l*/, 0/*f2d*/, 0/*d2i*/, 0/*d2l*/,
+    0/*d2f*/, 0/*i2b*/, 0/*i2c*/, 0/*i2s*/, 0/*lcmp*/, 0/*fcmpl*/,
+    0/*fcmpg*/, 0/*dcmpl*/, 0/*dcmpg*/, 2/*ifeq*/, 2/*ifne*/,
+    2/*iflt*/, 2/*ifge*/, 2/*ifgt*/, 2/*ifle*/, 2/*if_icmpeq*/,
+    2/*if_icmpne*/, 2/*if_icmplt*/, 2/*if_icmpge*/, 2/*if_icmpgt*/,
+    2/*if_icmple*/, 2/*if_acmpeq*/, 2/*if_acmpne*/, 2/*goto*/,
+    2/*jsr*/, 1/*ret*/, UNPREDICTABLE/*tableswitch*/, UNPREDICTABLE/*lookupswitch*/,
+    0/*ireturn*/, 0/*lreturn*/, 0/*freturn*/,
+    0/*dreturn*/, 0/*areturn*/, 0/*return*/,
+    2/*getstatic*/, 2/*putstatic*/, 2/*getfield*/,
+    2/*putfield*/, 2/*invokevirtual*/, 2/*invokespecial*/, 2/*invokestatic*/,
+    4/*invokeinterface*/, UNDEFINED, 2/*new*/,
+    1/*newarray*/, 2/*anewarray*/,
+    0/*arraylength*/, 0/*athrow*/, 2/*checkcast*/,
+    2/*instanceof*/, 0/*monitorenter*/,
+    0/*monitorexit*/, UNPREDICTABLE/*wide*/, 3/*multianewarray*/,
+    2/*ifnull*/, 2/*ifnonnull*/, 4/*goto_w*/,
+    4/*jsr_w*/, 0/*breakpoint*/, UNDEFINED,
+    UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED,
+    UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED,
+    UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED,
+    UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED,
+    UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED,
+    UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED,
+    UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED,
+    UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED,
+    UNDEFINED, UNDEFINED, RESERVED/*impdep1*/, RESERVED/*impdep2*/
+  };
+
+  /**
+   * How the byte code operands are to be interpreted for each opcode.
+   * Indexed by opcode.  TYPE_OF_OPERANDS[ILOAD] = an array of shorts
+   * describing the data types for the instruction.
+   */
+  public static final short[][] TYPE_OF_OPERANDS = {
+    {}/*nop*/, {}/*aconst_null*/, {}/*iconst_m1*/, {}/*iconst_0*/,
+    {}/*iconst_1*/, {}/*iconst_2*/, {}/*iconst_3*/, {}/*iconst_4*/,
+    {}/*iconst_5*/, {}/*lconst_0*/, {}/*lconst_1*/, {}/*fconst_0*/,
+    {}/*fconst_1*/, {}/*fconst_2*/, {}/*dconst_0*/, {}/*dconst_1*/,
+    {T_BYTE}/*bipush*/, {T_SHORT}/*sipush*/, {T_BYTE}/*ldc*/,
+    {T_SHORT}/*ldc_w*/, {T_SHORT}/*ldc2_w*/,
+    {T_BYTE}/*iload*/, {T_BYTE}/*lload*/, {T_BYTE}/*fload*/,
+    {T_BYTE}/*dload*/, {T_BYTE}/*aload*/, {}/*iload_0*/,
+    {}/*iload_1*/, {}/*iload_2*/, {}/*iload_3*/, {}/*lload_0*/,
+    {}/*lload_1*/, {}/*lload_2*/, {}/*lload_3*/, {}/*fload_0*/,
+    {}/*fload_1*/, {}/*fload_2*/, {}/*fload_3*/, {}/*dload_0*/,
+    {}/*dload_1*/, {}/*dload_2*/, {}/*dload_3*/, {}/*aload_0*/,
+    {}/*aload_1*/, {}/*aload_2*/, {}/*aload_3*/, {}/*iaload*/,
+    {}/*laload*/, {}/*faload*/, {}/*daload*/, {}/*aaload*/,
+    {}/*baload*/, {}/*caload*/, {}/*saload*/, {T_BYTE}/*istore*/,
+    {T_BYTE}/*lstore*/, {T_BYTE}/*fstore*/, {T_BYTE}/*dstore*/,
+    {T_BYTE}/*astore*/, {}/*istore_0*/, {}/*istore_1*/,
+    {}/*istore_2*/, {}/*istore_3*/, {}/*lstore_0*/, {}/*lstore_1*/,
+    {}/*lstore_2*/, {}/*lstore_3*/, {}/*fstore_0*/, {}/*fstore_1*/,
+    {}/*fstore_2*/, {}/*fstore_3*/, {}/*dstore_0*/, {}/*dstore_1*/,
+    {}/*dstore_2*/, {}/*dstore_3*/, {}/*astore_0*/, {}/*astore_1*/,
+    {}/*astore_2*/, {}/*astore_3*/, {}/*iastore*/, {}/*lastore*/,
+    {}/*fastore*/, {}/*dastore*/, {}/*aastore*/, {}/*bastore*/,
+    {}/*castore*/, {}/*sastore*/, {}/*pop*/, {}/*pop2*/, {}/*dup*/,
+    {}/*dup_x1*/, {}/*dup_x2*/, {}/*dup2*/, {}/*dup2_x1*/,
+    {}/*dup2_x2*/, {}/*swap*/, {}/*iadd*/, {}/*ladd*/, {}/*fadd*/,
+    {}/*dadd*/, {}/*isub*/, {}/*lsub*/, {}/*fsub*/, {}/*dsub*/,
+    {}/*imul*/, {}/*lmul*/, {}/*fmul*/, {}/*dmul*/, {}/*idiv*/,
+    {}/*ldiv*/, {}/*fdiv*/, {}/*ddiv*/, {}/*irem*/, {}/*lrem*/,
+    {}/*frem*/, {}/*drem*/, {}/*ineg*/, {}/*lneg*/, {}/*fneg*/,
+    {}/*dneg*/, {}/*ishl*/, {}/*lshl*/, {}/*ishr*/, {}/*lshr*/,
+    {}/*iushr*/, {}/*lushr*/, {}/*iand*/, {}/*land*/, {}/*ior*/,
+    {}/*lor*/, {}/*ixor*/, {}/*lxor*/, {T_BYTE, T_BYTE}/*iinc*/,
+    {}/*i2l*/, {}/*i2f*/, {}/*i2d*/, {}/*l2i*/, {}/*l2f*/, {}/*l2d*/,
+    {}/*f2i*/, {}/*f2l*/, {}/*f2d*/, {}/*d2i*/, {}/*d2l*/, {}/*d2f*/,
+    {}/*i2b*/, {}/*i2c*/,{}/*i2s*/, {}/*lcmp*/, {}/*fcmpl*/,
+    {}/*fcmpg*/, {}/*dcmpl*/, {}/*dcmpg*/, {T_SHORT}/*ifeq*/,
+    {T_SHORT}/*ifne*/, {T_SHORT}/*iflt*/, {T_SHORT}/*ifge*/,
+    {T_SHORT}/*ifgt*/, {T_SHORT}/*ifle*/, {T_SHORT}/*if_icmpeq*/,
+    {T_SHORT}/*if_icmpne*/, {T_SHORT}/*if_icmplt*/,
+    {T_SHORT}/*if_icmpge*/, {T_SHORT}/*if_icmpgt*/,
+    {T_SHORT}/*if_icmple*/, {T_SHORT}/*if_acmpeq*/,
+    {T_SHORT}/*if_acmpne*/, {T_SHORT}/*goto*/, {T_SHORT}/*jsr*/,
+    {T_BYTE}/*ret*/, {}/*tableswitch*/, {}/*lookupswitch*/,
+    {}/*ireturn*/, {}/*lreturn*/, {}/*freturn*/, {}/*dreturn*/,
+    {}/*areturn*/, {}/*return*/, {T_SHORT}/*getstatic*/,
+    {T_SHORT}/*putstatic*/, {T_SHORT}/*getfield*/,
+    {T_SHORT}/*putfield*/, {T_SHORT}/*invokevirtual*/,
+    {T_SHORT}/*invokespecial*/, {T_SHORT}/*invokestatic*/,
+    {T_SHORT, T_BYTE, T_BYTE}/*invokeinterface*/, {},
+    {T_SHORT}/*new*/, {T_BYTE}/*newarray*/,
+    {T_SHORT}/*anewarray*/, {}/*arraylength*/, {}/*athrow*/,
+    {T_SHORT}/*checkcast*/, {T_SHORT}/*instanceof*/,
+    {}/*monitorenter*/, {}/*monitorexit*/, {T_BYTE}/*wide*/,
+    {T_SHORT, T_BYTE}/*multianewarray*/, {T_SHORT}/*ifnull*/,
+    {T_SHORT}/*ifnonnull*/, {T_INT}/*goto_w*/, {T_INT}/*jsr_w*/,
+    {}/*breakpoint*/, {}, {}, {}, {}, {}, {}, {},
+    {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},
+    {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},
+    {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},
+    {}/*impdep1*/, {}/*impdep2*/
+  };
+
+  /**
+   * Names of opcodes.  Indexed by opcode.  OPCODE_NAMES[ALOAD] = "aload".
+   */ 
+  public static final String[] OPCODE_NAMES = {
+    "nop", "aconst_null", "iconst_m1", "iconst_0", "iconst_1",
+    "iconst_2", "iconst_3", "iconst_4", "iconst_5", "lconst_0",
+    "lconst_1", "fconst_0", "fconst_1", "fconst_2", "dconst_0",
+    "dconst_1", "bipush", "sipush", "ldc", "ldc_w", "ldc2_w", "iload",
+    "lload", "fload", "dload", "aload", "iload_0", "iload_1", "iload_2",
+    "iload_3", "lload_0", "lload_1", "lload_2", "lload_3", "fload_0",
+    "fload_1", "fload_2", "fload_3", "dload_0", "dload_1", "dload_2",
+    "dload_3", "aload_0", "aload_1", "aload_2", "aload_3", "iaload",
+    "laload", "faload", "daload", "aaload", "baload", "caload", "saload",
+    "istore", "lstore", "fstore", "dstore", "astore", "istore_0",
+    "istore_1", "istore_2", "istore_3", "lstore_0", "lstore_1",
+    "lstore_2", "lstore_3", "fstore_0", "fstore_1", "fstore_2",
+    "fstore_3", "dstore_0", "dstore_1", "dstore_2", "dstore_3",
+    "astore_0", "astore_1", "astore_2", "astore_3", "iastore", "lastore",
+    "fastore", "dastore", "aastore", "bastore", "castore", "sastore",
+    "pop", "pop2", "dup", "dup_x1", "dup_x2", "dup2", "dup2_x1",
+    "dup2_x2", "swap", "iadd", "ladd", "fadd", "dadd", "isub", "lsub",
+    "fsub", "dsub", "imul", "lmul", "fmul", "dmul", "idiv", "ldiv",
+    "fdiv", "ddiv", "irem", "lrem", "frem", "drem", "ineg", "lneg",
+    "fneg", "dneg", "ishl", "lshl", "ishr", "lshr", "iushr", "lushr",
+    "iand", "land", "ior", "lor", "ixor", "lxor", "iinc", "i2l", "i2f",
+    "i2d", "l2i", "l2f", "l2d", "f2i", "f2l", "f2d", "d2i", "d2l", "d2f",
+    "i2b", "i2c", "i2s", "lcmp", "fcmpl", "fcmpg",
+    "dcmpl", "dcmpg", "ifeq", "ifne", "iflt", "ifge", "ifgt", "ifle",
+    "if_icmpeq", "if_icmpne", "if_icmplt", "if_icmpge", "if_icmpgt",
+    "if_icmple", "if_acmpeq", "if_acmpne", "goto", "jsr", "ret",
+    "tableswitch", "lookupswitch", "ireturn", "lreturn", "freturn",
+    "dreturn", "areturn", "return", "getstatic", "putstatic", "getfield",
+    "putfield", "invokevirtual", "invokespecial", "invokestatic",
+    "invokeinterface", ILLEGAL_OPCODE, "new", "newarray", "anewarray",
+    "arraylength", "athrow", "checkcast", "instanceof", "monitorenter",
+    "monitorexit", "wide", "multianewarray", "ifnull", "ifnonnull",
+    "goto_w", "jsr_w", "breakpoint", ILLEGAL_OPCODE, ILLEGAL_OPCODE,
+    ILLEGAL_OPCODE, ILLEGAL_OPCODE, ILLEGAL_OPCODE, ILLEGAL_OPCODE,
+    ILLEGAL_OPCODE, ILLEGAL_OPCODE, ILLEGAL_OPCODE, ILLEGAL_OPCODE,
+    ILLEGAL_OPCODE, ILLEGAL_OPCODE, ILLEGAL_OPCODE, ILLEGAL_OPCODE,
+    ILLEGAL_OPCODE, ILLEGAL_OPCODE, ILLEGAL_OPCODE, ILLEGAL_OPCODE,
+    ILLEGAL_OPCODE, ILLEGAL_OPCODE, ILLEGAL_OPCODE, ILLEGAL_OPCODE,
+    ILLEGAL_OPCODE, ILLEGAL_OPCODE, ILLEGAL_OPCODE, ILLEGAL_OPCODE,
+    ILLEGAL_OPCODE, ILLEGAL_OPCODE, ILLEGAL_OPCODE, ILLEGAL_OPCODE,
+    ILLEGAL_OPCODE, ILLEGAL_OPCODE, ILLEGAL_OPCODE, ILLEGAL_OPCODE,
+    ILLEGAL_OPCODE, ILLEGAL_OPCODE, ILLEGAL_OPCODE, ILLEGAL_OPCODE,
+    ILLEGAL_OPCODE, ILLEGAL_OPCODE, ILLEGAL_OPCODE, ILLEGAL_OPCODE,
+    ILLEGAL_OPCODE, ILLEGAL_OPCODE, ILLEGAL_OPCODE, ILLEGAL_OPCODE,
+    ILLEGAL_OPCODE, ILLEGAL_OPCODE, ILLEGAL_OPCODE, ILLEGAL_OPCODE,
+    ILLEGAL_OPCODE, "impdep1", "impdep2"
+  };
+
+
+  /** Attributes and their corresponding names.
+   */
+  public static final byte ATTR_UNKNOWN                                 = -1;
+  public static final byte ATTR_SOURCE_FILE                             = 0;
+  public static final byte ATTR_CONSTANT_VALUE                          = 1;
+  public static final byte ATTR_CODE                                    = 2;
+  public static final byte ATTR_EXCEPTIONS                              = 3;
+  public static final byte ATTR_LINE_NUMBER_TABLE                       = 4;
+  public static final byte ATTR_LOCAL_VARIABLE_TABLE                    = 5;
+  public static final byte ATTR_INNER_CLASSES                           = 6;
+  public static final byte ATTR_SYNTHETIC                               = 7;
+  public static final byte ATTR_DEPRECATED                              = 8;
+  public static final byte ATTR_PMG                                     = 9;
+  public static final byte ATTR_SIGNATURE                               = 10;
+  public static final byte ATTR_STACK_MAP                               = 11;
+  public static final byte ATTR_RUNTIME_VISIBLE_ANNOTATIONS             = 12;
+  public static final byte ATTR_RUNTIMEIN_VISIBLE_ANNOTATIONS           = 13;
+  public static final byte ATTR_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS   = 14;
+  public static final byte ATTR_RUNTIMEIN_VISIBLE_PARAMETER_ANNOTATIONS = 15;
+  public static final byte ATTR_ANNOTATION_DEFAULT                      = 16;
+  public static final byte ATTR_LOCAL_VARIABLE_TYPE_TABLE               = 17;
+  public static final byte ATTR_ENCLOSING_METHOD                        = 18;
+  public static final byte ATTR_STACK_MAP_TABLE                         = 19;
+
+  public static final short KNOWN_ATTRIBUTES = 20;
+
+  // TOFO: FIXXXXX
+  public static final String[] ATTRIBUTE_NAMES = {
+    "SourceFile", "ConstantValue", "Code", "Exceptions",
+    "LineNumberTable", "LocalVariableTable",
+    "InnerClasses", "Synthetic", "Deprecated",
+    "PMGClass", "Signature", "StackMap", 
+    "RuntimeVisibleAnnotations", "RuntimeInvisibleAnnotations",
+    "RuntimeVisibleParameterAnnotations", "RuntimeInvisibleParameterAnnotations",
+    "AnnotationDefault", "LocalVariableTypeTable", "EnclosingMethod", "StackMapTable"
+  };
+
+  /** Constants used in the StackMap attribute.
+   */
+  public static final byte ITEM_Bogus      = 0;
+  public static final byte ITEM_Object     = 7;
+  public static final byte ITEM_NewObject  = 8;
+
+  public static final String[] ITEM_NAMES = {
+    "Bogus", "Integer", "Float", "Double", "Long",
+    "Null", "InitObject", "Object", "NewObject" 
+  };
+  
+  /** Constants used to identify StackMapEntry types.
+   * 
+   * For those types which can specify a range, the 
+   * constant names the lowest value.
+   */
+  public static final int SAME_FRAME = 0; 
+  public static final int SAME_LOCALS_1_STACK_ITEM_FRAME = 64; 
+  public static final int SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED = 247; 
+  public static final int CHOP_FRAME = 248; 
+  public static final int SAME_FRAME_EXTENDED = 251; 
+  public static final int APPEND_FRAME = 252; 
+  public static final int FULL_FRAME = 255; 
+  
+  /** Constants that define the maximum value of 
+   * those constants which store ranges. */
+  
+  public static final int SAME_FRAME_MAX = 63;
+  public static final int SAME_LOCALS_1_STACK_ITEM_FRAME_MAX = 127;
+  public static final int CHOP_FRAME_MAX = 250;
+  public static final int APPEND_FRAME_MAX = 254;
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/AccessFlags.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/AccessFlags.java
new file mode 100644
index 0000000..6b4b88b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/AccessFlags.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.tomcat.util.bcel.classfile;
+
+/**
+ * Super class for all objects that have modifiers like private, final, ...
+ * I.e. classes, fields, and methods.
+ *
+ * @version $Id: AccessFlags.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ */
+public abstract class AccessFlags implements java.io.Serializable {
+
+    private static final long serialVersionUID = 2548932939969293935L;
+    protected int access_flags;
+
+
+    public AccessFlags() {
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/AnnotationDefault.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/AnnotationDefault.java
new file mode 100644
index 0000000..7b01b1e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/AnnotationDefault.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.util.bcel.classfile;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * represents the default value of a annotation for a method info
+ * 
+ * @version $Id: AnnotationDefault.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ * @author <A HREF="mailto:dbrosius@qis.net">D. Brosius</A>
+ * @since 5.3
+ */
+public class AnnotationDefault extends Attribute
+{
+    private static final long serialVersionUID = 6715933396664171543L;
+    ElementValue default_value;
+
+    /**
+     * @param name_index
+     *            Index pointing to the name <em>Code</em>
+     * @param length
+     *            Content length in bytes
+     * @param file
+     *            Input stream
+     * @param constant_pool
+     *            Array of constants
+     */
+    public AnnotationDefault(int name_index, int length,
+            DataInputStream file, ConstantPool constant_pool)
+            throws IOException
+    {
+        this(name_index, length, (ElementValue) null,
+                constant_pool);
+        default_value = ElementValue.readElementValue(file, constant_pool);
+    }
+
+    /**
+     * @param name_index
+     *            Index pointing to the name <em>Code</em>
+     * @param length
+     *            Content length in bytes
+     * @param defaultValue
+     *            the annotation's default value
+     * @param constant_pool
+     *            Array of constants
+     */
+    public AnnotationDefault(int name_index, int length,
+            ElementValue defaultValue, ConstantPool constant_pool)
+    {
+        super(Constants.ATTR_ANNOTATION_DEFAULT, name_index, length, constant_pool);
+        setDefaultValue(defaultValue);
+    }
+
+    /**
+     * @param defaultValue
+     *            the default value of this methodinfo's annotation
+     */
+    public final void setDefaultValue(ElementValue defaultValue)
+    {
+        default_value = defaultValue;
+    }
+
+
+    @Override
+    public Attribute copy(ConstantPool _constant_pool)
+    {
+        throw new RuntimeException("Not implemented yet!");
+    }
+
+    @Override
+    public final void dump(DataOutputStream dos) throws IOException
+    {
+      super.dump(dos);
+      default_value.dump(dos);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/AnnotationElementValue.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/AnnotationElementValue.java
new file mode 100644
index 0000000..0965ea0
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/AnnotationElementValue.java
@@ -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.
+ *
+ */
+package org.apache.tomcat.util.bcel.classfile;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+public class AnnotationElementValue extends ElementValue
+{
+    // For annotation element values, this is the annotation
+    private AnnotationEntry annotationEntry;
+
+    public AnnotationElementValue(int type, AnnotationEntry annotationEntry,
+            ConstantPool cpool)
+    {
+        super(type, cpool);
+        if (type != ANNOTATION)
+            throw new RuntimeException(
+                    "Only element values of type annotation can be built with this ctor - type specified: " + type);
+        this.annotationEntry = annotationEntry;
+    }
+
+    @Override
+    public void dump(DataOutputStream dos) throws IOException
+    {
+        dos.writeByte(type); // u1 type of value (ANNOTATION == '@')
+        annotationEntry.dump(dos);
+    }
+
+    @Override
+    public String stringifyValue()
+    {
+        StringBuffer sb = new StringBuffer();
+        sb.append(annotationEntry.toString());
+        return sb.toString();
+    }
+
+    @Override
+    public String toString()
+    {
+        return stringifyValue();
+    }
+
+    public AnnotationEntry getAnnotationEntry()
+    {
+        return annotationEntry;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/AnnotationEntry.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/AnnotationEntry.java
new file mode 100644
index 0000000..b633bc2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/AnnotationEntry.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.util.bcel.classfile;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * represents one annotation in the annotation table
+ * 
+ * @version $Id: AnnotationEntry
+ * @author  <A HREF="mailto:dbrosius@mebigfatguy.com">D. Brosius</A>
+ * @since 5.3
+ */
+public class AnnotationEntry implements Constants, Serializable {
+
+    private static final long serialVersionUID = 1L;
+    
+    private int type_index;
+    private ConstantPool constant_pool;
+
+    private List<ElementValuePair> element_value_pairs;
+    
+    /**
+     * Factory method to create an AnnotionEntry from a DataInputStream
+     * 
+     * @param file
+     * @param constant_pool
+     * @throws IOException
+     */
+    public static AnnotationEntry read(DataInputStream file, ConstantPool constant_pool) throws IOException {
+        
+        final AnnotationEntry annotationEntry = new AnnotationEntry(file.readUnsignedShort(), constant_pool);
+        final int num_element_value_pairs = (file.readUnsignedShort());
+        annotationEntry.element_value_pairs = new ArrayList<ElementValuePair>();
+        for (int i = 0; i < num_element_value_pairs; i++) {
+            annotationEntry.element_value_pairs.add(new ElementValuePair(file.readUnsignedShort(), ElementValue.readElementValue(file, constant_pool),
+                    constant_pool));
+        }
+        return annotationEntry;
+    }
+
+    public AnnotationEntry(int type_index, ConstantPool constant_pool) {
+        this.type_index = type_index;
+        this.constant_pool = constant_pool;
+    }
+    
+    /**
+     * @return the annotation type name
+     */
+    public String getAnnotationType() {
+        final ConstantUtf8 c = (ConstantUtf8) constant_pool.getConstant(type_index, CONSTANT_Utf8);
+        return c.getBytes();
+    }
+
+    /**
+     * @return the element value pairs in this annotation entry
+     */
+    public ElementValuePair[] getElementValuePairs() {
+        // TOFO return List
+        return element_value_pairs.toArray(new ElementValuePair[element_value_pairs.size()]);
+    }
+
+
+    public void dump(DataOutputStream dos) throws IOException
+    {
+        dos.writeShort(type_index);    // u2 index of type name in cpool
+        dos.writeShort(element_value_pairs.size()); // u2 element_value pair count
+        for (int i = 0 ; i<element_value_pairs.size();i++) {
+            ElementValuePair envp = element_value_pairs.get(i);
+            envp.dump(dos);
+        }
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Annotations.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Annotations.java
new file mode 100644
index 0000000..7a38f6a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Annotations.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.tomcat.util.bcel.classfile;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * base class for annotations
+ * 
+ * @version $Id: Annotations
+ * @author  <A HREF="mailto:dbrosius@qis.net">D. Brosius</A>
+ * @since 5.3
+ */
+public abstract class Annotations extends Attribute {
+
+    private static final long serialVersionUID = 1L;
+    
+    private AnnotationEntry[] annotation_table;
+    
+    /**
+     * @param annotation_type the subclass type of the annotation
+     * @param name_index Index pointing to the name <em>Code</em>
+     * @param length Content length in bytes
+     * @param file Input stream
+     * @param constant_pool Array of constants
+     */
+    public Annotations(byte annotation_type, int name_index, int length, DataInputStream file, ConstantPool constant_pool) throws IOException {
+        this(annotation_type, name_index, length, (AnnotationEntry[]) null, constant_pool);
+        final int annotation_table_length = (file.readUnsignedShort());
+        annotation_table = new AnnotationEntry[annotation_table_length];
+        for (int i = 0; i < annotation_table_length; i++) {
+            annotation_table[i] = AnnotationEntry.read(file, constant_pool);
+        }
+    }
+
+
+    /**
+     * @param annotation_type the subclass type of the annotation
+     * @param name_index Index pointing to the name <em>Code</em>
+     * @param length Content length in bytes
+     * @param annotation_table the actual annotations
+     * @param constant_pool Array of constants
+     */
+    public Annotations(byte annotation_type, int name_index, int length, AnnotationEntry[] annotation_table, ConstantPool constant_pool) {
+        super(annotation_type, name_index, length, constant_pool);
+        setAnnotationTable(annotation_table);
+    }
+
+    /**
+     * @param annotation_table the entries to set in this annotation
+     */
+    public final void setAnnotationTable( AnnotationEntry[] annotation_table ) {
+        this.annotation_table = annotation_table;
+    }
+
+    /**
+     * returns the array of annotation entries in this annotation
+     */
+    public AnnotationEntry[] getAnnotationEntries() {
+        return annotation_table;
+    }
+
+    protected void writeAnnotations(DataOutputStream dos) throws IOException {
+        if (annotation_table == null) {
+            return;
+        }
+        dos.writeShort(annotation_table.length);
+        for (int i = 0; i < annotation_table.length; i++)
+            annotation_table[i].dump(dos);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ArrayElementValue.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ArrayElementValue.java
new file mode 100644
index 0000000..c4c5639
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ArrayElementValue.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.bcel.classfile;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+public class ArrayElementValue extends ElementValue
+{
+    // For array types, this is the array
+    private ElementValue[] evalues;
+
+    @Override
+    public String toString()
+    {
+        StringBuffer sb = new StringBuffer();
+        sb.append("{");
+        for (int i = 0; i < evalues.length; i++)
+        {
+            sb.append(evalues[i].toString());
+            if ((i + 1) < evalues.length)
+                sb.append(",");
+        }
+        sb.append("}");
+        return sb.toString();
+    }
+
+    public ArrayElementValue(int type, ElementValue[] datums, ConstantPool cpool)
+    {
+        super(type, cpool);
+        if (type != ARRAY)
+            throw new RuntimeException(
+                    "Only element values of type array can be built with this ctor - type specified: " + type);
+        this.evalues = datums;
+    }
+
+    @Override
+    public void dump(DataOutputStream dos) throws IOException
+    {
+        dos.writeByte(type); // u1 type of value (ARRAY == '[')
+        dos.writeShort(evalues.length);
+        for (int i = 0; i < evalues.length; i++)
+        {
+            evalues[i].dump(dos);
+        }
+    }
+
+    @Override
+    public String stringifyValue()
+    {
+        StringBuffer sb = new StringBuffer();
+        sb.append("[");
+        for (int i = 0; i < evalues.length; i++)
+        {
+            sb.append(evalues[i].stringifyValue());
+            if ((i + 1) < evalues.length)
+                sb.append(",");
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
+    public ElementValue[] getElementValuesArray()
+    {
+        return evalues;
+    }
+
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Attribute.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Attribute.java
new file mode 100644
index 0000000..b2025cb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Attribute.java
@@ -0,0 +1,242 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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.classfile;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * Abstract super class for <em>Attribute</em> objects. Currently the
+ * <em>ConstantValue</em>, <em>SourceFile</em>, <em>Code</em>,
+ * <em>Exceptiontable</em>, <em>LineNumberTable</em>,
+ * <em>LocalVariableTable</em>, <em>InnerClasses</em> and
+ * <em>Synthetic</em> attributes are supported. The <em>Unknown</em>
+ * attribute stands for non-standard-attributes.
+ * 
+ * @version $Id: Attribute.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see ConstantValue
+ * @see SourceFile
+ * @see Code
+ * @see Unknown
+ * @see ExceptionTable
+ * @see LineNumberTable
+ * @see LocalVariableTable
+ * @see InnerClasses
+ * @see Synthetic
+ * @see Deprecated
+ * @see Signature
+ */
+public abstract class Attribute implements Cloneable, Serializable
+{
+    private static final long serialVersionUID = 1514136303496688899L;
+
+    protected int name_index; // Points to attribute name in constant pool
+
+    protected int length; // Content length of attribute field
+
+    protected byte tag; // Tag to distiguish subclasses
+
+    protected ConstantPool constant_pool;
+
+    protected Attribute(byte tag, int name_index, int length,
+            ConstantPool constant_pool)
+    {
+        this.tag = tag;
+        this.name_index = name_index;
+        this.length = length;
+        this.constant_pool = constant_pool;
+    }
+
+    /**
+     * Dump attribute to file stream in binary format.
+     * 
+     * @param file
+     *            Output file stream
+     * @throws IOException
+     */
+    public void dump(DataOutputStream file) throws IOException
+    {
+        file.writeShort(name_index);
+        file.writeInt(length);
+    }
+
+    private static final Map<String,AttributeReader> readers =
+            new HashMap<String,AttributeReader>();
+
+    /*
+     * Class method reads one attribute from the input data stream. This method
+     * must not be accessible from the outside. It is called by the Field and
+     * Method constructor methods.
+     * 
+     * @see Field
+     * @see Method @param file Input stream @param constant_pool Array of
+     *      constants @return Attribute @throws IOException @throws
+     *      ClassFormatException
+     */
+    public static final Attribute readAttribute(DataInputStream file,
+            ConstantPool constant_pool) throws IOException,
+            ClassFormatException
+    {
+        ConstantUtf8 c;
+        String name;
+        int name_index;
+        int length;
+        byte tag = Constants.ATTR_UNKNOWN; // Unknown attribute
+        // Get class name from constant pool via `name_index' indirection
+        name_index = file.readUnsignedShort();
+        c = (ConstantUtf8) constant_pool.getConstant(name_index,
+                Constants.CONSTANT_Utf8);
+        name = c.getBytes();
+        // Length of data in bytes
+        length = file.readInt();
+        // Compare strings to find known attribute
+        // System.out.println(name);
+        for (byte i = 0; i < Constants.KNOWN_ATTRIBUTES; i++)
+        {
+            if (name.equals(Constants.ATTRIBUTE_NAMES[i]))
+            {
+                tag = i; // found!
+                break;
+            }
+        }
+        // Call proper constructor, depending on `tag'
+        switch (tag)
+        {
+        case Constants.ATTR_UNKNOWN:
+            AttributeReader r = readers.get(name);
+            if (r != null)
+            {
+                return r.createAttribute(name_index, length, file,
+                        constant_pool);
+            }
+            return new Unknown(name_index, length, file, constant_pool);
+        case Constants.ATTR_CONSTANT_VALUE:
+            return new ConstantValue(name_index, length, file, constant_pool);
+        case Constants.ATTR_SOURCE_FILE:
+            return new SourceFile(name_index, length, file, constant_pool);
+        case Constants.ATTR_CODE:
+            return new Code(name_index, length, file, constant_pool);
+        case Constants.ATTR_EXCEPTIONS:
+            return new ExceptionTable(name_index, length, file, constant_pool);
+        case Constants.ATTR_LINE_NUMBER_TABLE:
+            return new LineNumberTable(name_index, length, file, constant_pool);
+        case Constants.ATTR_LOCAL_VARIABLE_TABLE:
+            return new LocalVariableTable(name_index, length, file,
+                    constant_pool);
+        case Constants.ATTR_INNER_CLASSES:
+            return new InnerClasses(name_index, length, file, constant_pool);
+        case Constants.ATTR_SYNTHETIC:
+            return new Synthetic(name_index, length, file, constant_pool);
+        case Constants.ATTR_DEPRECATED:
+            return new Deprecated(name_index, length, file, constant_pool);
+        case Constants.ATTR_PMG:
+            return new PMGClass(name_index, length, file, constant_pool);
+        case Constants.ATTR_SIGNATURE:
+            return new Signature(name_index, length, file, constant_pool);
+        case Constants.ATTR_STACK_MAP:
+            return new StackMap(name_index, length, file, constant_pool);
+        case Constants.ATTR_RUNTIME_VISIBLE_ANNOTATIONS:
+            return new RuntimeVisibleAnnotations(name_index, length, file,
+                    constant_pool);
+        case Constants.ATTR_RUNTIMEIN_VISIBLE_ANNOTATIONS:
+            return new RuntimeInvisibleAnnotations(name_index, length, file,
+                    constant_pool);
+        case Constants.ATTR_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS:
+            return new RuntimeVisibleParameterAnnotations(name_index, length,
+                    file, constant_pool);
+        case Constants.ATTR_RUNTIMEIN_VISIBLE_PARAMETER_ANNOTATIONS:
+            return new RuntimeInvisibleParameterAnnotations(name_index, length,
+                    file, constant_pool);
+        case Constants.ATTR_ANNOTATION_DEFAULT:
+            return new AnnotationDefault(name_index, length, file,
+                    constant_pool);
+        case Constants.ATTR_LOCAL_VARIABLE_TYPE_TABLE:
+            return new LocalVariableTypeTable(name_index, length, file,
+                    constant_pool);
+        case Constants.ATTR_ENCLOSING_METHOD:
+            return new EnclosingMethod(name_index, length, file, constant_pool);
+        case Constants.ATTR_STACK_MAP_TABLE:
+            return new StackMapTable(name_index, length, file, constant_pool);
+        default: // Never reached
+            throw new IllegalStateException("Unrecognized attribute type tag parsed: " + tag);
+        }
+    }
+
+    /**
+     * @return Name of attribute
+     */
+    public String getName()
+    {
+        ConstantUtf8 c = (ConstantUtf8) constant_pool.getConstant(name_index,
+                Constants.CONSTANT_Utf8);
+        return c.getBytes();
+    }
+
+
+    /**
+     * @return Tag of attribute, i.e., its type. Value may not be altered, thus
+     *         there is no setTag() method.
+     */
+    public final byte getTag()
+    {
+        return tag;
+    }
+
+
+    /**
+     * Use copy() if you want to have a deep copy(), i.e., with all references
+     * copied correctly.
+     * 
+     * @return shallow copy of this attribute
+     */
+    @Override
+    public Object clone()
+    {
+        Object o = null;
+        try
+        {
+            o = super.clone();
+        }
+        catch (CloneNotSupportedException e)
+        {
+            e.printStackTrace(); // Never occurs
+        }
+        return o;
+    }
+
+    /**
+     * @return deep copy of this attribute
+     */
+    public abstract Attribute copy(ConstantPool _constant_pool);
+
+    /**
+     * @return attribute name.
+     */
+    @Override
+    public String toString()
+    {
+        return Constants.ATTRIBUTE_NAMES[tag];
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/AttributeReader.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/AttributeReader.java
new file mode 100644
index 0000000..340ae2c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/AttributeReader.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 org.apache.tomcat.util.bcel.classfile;
+
+/**
+ * Unknown (non-standard) attributes may be read via user-defined factory
+ * objects that can be registered with the Attribute.addAttributeReader
+ * method. These factory objects should implement this interface.
+
+ * @see Attribute
+ * @version $Id: AttributeReader.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ */
+public interface AttributeReader {
+
+    /**
+     When this attribute reader is added via the static method
+     Attribute.addAttributeReader, an attribute name is associated with it.
+     As the class file parser parses attributes, it will call various
+     AttributeReaders based on the name of the attributes it is
+     constructing.
+
+     @param name_index An index into the constant pool, indexing a
+     ConstantUtf8 that represents the name of the attribute.
+
+     @param length The length of the data contained in the attribute.  This
+     is written into the constant pool and should agree with what the
+     factory expects the length to be.
+
+     @param file This is the data input stream that the factory needs to read
+     its data from.
+
+     @param constant_pool This is the constant pool associated with the
+     Attribute that we are constructing.
+
+     @return The user-defined AttributeReader should take this data and use
+     it to construct an attribute.  In the case of errors, a null can be
+     returned which will cause the parsing of the class file to fail.
+     */
+    public Attribute createAttribute( int name_index, int length, java.io.DataInputStream file,
+            ConstantPool constant_pool );
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ClassElementValue.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ClassElementValue.java
new file mode 100644
index 0000000..0008796
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ClassElementValue.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.bcel.classfile;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+public class ClassElementValue extends ElementValue
+{
+    // For primitive types and string type, this points to the value entry in
+    // the cpool
+    // For 'class' this points to the class entry in the cpool
+    private int idx;
+
+    public ClassElementValue(int type, int idx, ConstantPool cpool)
+    {
+        super(type, cpool);
+        this.idx = idx;
+    }
+
+
+    @Override
+    public String stringifyValue()
+    {
+        ConstantUtf8 cu8 = (ConstantUtf8) cpool.getConstant(idx,
+                Constants.CONSTANT_Utf8);
+        return cu8.getBytes();
+    }
+
+    @Override
+    public void dump(DataOutputStream dos) throws IOException
+    {
+        dos.writeByte(type); // u1 kind of value
+        dos.writeShort(idx);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ClassFormatException.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ClassFormatException.java
new file mode 100644
index 0000000..c9f392e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ClassFormatException.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.bcel.classfile;
+
+/** 
+ * Thrown when the BCEL attempts to read a class file and determines
+ * that the file is malformed or otherwise cannot be interpreted as a
+ * class file.
+ *
+ * @version $Id: ClassFormatException.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ */
+public class ClassFormatException extends RuntimeException {
+
+    private static final long serialVersionUID = 3243149520175287759L;
+
+    public ClassFormatException() {
+        super();
+    }
+
+
+    public ClassFormatException(String s) {
+        super(s);
+    }
+    
+    public ClassFormatException(String s, Throwable initCause) {
+        super(s, initCause);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ClassParser.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ClassParser.java
new file mode 100644
index 0000000..df96495
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ClassParser.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 org.apache.tomcat.util.bcel.classfile;
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * Wrapper class that parses a given Java .class file. The method <A
+ * href ="#parse">parse</A> returns a <A href ="JavaClass.html">
+ * JavaClass</A> object on success. When an I/O error or an
+ * inconsistency occurs an appropiate exception is propagated back to
+ * the caller.
+ *
+ * The structure and the names comply, except for a few conveniences,
+ * exactly with the <A href="ftp://java.sun.com/docs/specs/vmspec.ps">
+ * JVM specification 1.0</a>. See this paper for
+ * further details about the structure of a bytecode file.
+ *
+ * @version $Id: ClassParser.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A> 
+ */
+public final class ClassParser {
+
+    private DataInputStream file;
+    private boolean fileOwned;
+    private String file_name;
+    private String zip_file;
+    private int class_name_index, superclass_name_index;
+    private int major, minor; // Compiler version
+    private int access_flags; // Access rights of parsed class
+    private int[] interfaces; // Names of implemented interfaces
+    private ConstantPool constant_pool; // collection of constants
+    private Field[] fields; // class fields, i.e., its variables
+    private Method[] methods; // methods defined in the class
+    private Attribute[] attributes; // attributes defined in the class
+    private boolean is_zip; // Loaded from zip file
+    private static final int BUFSIZE = 8192;
+
+
+    /**
+     * Parse class from the given stream.
+     *
+     * @param file Input stream
+     * @param file_name File name
+     */
+    public ClassParser(InputStream file, String file_name) {
+        this.file_name = file_name;
+        fileOwned = false;
+        String clazz = file.getClass().getName(); // Not a very clean solution ...
+        is_zip = clazz.startsWith("java.util.zip.") || clazz.startsWith("java.util.jar.");
+        if (file instanceof DataInputStream) {
+            this.file = (DataInputStream) file;
+        } else {
+            this.file = new DataInputStream(new BufferedInputStream(file, BUFSIZE));
+        }
+    }
+
+
+    /**
+     * Parse the given Java class file and return an object that represents
+     * the contained data, i.e., constants, methods, fields and commands.
+     * A <em>ClassFormatException</em> is raised, if the file is not a valid
+     * .class file. (This does not include verification of the byte code as it
+     * is performed by the java interpreter).
+     *
+     * @return Class object representing the parsed class file
+     * @throws  IOException
+     * @throws  ClassFormatException
+     */
+    public JavaClass parse() throws IOException, ClassFormatException {
+        ZipFile zip = null;
+        try {
+            if (fileOwned) {
+                if (is_zip) {
+                    zip = new ZipFile(zip_file);
+                    ZipEntry entry = zip.getEntry(file_name);
+                    
+                    if (entry == null) {
+                        throw new IOException("File " + file_name + " not found");
+                    }
+                    
+                    file = new DataInputStream(new BufferedInputStream(zip.getInputStream(entry),
+                            BUFSIZE));
+                } else {
+                    file = new DataInputStream(new BufferedInputStream(new FileInputStream(
+                            file_name), BUFSIZE));
+                }
+            }
+            /****************** Read headers ********************************/
+            // Check magic tag of class file
+            readID();
+            // Get compiler version
+            readVersion();
+            /****************** Read constant pool and related **************/
+            // Read constant pool entries
+            readConstantPool();
+            // Get class information
+            readClassInfo();
+            // Get interface information, i.e., implemented interfaces
+            readInterfaces();
+            /****************** Read class fields and methods ***************/
+            // Read class fields, i.e., the variables of the class
+            readFields();
+            // Read class methods, i.e., the functions in the class
+            readMethods();
+            // Read class attributes
+            readAttributes();
+            // Check for unknown variables
+            //Unknown[] u = Unknown.getUnknownAttributes();
+            //for(int i=0; i < u.length; i++)
+            //  System.err.println("WARNING: " + u[i]);
+            // Everything should have been read now
+            //      if(file.available() > 0) {
+            //        int bytes = file.available();
+            //        byte[] buf = new byte[bytes];
+            //        file.read(buf);
+            //        if(!(is_zip && (buf.length == 1))) {
+            //          System.err.println("WARNING: Trailing garbage at end of " + file_name);
+            //          System.err.println(bytes + " extra bytes: " + Utility.toHexString(buf));
+            //        }
+            //      }
+        } finally {
+            // Read everything of interest, so close the file
+            if (fileOwned) {
+                try {
+                    if (file != null) {
+                        file.close();
+                    }
+                    if (zip != null) {
+                        zip.close();
+                    }
+                } catch (IOException ioe) {
+                    //ignore close exceptions
+                }
+            }
+        }
+        // Return the information we have gathered in a new object
+        return new JavaClass(class_name_index, superclass_name_index, file_name, major, minor,
+                access_flags, constant_pool, interfaces, fields, methods, attributes);
+    }
+
+
+    /**
+     * Read information about the attributes of the class.
+     * @throws  IOException
+     * @throws  ClassFormatException
+     */
+    private final void readAttributes() throws IOException, ClassFormatException {
+        int attributes_count;
+        attributes_count = file.readUnsignedShort();
+        attributes = new Attribute[attributes_count];
+        for (int i = 0; i < attributes_count; i++) {
+            attributes[i] = Attribute.readAttribute(file, constant_pool);
+        }
+    }
+
+
+    /**
+     * Read information about the class and its super class.
+     * @throws  IOException
+     * @throws  ClassFormatException
+     */
+    private final void readClassInfo() throws IOException, ClassFormatException {
+        access_flags = file.readUnsignedShort();
+        /* Interfaces are implicitely abstract, the flag should be set
+         * according to the JVM specification.
+         */
+        if ((access_flags & Constants.ACC_INTERFACE) != 0) {
+            access_flags |= Constants.ACC_ABSTRACT;
+        }
+        if (((access_flags & Constants.ACC_ABSTRACT) != 0)
+                && ((access_flags & Constants.ACC_FINAL) != 0)) {
+            throw new ClassFormatException("Class " + file_name + " can't be both final and abstract");
+        }
+        class_name_index = file.readUnsignedShort();
+        superclass_name_index = file.readUnsignedShort();
+    }
+
+
+    /**
+     * Read constant pool entries.
+     * @throws  IOException
+     * @throws  ClassFormatException
+     */
+    private final void readConstantPool() throws IOException, ClassFormatException {
+        constant_pool = new ConstantPool(file);
+    }
+
+
+    /**
+     * Read information about the fields of the class, i.e., its variables.
+     * @throws  IOException
+     * @throws  ClassFormatException
+     */
+    private final void readFields() throws IOException, ClassFormatException {
+        int fields_count;
+        fields_count = file.readUnsignedShort();
+        fields = new Field[fields_count];
+        for (int i = 0; i < fields_count; i++) {
+            fields[i] = new Field(file, constant_pool);
+        }
+    }
+
+
+    /******************** Private utility methods **********************/
+    /**
+     * Check whether the header of the file is ok.
+     * Of course, this has to be the first action on successive file reads.
+     * @throws  IOException
+     * @throws  ClassFormatException
+     */
+    private final void readID() throws IOException, ClassFormatException {
+        int magic = 0xCAFEBABE;
+        if (file.readInt() != magic) {
+            throw new ClassFormatException(file_name + " is not a Java .class file");
+        }
+    }
+
+
+    /**
+     * Read information about the interfaces implemented by this class.
+     * @throws  IOException
+     * @throws  ClassFormatException
+     */
+    private final void readInterfaces() throws IOException, ClassFormatException {
+        int interfaces_count;
+        interfaces_count = file.readUnsignedShort();
+        interfaces = new int[interfaces_count];
+        for (int i = 0; i < interfaces_count; i++) {
+            interfaces[i] = file.readUnsignedShort();
+        }
+    }
+
+
+    /**
+     * Read information about the methods of the class.
+     * @throws  IOException
+     * @throws  ClassFormatException
+     */
+    private final void readMethods() throws IOException, ClassFormatException {
+        int methods_count;
+        methods_count = file.readUnsignedShort();
+        methods = new Method[methods_count];
+        for (int i = 0; i < methods_count; i++) {
+            methods[i] = new Method(file, constant_pool);
+        }
+    }
+
+
+    /**
+     * Read major and minor version of compiler which created the file.
+     * @throws  IOException
+     * @throws  ClassFormatException
+     */
+    private final void readVersion() throws IOException, ClassFormatException {
+        minor = file.readUnsignedShort();
+        major = file.readUnsignedShort();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Code.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Code.java
new file mode 100644
index 0000000..a424d25
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Code.java
@@ -0,0 +1,265 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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.classfile;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/** 
+ * This class represents a chunk of Java byte code contained in a
+ * method. It is instantiated by the
+ * <em>Attribute.readAttribute()</em> method. A <em>Code</em>
+ * attribute contains informations about operand stack, local
+ * variables, byte code and the exceptions handled within this
+ * method.
+ *
+ * This attribute has attributes itself, namely <em>LineNumberTable</em> which
+ * is used for debugging purposes and <em>LocalVariableTable</em> which 
+ * contains information about the local variables.
+ *
+ * @version $Id: Code.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     Attribute
+ * @see     CodeException
+ * @see     LineNumberTable
+ * @see LocalVariableTable
+ */
+public final class Code extends Attribute {
+
+    private static final long serialVersionUID = 8936843273318969602L;
+    private int max_stack; // Maximum size of stack used by this method
+    private int max_locals; // Number of local variables
+    private int code_length; // Length of code in bytes
+    private byte[] code; // Actual byte code
+    private int exception_table_length;
+    private CodeException[] exception_table; // Table of handled exceptions
+    private int attributes_count; // Attributes of code: LineNumber
+    private Attribute[] attributes; // or LocalVariable
+
+
+    /**
+     * @param name_index Index pointing to the name <em>Code</em>
+     * @param length Content length in bytes
+     * @param file Input stream
+     * @param constant_pool Array of constants
+     */
+    Code(int name_index, int length, DataInputStream file, ConstantPool constant_pool)
+            throws IOException {
+        // Initialize with some default values which will be overwritten later
+        this(name_index, length, file.readUnsignedShort(), file.readUnsignedShort(), (byte[]) null,
+                (CodeException[]) null, (Attribute[]) null, constant_pool);
+        code_length = file.readInt();
+        code = new byte[code_length]; // Read byte code
+        file.readFully(code);
+        /* Read exception table that contains all regions where an exception
+         * handler is active, i.e., a try { ... } catch() block.
+         */
+        exception_table_length = file.readUnsignedShort();
+        exception_table = new CodeException[exception_table_length];
+        for (int i = 0; i < exception_table_length; i++) {
+            exception_table[i] = new CodeException(file);
+        }
+        /* Read all attributes, currently `LineNumberTable' and
+         * `LocalVariableTable'
+         */
+        attributes_count = file.readUnsignedShort();
+        attributes = new Attribute[attributes_count];
+        for (int i = 0; i < attributes_count; i++) {
+            attributes[i] = Attribute.readAttribute(file, constant_pool);
+        }
+        /* Adjust length, because of setAttributes in this(), s.b.  length
+         * is incorrect, because it didn't take the internal attributes
+         * into account yet! Very subtle bug, fixed in 3.1.1.
+         */
+        this.length = length;
+    }
+
+
+    /**
+     * @param name_index Index pointing to the name <em>Code</em>
+     * @param length Content length in bytes
+     * @param max_stack Maximum size of stack
+     * @param max_locals Number of local variables
+     * @param code Actual byte code
+     * @param exception_table Table of handled exceptions
+     * @param attributes Attributes of code: LineNumber or LocalVariable
+     * @param constant_pool Array of constants
+     */
+    public Code(int name_index, int length, int max_stack, int max_locals, byte[] code,
+            CodeException[] exception_table, Attribute[] attributes, ConstantPool constant_pool) {
+        super(Constants.ATTR_CODE, name_index, length, constant_pool);
+        this.max_stack = max_stack;
+        this.max_locals = max_locals;
+        setCode(code);
+        setExceptionTable(exception_table);
+        setAttributes(attributes); // Overwrites length!
+    }
+
+
+    /**
+     * Dump code attribute to file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    @Override
+    public final void dump( DataOutputStream file ) throws IOException {
+        super.dump(file);
+        file.writeShort(max_stack);
+        file.writeShort(max_locals);
+        file.writeInt(code_length);
+        file.write(code, 0, code_length);
+        file.writeShort(exception_table_length);
+        for (int i = 0; i < exception_table_length; i++) {
+            exception_table[i].dump(file);
+        }
+        file.writeShort(attributes_count);
+        for (int i = 0; i < attributes_count; i++) {
+            attributes[i].dump(file);
+        }
+    }
+
+
+    /**
+     * @return LocalVariableTable of Code, if it has one
+     */
+    public LocalVariableTable getLocalVariableTable() {
+        for (int i = 0; i < attributes_count; i++) {
+            if (attributes[i] instanceof LocalVariableTable) {
+                return (LocalVariableTable) attributes[i];
+            }
+        }
+        return null;
+    }
+
+
+    /**
+     * @return the internal length of this code attribute (minus the first 6 bytes) 
+     * and excluding all its attributes
+     */
+    private final int getInternalLength() {
+        return 2 /*max_stack*/+ 2 /*max_locals*/+ 4 /*code length*/
+                + code_length /*byte-code*/
+                + 2 /*exception-table length*/
+                + 8 * exception_table_length /* exception table */
+                + 2 /* attributes count */;
+    }
+
+
+    /**
+     * @return the full size of this code attribute, minus its first 6 bytes,
+     * including the size of all its contained attributes
+     */
+    private final int calculateLength() {
+        int len = 0;
+        for (int i = 0; i < attributes_count; i++) {
+            len += attributes[i].length + 6 /*attribute header size*/;
+        }
+        return len + getInternalLength();
+    }
+
+
+    /**
+     * @param attributes the attributes to set for this Code
+     */
+    public final void setAttributes( Attribute[] attributes ) {
+        this.attributes = attributes;
+        attributes_count = (attributes == null) ? 0 : attributes.length;
+        length = calculateLength(); // Adjust length
+    }
+
+
+    /**
+     * @param code byte code
+     */
+    public final void setCode( byte[] code ) {
+        this.code = code;
+        code_length = (code == null) ? 0 : code.length;
+    }
+
+
+    /**
+     * @param exception_table exception table
+     */
+    public final void setExceptionTable( CodeException[] exception_table ) {
+        this.exception_table = exception_table;
+        exception_table_length = (exception_table == null) ? 0 : exception_table.length;
+    }
+
+
+    /**
+     * @return String representation of code chunk.
+     */
+    public final String toString( boolean verbose ) {
+        StringBuffer buf;
+        buf = new StringBuffer(100);
+        buf.append("Code(max_stack = ").append(max_stack).append(", max_locals = ").append(
+                max_locals).append(", code_length = ").append(code_length).append(")\n").append(
+                Utility.codeToString(code, constant_pool, 0, -1, verbose));
+        if (exception_table_length > 0) {
+            buf.append("\nException handler(s) = \n").append("From\tTo\tHandler\tType\n");
+            for (int i = 0; i < exception_table_length; i++) {
+                buf.append(exception_table[i].toString(constant_pool, verbose)).append("\n");
+            }
+        }
+        if (attributes_count > 0) {
+            buf.append("\nAttribute(s) = \n");
+            for (int i = 0; i < attributes_count; i++) {
+                buf.append(attributes[i].toString()).append("\n");
+            }
+        }
+        return buf.toString();
+    }
+
+
+    /**
+     * @return String representation of code chunk.
+     */
+    @Override
+    public final String toString() {
+        return toString(true);
+    }
+
+
+    /**
+     * @return deep copy of this attribute
+     * 
+     * @param _constant_pool the constant pool to duplicate
+     */
+    @Override
+    public Attribute copy( ConstantPool _constant_pool ) {
+        Code c = (Code) clone();
+        if (code != null) {
+            c.code = new byte[code.length];
+            System.arraycopy(code, 0, c.code, 0, code.length);
+        }
+        c.constant_pool = _constant_pool;
+        c.exception_table = new CodeException[exception_table_length];
+        for (int i = 0; i < exception_table_length; i++) {
+            c.exception_table[i] = exception_table[i].copy();
+        }
+        c.attributes = new Attribute[attributes_count];
+        for (int i = 0; i < attributes_count; i++) {
+            c.attributes[i] = attributes[i].copy(_constant_pool);
+        }
+        return c;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/CodeException.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/CodeException.java
new file mode 100644
index 0000000..43363b3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/CodeException.java
@@ -0,0 +1,128 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.tomcat.util.bcel.classfile;
+
+import java.io.DataInput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.Serializable;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * This class represents an entry in the exception table of the <em>Code</em>
+ * attribute and is used only there. It contains a range in which a
+ * particular exception handler is active.
+ *
+ * @version $Id: CodeException.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     Code
+ */
+public final class CodeException implements Cloneable, Constants, Serializable {
+
+    private static final long serialVersionUID = -6351674720658890686L;
+    private int start_pc; // Range in the code the exception handler is
+    private int end_pc; // active. start_pc is inclusive, end_pc exclusive
+    private int handler_pc; /* Starting address of exception handler, i.e.,
+     * an offset from start of code.
+     */
+    private int catch_type; /* If this is zero the handler catches any
+     * exception, otherwise it points to the
+     * exception class which is to be caught.
+     */
+
+
+    /**
+     * Construct object from file stream.
+     * @param file Input stream
+     * @throws IOException
+     */
+    CodeException(DataInput file) throws IOException {
+        this(file.readUnsignedShort(), file.readUnsignedShort(), file.readUnsignedShort(), file
+                .readUnsignedShort());
+    }
+
+
+    /**
+     * @param start_pc Range in the code the exception handler is active,
+     * start_pc is inclusive while
+     * @param end_pc is exclusive
+     * @param handler_pc Starting address of exception handler, i.e.,
+     * an offset from start of code.
+     * @param catch_type If zero the handler catches any 
+     * exception, otherwise it points to the exception class which is 
+     * to be caught.
+     */
+    public CodeException(int start_pc, int end_pc, int handler_pc, int catch_type) {
+        this.start_pc = start_pc;
+        this.end_pc = end_pc;
+        this.handler_pc = handler_pc;
+        this.catch_type = catch_type;
+    }
+
+
+    /**
+     * Dump code exception to file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    public final void dump( DataOutputStream file ) throws IOException {
+        file.writeShort(start_pc);
+        file.writeShort(end_pc);
+        file.writeShort(handler_pc);
+        file.writeShort(catch_type);
+    }
+
+
+    /**
+     * @return String representation.
+     */
+    @Override
+    public final String toString() {
+        return "CodeException(start_pc = " + start_pc + ", end_pc = " + end_pc + ", handler_pc = "
+                + handler_pc + ", catch_type = " + catch_type + ")";
+    }
+
+
+    /**
+     * @return String representation.
+     */
+    public final String toString( ConstantPool cp, boolean verbose ) {
+        String str;
+        if (catch_type == 0) {
+            str = "<Any exception>(0)";
+        } else {
+            str = Utility.compactClassName(cp.getConstantString(catch_type, CONSTANT_Class), false)
+                    + (verbose ? "(" + catch_type + ")" : "");
+        }
+        return start_pc + "\t" + end_pc + "\t" + handler_pc + "\t" + str;
+    }
+
+
+    /**
+     * @return deep copy of this object
+     */
+    public CodeException copy() {
+        try {
+            return (CodeException) clone();
+        } catch (CloneNotSupportedException e) {
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Constant.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Constant.java
new file mode 100644
index 0000000..8bb8877
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Constant.java
@@ -0,0 +1,159 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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.classfile;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.Serializable;
+
+import org.apache.tomcat.util.bcel.Constants;
+import org.apache.tomcat.util.bcel.util.BCELComparator;
+
+/**
+ * Abstract superclass for classes to represent the different constant types
+ * in the constant pool of a class file. The classes keep closely to
+ * the JVM specification.
+ *
+ * @version $Id: Constant.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ */
+public abstract class Constant implements Cloneable, Serializable {
+
+    private static final long serialVersionUID = 2827409182154809454L;
+    private static BCELComparator _cmp = new BCELComparator() {
+
+        @Override
+        public boolean equals( Object o1, Object o2 ) {
+            Constant THIS = (Constant) o1;
+            Constant THAT = (Constant) o2;
+            return THIS.toString().equals(THAT.toString());
+        }
+
+
+        @Override
+        public int hashCode( Object o ) {
+            Constant THIS = (Constant) o;
+            return THIS.toString().hashCode();
+        }
+    };
+    /* In fact this tag is redundant since we can distinguish different
+     * `Constant' objects by their type, i.e., via `instanceof'. In some
+     * places we will use the tag for switch()es anyway.
+     *
+     * First, we want match the specification as closely as possible. Second we
+     * need the tag as an index to select the corresponding class name from the 
+     * `CONSTANT_NAMES' array.
+     */
+    protected byte tag;
+
+
+    Constant(byte tag) {
+        this.tag = tag;
+    }
+
+
+    public abstract void dump( DataOutputStream file ) throws IOException;
+
+
+    /**
+     * @return Tag of constant, i.e., its type. No setTag() method to avoid
+     * confusion.
+     */
+    public final byte getTag() {
+        return tag;
+    }
+
+
+    /**
+     * @return String representation.
+     */
+    @Override
+    public String toString() {
+        return Constants.CONSTANT_NAMES[tag] + "[" + tag + "]";
+    }
+
+
+    @Override
+    public Object clone() throws CloneNotSupportedException {
+        return super.clone();
+    }
+
+
+    /**
+     * Read one constant from the given file, the type depends on a tag byte.
+     *
+     * @param file Input stream
+     * @return Constant object
+     */
+    static final Constant readConstant( DataInputStream file ) throws IOException,
+            ClassFormatException {
+        byte b = file.readByte(); // Read tag byte
+        switch (b) {
+            case Constants.CONSTANT_Class:
+                return new ConstantClass(file);
+            case Constants.CONSTANT_Fieldref:
+                return new ConstantFieldref(file);
+            case Constants.CONSTANT_Methodref:
+                return new ConstantMethodref(file);
+            case Constants.CONSTANT_InterfaceMethodref:
+                return new ConstantInterfaceMethodref(file);
+            case Constants.CONSTANT_String:
+                return new ConstantString(file);
+            case Constants.CONSTANT_Integer:
+                return new ConstantInteger(file);
+            case Constants.CONSTANT_Float:
+                return new ConstantFloat(file);
+            case Constants.CONSTANT_Long:
+                return new ConstantLong(file);
+            case Constants.CONSTANT_Double:
+                return new ConstantDouble(file);
+            case Constants.CONSTANT_NameAndType:
+                return new ConstantNameAndType(file);
+            case Constants.CONSTANT_Utf8:
+                return new ConstantUtf8(file);
+            default:
+                throw new ClassFormatException("Invalid byte tag in constant pool: " + b);
+        }
+    }
+
+
+    /**
+     * Return value as defined by given BCELComparator strategy.
+     * By default two Constant objects are said to be equal when
+     * the result of toString() is equal.
+     * 
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals( Object obj ) {
+        return _cmp.equals(this, obj);
+    }
+
+
+    /**
+     * Return value as defined by given BCELComparator strategy.
+     * By default return the hashcode of the result of toString().
+     * 
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        return _cmp.hashCode(this);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantCP.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantCP.java
new file mode 100644
index 0000000..e64b305
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantCP.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.bcel.classfile;
+
+import java.io.DataInput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/** 
+ * Abstract super class for Fieldref and Methodref constants.
+ *
+ * @version $Id: ConstantCP.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     ConstantFieldref
+ * @see     ConstantMethodref
+ * @see     ConstantInterfaceMethodref
+ */
+public abstract class ConstantCP extends Constant {
+
+    private static final long serialVersionUID = 7282382456501145526L;
+    /** References to the constants containing the class and the field signature
+     */
+    protected int class_index, name_and_type_index;
+
+
+    /**
+     * Initialize instance from file data.
+     *
+     * @param tag  Constant type tag
+     * @param file Input stream
+     * @throws IOException
+     */
+    ConstantCP(byte tag, DataInput file) throws IOException {
+        this(tag, file.readUnsignedShort(), file.readUnsignedShort());
+    }
+
+
+    /**
+     * @param class_index Reference to the class containing the field
+     * @param name_and_type_index and the field signature
+     */
+    protected ConstantCP(byte tag, int class_index, int name_and_type_index) {
+        super(tag);
+        this.class_index = class_index;
+        this.name_and_type_index = name_and_type_index;
+    }
+
+
+    /** 
+     * Dump constant field reference to file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    @Override
+    public final void dump( DataOutputStream file ) throws IOException {
+        file.writeByte(tag);
+        file.writeShort(class_index);
+        file.writeShort(name_and_type_index);
+    }
+
+
+    /**
+     * @return Reference (index) to class this field or method belongs to.
+     */
+    public final int getClassIndex() {
+        return class_index;
+    }
+
+
+    /**
+     * @return Reference (index) to signature of the field.
+     */
+    public final int getNameAndTypeIndex() {
+        return name_and_type_index;
+    }
+
+
+    /**
+     * @return String representation.
+     */
+    @Override
+    public final String toString() {
+        return super.toString() + "(class_index = " + class_index + ", name_and_type_index = "
+                + name_and_type_index + ")";
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantClass.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantClass.java
new file mode 100644
index 0000000..f00aa1b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantClass.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.tomcat.util.bcel.classfile;
+
+import java.io.DataInput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/** 
+ * This class is derived from the abstract 
+ * <A HREF="org.apache.tomcat.util.bcel.classfile.Constant.html">Constant</A> class 
+ * and represents a reference to a (external) class.
+ *
+ * @version $Id: ConstantClass.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     Constant
+ */
+public final class ConstantClass extends Constant implements ConstantObject {
+
+    private static final long serialVersionUID = -6603658849582876642L;
+    private int name_index; // Identical to ConstantString except for the name
+
+
+    /**
+     * Initialize instance from file data.
+     *
+     * @param file Input stream
+     * @throws IOException
+     */
+    ConstantClass(DataInput file) throws IOException {
+        this(file.readUnsignedShort());
+    }
+
+
+    /**
+     * @param name_index Name index in constant pool.  Should refer to a
+     * ConstantUtf8.
+     */
+    public ConstantClass(int name_index) {
+        super(Constants.CONSTANT_Class);
+        this.name_index = name_index;
+    }
+
+
+    /** 
+     * Dump constant class to file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    @Override
+    public final void dump( DataOutputStream file ) throws IOException {
+        file.writeByte(tag);
+        file.writeShort(name_index);
+    }
+
+
+    /**
+     * @return Name index in constant pool of class name.
+     */
+    public final int getNameIndex() {
+        return name_index;
+    }
+
+
+    /**
+     * @return String representation.
+     */
+    @Override
+    public final String toString() {
+        return super.toString() + "(name_index = " + name_index + ")";
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantDouble.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantDouble.java
new file mode 100644
index 0000000..d50a1bf
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantDouble.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.bcel.classfile;
+
+import java.io.DataInput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/** 
+ * This class is derived from the abstract 
+ * <A HREF="org.apache.tomcat.util.bcel.classfile.Constant.html">Constant</A> class 
+ * and represents a reference to a Double object.
+ *
+ * @version $Id: ConstantDouble.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     Constant
+ */
+public final class ConstantDouble extends Constant implements ConstantObject {
+
+    private static final long serialVersionUID = 3450743772468544760L;
+    private double bytes;
+
+
+    /** 
+     * @param bytes Data
+     */
+    public ConstantDouble(double bytes) {
+        super(Constants.CONSTANT_Double);
+        this.bytes = bytes;
+    }
+
+
+    /** 
+     * Initialize instance from file data.
+     *
+     * @param file Input stream
+     * @throws IOException
+     */
+    ConstantDouble(DataInput file) throws IOException {
+        this(file.readDouble());
+    }
+
+
+    /**
+     * Dump constant double to file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    @Override
+    public final void dump( DataOutputStream file ) throws IOException {
+        file.writeByte(tag);
+        file.writeDouble(bytes);
+    }
+
+
+    /**
+     * @return data, i.e., 8 bytes.
+     */
+    public final double getBytes() {
+        return bytes;
+    }
+
+
+    /**
+     * @return String representation.
+     */
+    @Override
+    public final String toString() {
+        return super.toString() + "(bytes = " + bytes + ")";
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantFieldref.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantFieldref.java
new file mode 100644
index 0000000..23192ce
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantFieldref.java
@@ -0,0 +1,47 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.tomcat.util.bcel.classfile;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/** 
+ * This class represents a constant pool reference to a field.
+ *
+ * @version $Id: ConstantFieldref.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ */
+public final class ConstantFieldref extends ConstantCP {
+
+
+    private static final long serialVersionUID = -8062332095934294437L;
+
+    /**
+     * Initialize instance from file data.
+     *
+     * @param file input stream
+     * @throws IOException
+     */
+    ConstantFieldref(DataInputStream file) throws IOException {
+        super(Constants.CONSTANT_Fieldref, file);
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantFloat.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantFloat.java
new file mode 100644
index 0000000..a0d5b38
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantFloat.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.bcel.classfile;
+
+import java.io.DataInput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/** 
+ * This class is derived from the abstract 
+ * <A HREF="org.apache.tomcat.util.bcel.classfile.Constant.html">Constant</A> class 
+ * and represents a reference to a float object.
+ *
+ * @version $Id: ConstantFloat.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     Constant
+ */
+public final class ConstantFloat extends Constant implements ConstantObject {
+
+    private static final long serialVersionUID = 8301269629885378651L;
+    private float bytes;
+
+
+    /** 
+     * @param bytes Data
+     */
+    public ConstantFloat(float bytes) {
+        super(Constants.CONSTANT_Float);
+        this.bytes = bytes;
+    }
+
+
+    /** 
+     * Initialize instance from file data.
+     *
+     * @param file Input stream
+     * @throws IOException
+     */
+    ConstantFloat(DataInput file) throws IOException {
+        this(file.readFloat());
+    }
+
+    
+    /**
+     * Dump constant float to file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    @Override
+    public final void dump( DataOutputStream file ) throws IOException {
+        file.writeByte(tag);
+        file.writeFloat(bytes);
+    }
+
+
+    /**
+     * @return data, i.e., 4 bytes.
+     */
+    public final float getBytes() {
+        return bytes;
+    }
+
+
+    /**
+     * @return String representation.
+     */
+    @Override
+    public final String toString() {
+        return super.toString() + "(bytes = " + bytes + ")";
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantInteger.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantInteger.java
new file mode 100644
index 0000000..9934cf3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantInteger.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.bcel.classfile;
+
+import java.io.DataInput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/** 
+ * This class is derived from the abstract 
+ * <A HREF="org.apache.tomcat.util.bcel.classfile.Constant.html">Constant</A> class 
+ * and represents a reference to an int object.
+ *
+ * @version $Id: ConstantInteger.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     Constant
+ */
+public final class ConstantInteger extends Constant implements ConstantObject {
+
+    private static final long serialVersionUID = -6415476571232528966L;
+    private int bytes;
+
+
+    /** 
+     * @param bytes Data
+     */
+    public ConstantInteger(int bytes) {
+        super(Constants.CONSTANT_Integer);
+        this.bytes = bytes;
+    }
+
+
+    /** 
+     * Initialize instance from file data.
+     *
+     * @param file Input stream
+     * @throws IOException
+     */
+    ConstantInteger(DataInput file) throws IOException {
+        this(file.readInt());
+    }
+
+
+    /**
+     * Dump constant integer to file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    @Override
+    public final void dump( DataOutputStream file ) throws IOException {
+        file.writeByte(tag);
+        file.writeInt(bytes);
+    }
+
+
+    /**
+     * @return data, i.e., 4 bytes.
+     */
+    public final int getBytes() {
+        return bytes;
+    }
+
+
+    /**
+     * @return String representation.
+     */
+    @Override
+    public final String toString() {
+        return super.toString() + "(bytes = " + bytes + ")";
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantInterfaceMethodref.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantInterfaceMethodref.java
new file mode 100644
index 0000000..b68cf28
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantInterfaceMethodref.java
@@ -0,0 +1,47 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.tomcat.util.bcel.classfile;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/** 
+ * This class represents a constant pool reference to an interface method.
+ *
+ * @version $Id: ConstantInterfaceMethodref.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ */
+public final class ConstantInterfaceMethodref extends ConstantCP {
+
+
+    private static final long serialVersionUID = -8587605570227841891L;
+
+    /**
+     * Initialize instance from file data.
+     *
+     * @param file input stream
+     * @throws IOException
+     */
+    ConstantInterfaceMethodref(DataInputStream file) throws IOException {
+        super(Constants.CONSTANT_InterfaceMethodref, file);
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantLong.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantLong.java
new file mode 100644
index 0000000..e3d953c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantLong.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.bcel.classfile;
+
+import java.io.DataInput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/** 
+ * This class is derived from the abstract 
+ * <A HREF="org.apache.tomcat.util.bcel.classfile.Constant.html">Constant</A> class 
+ * and represents a reference to a long object.
+ *
+ * @version $Id: ConstantLong.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     Constant
+ */
+public final class ConstantLong extends Constant implements ConstantObject {
+
+    private static final long serialVersionUID = -1893131676489003562L;
+    private long bytes;
+
+
+    /** 
+     * @param bytes Data
+     */
+    public ConstantLong(long bytes) {
+        super(Constants.CONSTANT_Long);
+        this.bytes = bytes;
+    }
+
+
+    /** 
+     * Initialize instance from file data.
+     *
+     * @param file Input stream
+     * @throws IOException
+     */
+    ConstantLong(DataInput file) throws IOException {
+        this(file.readLong());
+    }
+
+
+    /**
+     * Dump constant long to file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    @Override
+    public final void dump( DataOutputStream file ) throws IOException {
+        file.writeByte(tag);
+        file.writeLong(bytes);
+    }
+
+
+    /**
+     * @return data, i.e., 8 bytes.
+     */
+    public final long getBytes() {
+        return bytes;
+    }
+
+
+    /**
+     * @return String representation.
+     */
+    @Override
+    public final String toString() {
+        return super.toString() + "(bytes = " + bytes + ")";
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantMethodref.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantMethodref.java
new file mode 100644
index 0000000..d48f71d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantMethodref.java
@@ -0,0 +1,47 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.tomcat.util.bcel.classfile;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/** 
+ * This class represents a constant pool reference to a method.
+ *
+ * @version $Id: ConstantMethodref.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ */
+public final class ConstantMethodref extends ConstantCP {
+
+
+    private static final long serialVersionUID = -7857009620954576086L;
+
+    /**
+     * Initialize instance from file data.
+     *
+     * @param file input stream
+     * @throws IOException
+     */
+    ConstantMethodref(DataInputStream file) throws IOException {
+        super(Constants.CONSTANT_Methodref, file);
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantNameAndType.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantNameAndType.java
new file mode 100644
index 0000000..380fb19
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantNameAndType.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.tomcat.util.bcel.classfile;
+
+import java.io.DataInput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/** 
+ * This class is derived from the abstract 
+ * <A HREF="org.apache.tomcat.util.bcel.classfile.Constant.html">Constant</A> class 
+ * and represents a reference to the name and signature
+ * of a field or method.
+ *
+ * @version $Id: ConstantNameAndType.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     Constant
+ */
+public final class ConstantNameAndType extends Constant {
+
+    private static final long serialVersionUID = 1010506730811368756L;
+    private int name_index; // Name of field/method
+    private int signature_index; // and its signature.
+
+
+    /**
+     * Initialize instance from file data.
+     *
+     * @param file Input stream
+     * @throws IOException
+     */
+    ConstantNameAndType(DataInput file) throws IOException {
+        this(file.readUnsignedShort(), file.readUnsignedShort());
+    }
+
+
+    /**
+     * @param name_index Name of field/method
+     * @param signature_index and its signature
+     */
+    public ConstantNameAndType(int name_index, int signature_index) {
+        super(Constants.CONSTANT_NameAndType);
+        this.name_index = name_index;
+        this.signature_index = signature_index;
+    }
+
+
+    /**
+     * Dump name and signature index to file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    @Override
+    public final void dump( DataOutputStream file ) throws IOException {
+        file.writeByte(tag);
+        file.writeShort(name_index);
+        file.writeShort(signature_index);
+    }
+
+
+    /**
+     * @return Name index in constant pool of field/method name.
+     */
+    public final int getNameIndex() {
+        return name_index;
+    }
+
+
+    /**
+     * @return Index in constant pool of field/method signature.
+     */
+    public final int getSignatureIndex() {
+        return signature_index;
+    }
+
+
+    /**
+     * @return String representation
+     */
+    @Override
+    public final String toString() {
+        return super.toString() + "(name_index = " + name_index + ", signature_index = "
+                + signature_index + ")";
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantObject.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantObject.java
new file mode 100644
index 0000000..9725fd9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantObject.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.bcel.classfile;
+
+/** 
+ * This interface denotes those constants that have a "natural" value,
+ * such as ConstantLong, ConstantString, etc..
+ *
+ * @version $Id: ConstantObject.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     Constant
+ */
+public interface ConstantObject {
+
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantPool.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantPool.java
new file mode 100644
index 0000000..3f01ec2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantPool.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.bcel.classfile;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.Serializable;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * This class represents the constant pool, i.e., a table of constants, of
+ * a parsed classfile. It may contain null references, due to the JVM
+ * specification that skips an entry after an 8-byte constant (double,
+ * long) entry.  Those interested in generating constant pools
+ * programatically should see <a href="../generic/ConstantPoolGen.html">
+ * ConstantPoolGen</a>.
+
+ * @version $Id: ConstantPool.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ * @see     Constant
+ * @author <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ */
+public class ConstantPool implements Cloneable, Serializable {
+
+    private static final long serialVersionUID = -6765503791185687014L;
+    private int constant_pool_count;
+    private Constant[] constant_pool;
+
+
+    /**
+     * Read constants from given file stream.
+     *
+     * @param file Input stream
+     * @throws IOException
+     * @throws ClassFormatException
+     */
+    ConstantPool(DataInputStream file) throws IOException, ClassFormatException {
+        byte tag;
+        constant_pool_count = file.readUnsignedShort();
+        constant_pool = new Constant[constant_pool_count];
+        /* constant_pool[0] is unused by the compiler and may be used freely
+         * by the implementation.
+         */
+        for (int i = 1; i < constant_pool_count; i++) {
+            constant_pool[i] = Constant.readConstant(file);
+            /* Quote from the JVM specification:
+             * "All eight byte constants take up two spots in the constant pool.
+             * If this is the n'th byte in the constant pool, then the next item
+             * will be numbered n+2"
+             * 
+             * Thus we have to increment the index counter.
+             */
+            tag = constant_pool[i].getTag();
+            if ((tag == Constants.CONSTANT_Double) || (tag == Constants.CONSTANT_Long)) {
+                i++;
+            }
+        }
+    }
+
+
+    /**
+     * Resolve constant to a string representation.
+     *
+     * @param  c Constant to be printed
+     * @return String representation
+     */
+    public String constantToString( Constant c ) throws ClassFormatException {
+        String str;
+        int i;
+        byte tag = c.getTag();
+        switch (tag) {
+            case Constants.CONSTANT_Class:
+                i = ((ConstantClass) c).getNameIndex();
+                c = getConstant(i, Constants.CONSTANT_Utf8);
+                str = Utility.compactClassName(((ConstantUtf8) c).getBytes(), false);
+                break;
+            case Constants.CONSTANT_String:
+                i = ((ConstantString) c).getStringIndex();
+                c = getConstant(i, Constants.CONSTANT_Utf8);
+                str = "\"" + escape(((ConstantUtf8) c).getBytes()) + "\"";
+                break;
+            case Constants.CONSTANT_Utf8:
+                str = ((ConstantUtf8) c).getBytes();
+                break;
+            case Constants.CONSTANT_Double:
+                str = String.valueOf(((ConstantDouble) c).getBytes());
+                break;
+            case Constants.CONSTANT_Float:
+                str = String.valueOf(((ConstantFloat) c).getBytes());
+                break;
+            case Constants.CONSTANT_Long:
+                str = String.valueOf(((ConstantLong) c).getBytes());
+                break;
+            case Constants.CONSTANT_Integer:
+                str = String.valueOf(((ConstantInteger) c).getBytes());
+                break;
+            case Constants.CONSTANT_NameAndType:
+                str = (constantToString(((ConstantNameAndType) c).getNameIndex(),
+                        Constants.CONSTANT_Utf8)
+                        + " " + constantToString(((ConstantNameAndType) c).getSignatureIndex(),
+                        Constants.CONSTANT_Utf8));
+                break;
+            case Constants.CONSTANT_InterfaceMethodref:
+            case Constants.CONSTANT_Methodref:
+            case Constants.CONSTANT_Fieldref:
+                str = (constantToString(((ConstantCP) c).getClassIndex(), Constants.CONSTANT_Class)
+                        + "." + constantToString(((ConstantCP) c).getNameAndTypeIndex(),
+                        Constants.CONSTANT_NameAndType));
+                break;
+            default: // Never reached
+                throw new RuntimeException("Unknown constant type " + tag);
+        }
+        return str;
+    }
+
+
+    private static final String escape( String str ) {
+        int len = str.length();
+        StringBuffer buf = new StringBuffer(len + 5);
+        char[] ch = str.toCharArray();
+        for (int i = 0; i < len; i++) {
+            switch (ch[i]) {
+                case '\n':
+                    buf.append("\\n");
+                    break;
+                case '\r':
+                    buf.append("\\r");
+                    break;
+                case '\t':
+                    buf.append("\\t");
+                    break;
+                case '\b':
+                    buf.append("\\b");
+                    break;
+                case '"':
+                    buf.append("\\\"");
+                    break;
+                default:
+                    buf.append(ch[i]);
+            }
+        }
+        return buf.toString();
+    }
+
+
+    /**
+     * Retrieve constant at `index' from constant pool and resolve it to
+     * a string representation.
+     *
+     * @param  index of constant in constant pool
+     * @param  tag expected type
+     * @return String representation
+     */
+    public String constantToString( int index, byte tag ) throws ClassFormatException {
+        Constant c = getConstant(index, tag);
+        return constantToString(c);
+    }
+
+
+    /**
+     * Get constant from constant pool.
+     *
+     * @param  index Index in constant pool
+     * @return Constant value
+     * @see    Constant
+     */
+    public Constant getConstant( int index ) {
+        if (index >= constant_pool.length || index < 0) {
+            throw new ClassFormatException("Invalid constant pool reference: " + index
+                    + ". Constant pool size is: " + constant_pool.length);
+        }
+        return constant_pool[index];
+    }
+
+
+    /**
+     * Get constant from constant pool and check whether it has the
+     * expected type.
+     *
+     * @param  index Index in constant pool
+     * @param  tag Tag of expected constant, i.e., its type
+     * @return Constant value
+     * @see    Constant
+     * @throws  ClassFormatException
+     */
+    public Constant getConstant( int index, byte tag ) throws ClassFormatException {
+        Constant c;
+        c = getConstant(index);
+        if (c == null) {
+            throw new ClassFormatException("Constant pool at index " + index + " is null.");
+        }
+        if (c.getTag() != tag) {
+            throw new ClassFormatException("Expected class `" + Constants.CONSTANT_NAMES[tag]
+                    + "' at index " + index + " and got " + c);
+        }
+        return c;
+    }
+
+
+    /**
+     * Get string from constant pool and bypass the indirection of 
+     * `ConstantClass' and `ConstantString' objects. I.e. these classes have
+     * an index field that points to another entry of the constant pool of
+     * type `ConstantUtf8' which contains the real data.
+     *
+     * @param  index Index in constant pool
+     * @param  tag Tag of expected constant, either ConstantClass or ConstantString
+     * @return Contents of string reference
+     * @see    ConstantClass
+     * @see    ConstantString
+     * @throws  ClassFormatException
+     */
+    public String getConstantString( int index, byte tag ) throws ClassFormatException {
+        Constant c;
+        int i;
+        c = getConstant(index, tag);
+        /* This switch() is not that elegant, since the two classes have the
+         * same contents, they just differ in the name of the index
+         * field variable.
+         * But we want to stick to the JVM naming conventions closely though
+         * we could have solved these more elegantly by using the same
+         * variable name or by subclassing.
+         */
+        switch (tag) {
+            case Constants.CONSTANT_Class:
+                i = ((ConstantClass) c).getNameIndex();
+                break;
+            case Constants.CONSTANT_String:
+                i = ((ConstantString) c).getStringIndex();
+                break;
+            default:
+                throw new RuntimeException("getConstantString called with illegal tag " + tag);
+        }
+        // Finally get the string from the constant pool
+        c = getConstant(i, Constants.CONSTANT_Utf8);
+        return ((ConstantUtf8) c).getBytes();
+    }
+
+
+    /**
+     * @return Length of constant pool.
+     */
+    public int getLength() {
+        return constant_pool_count;
+    }
+
+
+    /**
+     * @return String representation.
+     */
+    @Override
+    public String toString() {
+        StringBuffer buf = new StringBuffer();
+        for (int i = 1; i < constant_pool_count; i++) {
+            buf.append(i).append(")").append(constant_pool[i]).append("\n");
+        }
+        return buf.toString();
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantString.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantString.java
new file mode 100644
index 0000000..ac82b7f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantString.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.bcel.classfile;
+
+import java.io.DataInput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/** 
+ * This class is derived from the abstract 
+ * <A HREF="org.apache.tomcat.util.bcel.classfile.Constant.html">Constant</A> class 
+ * and represents a reference to a String object.
+ *
+ * @version $Id: ConstantString.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     Constant
+ */
+public final class ConstantString extends Constant implements ConstantObject {
+
+    private static final long serialVersionUID = 2809338612858801341L;
+    private int string_index; // Identical to ConstantClass except for this name
+
+
+    /** 
+     * Initialize instance from file data.
+     *
+     * @param file Input stream
+     * @throws IOException
+     */
+    ConstantString(DataInput file) throws IOException {
+        this(file.readUnsignedShort());
+    }
+
+
+    /**
+     * @param string_index Index of Constant_Utf8 in constant pool
+     */
+    public ConstantString(int string_index) {
+        super(Constants.CONSTANT_String);
+        this.string_index = string_index;
+    }
+
+
+    /**
+     * Dump constant field reference to file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    @Override
+    public final void dump( DataOutputStream file ) throws IOException {
+        file.writeByte(tag);
+        file.writeShort(string_index);
+    }
+
+
+    /**
+     * @return Index in constant pool of the string (ConstantUtf8).
+     */
+    public final int getStringIndex() {
+        return string_index;
+    }
+
+
+    /**
+     * @return String representation.
+     */
+    @Override
+    public final String toString() {
+        return super.toString() + "(string_index = " + string_index + ")";
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantUtf8.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantUtf8.java
new file mode 100644
index 0000000..f04b337
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantUtf8.java
@@ -0,0 +1,80 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.tomcat.util.bcel.classfile;
+
+import java.io.DataInput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/** 
+ * This class is derived from the abstract 
+ * <A HREF="org.apache.tomcat.util.bcel.classfile.Constant.html">Constant</A> class 
+ * and represents a reference to a Utf8 encoded string.
+ *
+ * @version $Id: ConstantUtf8.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     Constant
+ */
+public final class ConstantUtf8 extends Constant {
+
+    private static final long serialVersionUID = 8119001312020421976L;
+    private String bytes;
+
+
+    /**
+     * Initialize instance from file data.
+     *
+     * @param file Input stream
+     * @throws IOException
+     */
+    ConstantUtf8(DataInput file) throws IOException {
+        super(Constants.CONSTANT_Utf8);
+        bytes = file.readUTF();
+    }
+
+
+    /**
+     * Dump String in Utf8 format to file stream.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    @Override
+    public final void dump( DataOutputStream file ) throws IOException {
+        file.writeByte(tag);
+        file.writeUTF(bytes);
+    }
+
+
+    /**
+     * @return Data converted to string.
+     */
+    public final String getBytes() {
+        return bytes;
+    }
+
+
+    /**
+     * @return String representation
+     */
+    @Override
+    public final String toString() {
+        return super.toString() + "(\"" + Utility.replace(bytes, "\n", "\\n") + "\")";
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantValue.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantValue.java
new file mode 100644
index 0000000..c951ecd
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ConstantValue.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.bcel.classfile;
+
+import java.io.DataInput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * This class is derived from <em>Attribute</em> and represents a constant 
+ * value, i.e., a default value for initializing a class field.
+ * This class is instantiated by the <em>Attribute.readAttribute()</em> method.
+ *
+ * @version $Id: ConstantValue.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     Attribute
+ */
+public final class ConstantValue extends Attribute {
+
+    private static final long serialVersionUID = -388222612752527969L;
+    private int constantvalue_index;
+
+
+    /**
+     * Construct object from file stream.
+     * @param name_index Name index in constant pool
+     * @param length Content length in bytes
+     * @param file Input stream
+     * @param constant_pool Array of constants
+     * @throws IOException
+     */
+    ConstantValue(int name_index, int length, DataInput file, ConstantPool constant_pool)
+            throws IOException {
+        this(name_index, length, file.readUnsignedShort(), constant_pool);
+    }
+
+
+    /**
+     * @param name_index Name index in constant pool
+     * @param length Content length in bytes
+     * @param constantvalue_index Index in constant pool
+     * @param constant_pool Array of constants
+     */
+    public ConstantValue(int name_index, int length, int constantvalue_index,
+            ConstantPool constant_pool) {
+        super(Constants.ATTR_CONSTANT_VALUE, name_index, length, constant_pool);
+        this.constantvalue_index = constantvalue_index;
+    }
+
+
+    /**
+     * Dump constant value attribute to file stream on binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    @Override
+    public final void dump( DataOutputStream file ) throws IOException {
+        super.dump(file);
+        file.writeShort(constantvalue_index);
+    }
+
+
+    /**
+     * @return String representation of constant value.
+     */
+    @Override
+    public final String toString() {
+        Constant c = constant_pool.getConstant(constantvalue_index);
+        String buf;
+        int i;
+        // Print constant to string depending on its type
+        switch (c.getTag()) {
+            case Constants.CONSTANT_Long:
+                buf = String.valueOf(((ConstantLong) c).getBytes());
+                break;
+            case Constants.CONSTANT_Float:
+                buf = String.valueOf(((ConstantFloat) c).getBytes());
+                break;
+            case Constants.CONSTANT_Double:
+                buf = String.valueOf(((ConstantDouble) c).getBytes());
+                break;
+            case Constants.CONSTANT_Integer:
+                buf = String.valueOf(((ConstantInteger) c).getBytes());
+                break;
+            case Constants.CONSTANT_String:
+                i = ((ConstantString) c).getStringIndex();
+                c = constant_pool.getConstant(i, Constants.CONSTANT_Utf8);
+                buf = "\"" + Utility.convertString(((ConstantUtf8) c).getBytes()) + "\"";
+                break;
+            default:
+                throw new IllegalStateException("Type of ConstValue invalid: " + c);
+        }
+        return buf;
+    }
+
+
+    /**
+     * @return deep copy of this attribute
+     */
+    @Override
+    public Attribute copy( ConstantPool _constant_pool ) {
+        ConstantValue c = (ConstantValue) clone();
+        c.constant_pool = _constant_pool;
+        return c;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Deprecated.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Deprecated.java
new file mode 100644
index 0000000..4ccabe7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Deprecated.java
@@ -0,0 +1,109 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.tomcat.util.bcel.classfile;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * This class is derived from <em>Attribute</em> and denotes that this is a
+ * deprecated method.
+ * It is instantiated from the <em>Attribute.readAttribute()</em> method.
+ *
+ * @version $Id: Deprecated.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     Attribute
+ */
+public final class Deprecated extends Attribute {
+
+    private static final long serialVersionUID = 8499975360019639912L;
+    private byte[] bytes;
+
+
+    /**
+     * @param name_index Index in constant pool to CONSTANT_Utf8
+     * @param length Content length in bytes
+     * @param bytes Attribute contents
+     * @param constant_pool Array of constants
+     */
+    public Deprecated(int name_index, int length, byte[] bytes, ConstantPool constant_pool) {
+        super(Constants.ATTR_DEPRECATED, name_index, length, constant_pool);
+        this.bytes = bytes;
+    }
+
+
+    /**
+     * Construct object from file stream.
+     * @param name_index Index in constant pool to CONSTANT_Utf8
+     * @param length Content length in bytes
+     * @param file Input stream
+     * @param constant_pool Array of constants
+     * @throws IOException
+     */
+    Deprecated(int name_index, int length, DataInputStream file, ConstantPool constant_pool)
+            throws IOException {
+        this(name_index, length, (byte[]) null, constant_pool);
+        if (length > 0) {
+            bytes = new byte[length];
+            file.readFully(bytes);
+            System.err.println("Deprecated attribute with length > 0");
+        }
+    }
+
+
+    /**
+     * Dump source file attribute to file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    @Override
+    public final void dump( DataOutputStream file ) throws IOException {
+        super.dump(file);
+        if (length > 0) {
+            file.write(bytes, 0, length);
+        }
+    }
+
+
+    /**
+     * @return attribute name
+     */
+    @Override
+    public final String toString() {
+        return Constants.ATTRIBUTE_NAMES[Constants.ATTR_DEPRECATED];
+    }
+
+
+    /**
+     * @return deep copy of this attribute
+     */
+    @Override
+    public Attribute copy( ConstantPool _constant_pool ) {
+        Deprecated c = (Deprecated) clone();
+        if (bytes != null) {
+            c.bytes = new byte[bytes.length];
+            System.arraycopy(bytes, 0, c.bytes, 0, bytes.length);
+        }
+        c.constant_pool = _constant_pool;
+        return c;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ElementValue.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ElementValue.java
new file mode 100644
index 0000000..cdd4d7e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ElementValue.java
@@ -0,0 +1,135 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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.classfile;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * @version $Id: ElementValue
+ * @author <A HREF="mailto:dbrosius@qis.net">D. Brosius</A>
+ * @since 5.3
+ */
+public abstract class ElementValue
+{
+    protected int type;
+
+    protected ConstantPool cpool;
+
+    @Override
+    public String toString()
+    {
+        return stringifyValue();
+    }
+
+    protected ElementValue(int type, ConstantPool cpool)
+    {
+        this.type = type;
+        this.cpool = cpool;
+    }
+
+
+    public abstract String stringifyValue();
+
+    public abstract void dump(DataOutputStream dos) throws IOException;
+
+    public static final int STRING = 's';
+
+    public static final int ENUM_CONSTANT = 'e';
+
+    public static final int CLASS = 'c';
+
+    public static final int ANNOTATION = '@';
+
+    public static final int ARRAY = '[';
+
+    public static final int PRIMITIVE_INT = 'I';
+
+    public static final int PRIMITIVE_BYTE = 'B';
+
+    public static final int PRIMITIVE_CHAR = 'C';
+
+    public static final int PRIMITIVE_DOUBLE = 'D';
+
+    public static final int PRIMITIVE_FLOAT = 'F';
+
+    public static final int PRIMITIVE_LONG = 'J';
+
+    public static final int PRIMITIVE_SHORT = 'S';
+
+    public static final int PRIMITIVE_BOOLEAN = 'Z';
+
+    public static ElementValue readElementValue(DataInputStream dis,
+            ConstantPool cpool) throws IOException
+    {
+        byte type = dis.readByte();
+        switch (type)
+        {
+        case 'B': // byte
+            return new SimpleElementValue(PRIMITIVE_BYTE, dis
+                    .readUnsignedShort(), cpool);
+        case 'C': // char
+            return new SimpleElementValue(PRIMITIVE_CHAR, dis
+                    .readUnsignedShort(), cpool);
+        case 'D': // double
+            return new SimpleElementValue(PRIMITIVE_DOUBLE, dis
+                    .readUnsignedShort(), cpool);
+        case 'F': // float
+            return new SimpleElementValue(PRIMITIVE_FLOAT, dis
+                    .readUnsignedShort(), cpool);
+        case 'I': // int
+            return new SimpleElementValue(PRIMITIVE_INT, dis
+                    .readUnsignedShort(), cpool);
+        case 'J': // long
+            return new SimpleElementValue(PRIMITIVE_LONG, dis
+                    .readUnsignedShort(), cpool);
+        case 'S': // short
+            return new SimpleElementValue(PRIMITIVE_SHORT, dis
+                    .readUnsignedShort(), cpool);
+        case 'Z': // boolean
+            return new SimpleElementValue(PRIMITIVE_BOOLEAN, dis
+                    .readUnsignedShort(), cpool);
+        case 's': // String
+            return new SimpleElementValue(STRING, dis.readUnsignedShort(),
+                    cpool);
+        case 'e': // Enum constant
+            return new EnumElementValue(ENUM_CONSTANT, dis.readUnsignedShort(),
+                    dis.readUnsignedShort(), cpool);
+        case 'c': // Class
+            return new ClassElementValue(CLASS, dis.readUnsignedShort(), cpool);
+        case '@': // Annotation
+            // TODO isRuntimeVisible
+            return new AnnotationElementValue(ANNOTATION, AnnotationEntry.read(
+                    dis, cpool), cpool);
+        case '[': // Array
+            int numArrayVals = dis.readUnsignedShort();
+            ElementValue[] evalues = new ElementValue[numArrayVals];
+            for (int j = 0; j < numArrayVals; j++)
+            {
+                evalues[j] = ElementValue.readElementValue(dis, cpool);
+            }
+            return new ArrayElementValue(ARRAY, evalues, cpool);
+        default:
+            throw new RuntimeException(
+                    "Unexpected element value kind in annotation: " + type);
+        }
+    }
+
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ElementValuePair.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ElementValuePair.java
new file mode 100644
index 0000000..f9a3a95
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ElementValuePair.java
@@ -0,0 +1,64 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.tomcat.util.bcel.classfile;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * an annotation's element value pair
+ * 
+ * @version $Id: ElementValuePair
+ * @author <A HREF="mailto:dbrosius@qis.net">D. Brosius</A>
+ * @since 5.3
+ */
+public class ElementValuePair
+{
+    private ElementValue elementValue;
+
+    private ConstantPool constantPool;
+
+    private int elementNameIndex;
+
+    public ElementValuePair(int elementNameIndex, ElementValue elementValue,
+            ConstantPool constantPool)
+    {
+        this.elementValue = elementValue;
+        this.elementNameIndex = elementNameIndex;
+        this.constantPool = constantPool;
+    }
+
+    public String getNameString()
+    {
+        ConstantUtf8 c = (ConstantUtf8) constantPool.getConstant(
+                elementNameIndex, Constants.CONSTANT_Utf8);
+        return c.getBytes();
+    }
+
+    public final ElementValue getValue()
+    {
+        return elementValue;
+    }
+    
+    protected void dump(DataOutputStream dos) throws IOException {
+        dos.writeShort(elementNameIndex); // u2 name of the element
+        elementValue.dump(dos);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/EnclosingMethod.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/EnclosingMethod.java
new file mode 100644
index 0000000..4592923
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/EnclosingMethod.java
@@ -0,0 +1,71 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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.classfile;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * This attribute exists for local or 
+ * anonymous classes and ... there can be only one.
+ */
+public class EnclosingMethod extends Attribute {
+    
+    private static final long serialVersionUID = 6755214228300933233L;
+
+    // Pointer to the CONSTANT_Class_info structure representing the 
+    // innermost class that encloses the declaration of the current class.
+    private int classIndex;
+    
+    // If the current class is not immediately enclosed by a method or 
+    // constructor, then the value of the method_index item must be zero.  
+    // Otherwise, the value of the  method_index item must point to a 
+    // CONSTANT_NameAndType_info structure representing the name and the 
+    // type of a method in the class referenced by the class we point 
+    // to in the class_index.  *It is the compiler responsibility* to 
+    // ensure that the method identified by this index is the closest 
+    // lexically enclosing method that includes the local/anonymous class.
+    private int methodIndex;
+
+    // Ctors - and code to read an attribute in.
+    public EnclosingMethod(int nameIndex, int len, DataInputStream dis, ConstantPool cpool) throws IOException {
+        this(nameIndex, len, dis.readUnsignedShort(), dis.readUnsignedShort(), cpool);
+    }
+
+    private EnclosingMethod(int nameIndex, int len, int classIdx,int methodIdx, ConstantPool cpool) {
+        super(Constants.ATTR_ENCLOSING_METHOD, nameIndex, len, cpool);
+        classIndex  = classIdx;
+        methodIndex = methodIdx;
+    }
+
+    @Override
+    public Attribute copy(ConstantPool constant_pool) {
+        throw new RuntimeException("Not implemented yet!");
+        // is this next line sufficient?
+        // return (EnclosingMethod)clone();
+    }
+    
+    @Override
+    public final void dump(DataOutputStream file) throws IOException {
+        super.dump(file);
+        file.writeShort(classIndex);
+        file.writeShort(methodIndex);
+    }    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/EnumElementValue.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/EnumElementValue.java
new file mode 100644
index 0000000..3837e2a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/EnumElementValue.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.tomcat.util.bcel.classfile;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+public class EnumElementValue extends ElementValue
+{
+    // For enum types, these two indices point to the type and value
+    private int typeIdx;
+
+    private int valueIdx;
+
+    public EnumElementValue(int type, int typeIdx, int valueIdx,
+            ConstantPool cpool)
+    {
+        super(type, cpool);
+        if (type != ENUM_CONSTANT)
+            throw new RuntimeException(
+                    "Only element values of type enum can be built with this ctor - type specified: " + type);
+        this.typeIdx = typeIdx;
+        this.valueIdx = valueIdx;
+    }
+
+    @Override
+    public void dump(DataOutputStream dos) throws IOException
+    {
+        dos.writeByte(type); // u1 type of value (ENUM_CONSTANT == 'e')
+        dos.writeShort(typeIdx); // u2
+        dos.writeShort(valueIdx); // u2
+    }
+
+    @Override
+    public String stringifyValue()
+    {
+        ConstantUtf8 cu8 = (ConstantUtf8) cpool.getConstant(valueIdx,
+                Constants.CONSTANT_Utf8);
+        return cu8.getBytes();
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ExceptionTable.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ExceptionTable.java
new file mode 100644
index 0000000..f0ef1bb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ExceptionTable.java
@@ -0,0 +1,136 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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.classfile;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/** 
+ * This class represents the table of exceptions that are thrown by a
+ * method. This attribute may be used once per method.  The name of
+ * this class is <em>ExceptionTable</em> for historical reasons; The
+ * Java Virtual Machine Specification, Second Edition defines this
+ * attribute using the name <em>Exceptions</em> (which is inconsistent
+ * with the other classes).
+ *
+ * @version $Id: ExceptionTable.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     Code
+ */
+public final class ExceptionTable extends Attribute {
+
+    private static final long serialVersionUID = -5109672682663772900L;
+    private int number_of_exceptions; // Table of indices into
+    private int[] exception_index_table; // constant pool
+
+
+    /**
+     * @param name_index Index in constant pool
+     * @param length Content length in bytes
+     * @param exception_index_table Table of indices in constant pool
+     * @param constant_pool Array of constants
+     */
+    public ExceptionTable(int name_index, int length, int[] exception_index_table,
+            ConstantPool constant_pool) {
+        super(Constants.ATTR_EXCEPTIONS, name_index, length, constant_pool);
+        setExceptionIndexTable(exception_index_table);
+    }
+
+
+    /**
+     * Construct object from file stream.
+     * @param name_index Index in constant pool
+     * @param length Content length in bytes
+     * @param file Input stream
+     * @param constant_pool Array of constants
+     * @throws IOException
+     */
+    ExceptionTable(int name_index, int length, DataInputStream file, ConstantPool constant_pool)
+            throws IOException {
+        this(name_index, length, (int[]) null, constant_pool);
+        number_of_exceptions = file.readUnsignedShort();
+        exception_index_table = new int[number_of_exceptions];
+        for (int i = 0; i < number_of_exceptions; i++) {
+            exception_index_table[i] = file.readUnsignedShort();
+        }
+    }
+
+
+    /**
+     * Dump exceptions attribute to file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    @Override
+    public final void dump( DataOutputStream file ) throws IOException {
+        super.dump(file);
+        file.writeShort(number_of_exceptions);
+        for (int i = 0; i < number_of_exceptions; i++) {
+            file.writeShort(exception_index_table[i]);
+        }
+    }
+
+
+    /**
+     * @param exception_index_table the list of exception indexes
+     * Also redefines number_of_exceptions according to table length.
+     */
+    public final void setExceptionIndexTable( int[] exception_index_table ) {
+        this.exception_index_table = exception_index_table;
+        number_of_exceptions = (exception_index_table == null) ? 0 : exception_index_table.length;
+    }
+
+
+    /**
+     * @return String representation, i.e., a list of thrown exceptions.
+     */
+    @Override
+    public final String toString() {
+        StringBuffer buf = new StringBuffer();
+        String str;
+        for (int i = 0; i < number_of_exceptions; i++) {
+            str = constant_pool.getConstantString(exception_index_table[i],
+                    Constants.CONSTANT_Class);
+            buf.append(Utility.compactClassName(str, false));
+            if (i < number_of_exceptions - 1) {
+                buf.append(", ");
+            }
+        }
+        return buf.toString();
+    }
+
+
+    /**
+     * @return deep copy of this attribute
+     */
+    @Override
+    public Attribute copy( ConstantPool _constant_pool ) {
+        ExceptionTable c = (ExceptionTable) clone();
+        if (exception_index_table != null) {
+            c.exception_index_table = new int[exception_index_table.length];
+            System.arraycopy(exception_index_table, 0, c.exception_index_table, 0,
+                    exception_index_table.length);
+        }
+        c.constant_pool = _constant_pool;
+        return c;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Field.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Field.java
new file mode 100644
index 0000000..9fb7a82
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Field.java
@@ -0,0 +1,131 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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.classfile;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+import org.apache.tomcat.util.bcel.util.BCELComparator;
+
+/**
+ * This class represents the field info structure, i.e., the representation 
+ * for a variable in the class. See JVM specification for details.
+ *
+ * @version $Id: Field.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ */
+public final class Field extends FieldOrMethod {
+
+    private static final long serialVersionUID = 2646214544240375238L;
+    private static BCELComparator _cmp = new BCELComparator() {
+
+        @Override
+        public boolean equals( Object o1, Object o2 ) {
+            Field THIS = (Field) o1;
+            Field THAT = (Field) o2;
+            return THIS.getName().equals(THAT.getName())
+                    && THIS.getSignature().equals(THAT.getSignature());
+        }
+
+
+        @Override
+        public int hashCode( Object o ) {
+            Field THIS = (Field) o;
+            return THIS.getSignature().hashCode() ^ THIS.getName().hashCode();
+        }
+    };
+
+
+    /**
+     * Construct object from file stream.
+     * @param file Input stream
+     */
+    Field(DataInputStream file, ConstantPool constant_pool) throws IOException,
+            ClassFormatException {
+        super(file, constant_pool);
+    }
+
+
+    /**
+     * @return constant value associated with this field (may be null)
+     */
+    public final ConstantValue getConstantValue() {
+        for (int i = 0; i < attributes_count; i++) {
+            if (attributes[i].getTag() == Constants.ATTR_CONSTANT_VALUE) {
+                return (ConstantValue) attributes[i];
+            }
+        }
+        return null;
+    }
+
+
+    /**
+     * Return string representation close to declaration format,
+     * `public static final short MAX = 100', e.g..
+     *
+     * @return String representation of field, including the signature.
+     */
+    @Override
+    public final String toString() {
+        String name, signature, access; // Short cuts to constant pool
+        // Get names from constant pool
+        access = Utility.accessToString(access_flags);
+        access = access.equals("") ? "" : (access + " ");
+        signature = Utility.signatureToString(getSignature());
+        name = getName();
+        StringBuffer buf = new StringBuffer(64);
+        buf.append(access).append(signature).append(" ").append(name);
+        ConstantValue cv = getConstantValue();
+        if (cv != null) {
+            buf.append(" = ").append(cv);
+        }
+        for (int i = 0; i < attributes_count; i++) {
+            Attribute a = attributes[i];
+            if (!(a instanceof ConstantValue)) {
+                buf.append(" [").append(a.toString()).append("]");
+            }
+        }
+        return buf.toString();
+    }
+
+
+    /**
+     * Return value as defined by given BCELComparator strategy.
+     * By default two Field objects are said to be equal when
+     * their names and signatures are equal.
+     * 
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals( Object obj ) {
+        return _cmp.equals(this, obj);
+    }
+
+
+    /**
+     * Return value as defined by given BCELComparator strategy.
+     * By default return the hashcode of the field's name XOR signature.
+     * 
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        return _cmp.hashCode(this);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/FieldOrMethod.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/FieldOrMethod.java
new file mode 100644
index 0000000..89cc870
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/FieldOrMethod.java
@@ -0,0 +1,107 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.tomcat.util.bcel.classfile;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/** 
+ * Abstract super class for fields and methods.
+ *
+ * @version $Id: FieldOrMethod.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ */
+public abstract class FieldOrMethod extends AccessFlags implements Cloneable {
+
+    private static final long serialVersionUID = -3383525930205542157L;
+    protected int name_index; // Points to field name in constant pool 
+    protected int signature_index; // Points to encoded signature
+    protected int attributes_count; // No. of attributes
+    protected Attribute[] attributes; // Collection of attributes
+    
+    protected ConstantPool constant_pool;
+
+    FieldOrMethod() {
+    }
+
+
+    /**
+     * Construct object from file stream.
+     * @param file Input stream
+     * @throws IOException
+     * @throws ClassFormatException
+     */
+    protected FieldOrMethod(DataInputStream file, ConstantPool constant_pool) throws IOException,
+            ClassFormatException {
+        this(file.readUnsignedShort(), file.readUnsignedShort(), file.readUnsignedShort(), null,
+                constant_pool);
+        attributes_count = file.readUnsignedShort();
+        attributes = new Attribute[attributes_count];
+        for (int i = 0; i < attributes_count; i++) {
+            attributes[i] = Attribute.readAttribute(file, constant_pool);
+        }
+    }
+
+
+    /**
+     * @param access_flags Access rights of method
+     * @param name_index Points to field name in constant pool
+     * @param signature_index Points to encoded signature
+     * @param attributes Collection of attributes
+     * @param constant_pool Array of constants
+     */
+    protected FieldOrMethod(int access_flags, int name_index, int signature_index,
+            Attribute[] attributes, ConstantPool constant_pool) {
+        this.access_flags = access_flags;
+        this.name_index = name_index;
+        this.signature_index = signature_index;
+        this.constant_pool = constant_pool;
+        setAttributes(attributes);
+    }
+
+
+    /**
+     * @param attributes Collection of object attributes.
+     */
+    public final void setAttributes( Attribute[] attributes ) {
+        this.attributes = attributes;
+        attributes_count = (attributes == null) ? 0 : attributes.length;
+    }
+
+    
+    /**
+     * @return Name of object, i.e., method name or field name
+     */
+    public final String getName() {
+        ConstantUtf8 c;
+        c = (ConstantUtf8) constant_pool.getConstant(name_index, Constants.CONSTANT_Utf8);
+        return c.getBytes();
+    }
+
+
+    /**
+     * @return String representation of object's type signature (java style)
+     */
+    public final String getSignature() {
+        ConstantUtf8 c;
+        c = (ConstantUtf8) constant_pool.getConstant(signature_index, Constants.CONSTANT_Utf8);
+        return c.getBytes();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/InnerClass.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/InnerClass.java
new file mode 100644
index 0000000..a8d6511
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/InnerClass.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.tomcat.util.bcel.classfile;
+
+import java.io.DataInput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.Serializable;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/** 
+ * This class represents a inner class attribute, i.e., the class
+ * indices of the inner and outer classes, the name and the attributes
+ * of the inner class.
+ *
+ * @version $Id: InnerClass.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see InnerClasses
+ */
+public final class InnerClass implements Cloneable, Serializable {
+
+    private static final long serialVersionUID = -4964694103982806087L;
+    private int inner_class_index;
+    private int outer_class_index;
+    private int inner_name_index;
+    private int inner_access_flags;
+
+
+    /**
+     * Construct object from file stream.
+     * @param file Input stream
+     * @throws IOException
+     */
+    InnerClass(DataInput file) throws IOException {
+        this(file.readUnsignedShort(), file.readUnsignedShort(), file.readUnsignedShort(), file
+                .readUnsignedShort());
+    }
+
+
+    /**
+     * @param inner_class_index Class index in constant pool of inner class
+     * @param outer_class_index Class index in constant pool of outer class
+     * @param inner_name_index  Name index in constant pool of inner class
+     * @param inner_access_flags Access flags of inner class
+     */
+    public InnerClass(int inner_class_index, int outer_class_index, int inner_name_index,
+            int inner_access_flags) {
+        this.inner_class_index = inner_class_index;
+        this.outer_class_index = outer_class_index;
+        this.inner_name_index = inner_name_index;
+        this.inner_access_flags = inner_access_flags;
+    }
+
+
+    /**
+     * Dump inner class attribute to file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    public final void dump( DataOutputStream file ) throws IOException {
+        file.writeShort(inner_class_index);
+        file.writeShort(outer_class_index);
+        file.writeShort(inner_name_index);
+        file.writeShort(inner_access_flags);
+    }
+
+
+    /**
+     * @return String representation.
+     */
+    @Override
+    public final String toString() {
+        return "InnerClass(" + inner_class_index + ", " + outer_class_index + ", "
+                + inner_name_index + ", " + inner_access_flags + ")";
+    }
+
+
+    /**
+     * @return Resolved string representation
+     */
+    public final String toString( ConstantPool constant_pool ) {
+        String inner_class_name, outer_class_name, inner_name, access;
+        inner_class_name = constant_pool.getConstantString(inner_class_index,
+                Constants.CONSTANT_Class);
+        inner_class_name = Utility.compactClassName(inner_class_name);
+        if (outer_class_index != 0) {
+            outer_class_name = constant_pool.getConstantString(outer_class_index,
+                    Constants.CONSTANT_Class);
+            outer_class_name = Utility.compactClassName(outer_class_name);
+        } else {
+            outer_class_name = "<not a member>";
+        }
+        if (inner_name_index != 0) {
+            inner_name = ((ConstantUtf8) constant_pool.getConstant(inner_name_index,
+                    Constants.CONSTANT_Utf8)).getBytes();
+        } else {
+            inner_name = "<anonymous>";
+        }
+        access = Utility.accessToString(inner_access_flags, true);
+        access = access.equals("") ? "" : (access + " ");
+        return "InnerClass:" + access + inner_class_name + "(\"" + outer_class_name + "\", \""
+                + inner_name + "\")";
+    }
+
+
+    /**
+     * @return deep copy of this object
+     */
+    public InnerClass copy() {
+        try {
+            return (InnerClass) clone();
+        } catch (CloneNotSupportedException e) {
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/InnerClasses.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/InnerClasses.java
new file mode 100644
index 0000000..3dfa9fb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/InnerClasses.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.tomcat.util.bcel.classfile;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * This class is derived from <em>Attribute</em> and denotes that this class
+ * is an Inner class of another.
+ * to the source file of this class.
+ * It is instantiated from the <em>Attribute.readAttribute()</em> method.
+ *
+ * @version $Id: InnerClasses.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     Attribute
+ */
+public final class InnerClasses extends Attribute {
+
+    private static final long serialVersionUID = 54179484605570305L;
+    private InnerClass[] inner_classes;
+    private int number_of_classes;
+
+
+    /**
+     * @param name_index Index in constant pool to CONSTANT_Utf8
+     * @param length Content length in bytes
+     * @param inner_classes array of inner classes attributes
+     * @param constant_pool Array of constants
+     */
+    public InnerClasses(int name_index, int length, InnerClass[] inner_classes,
+            ConstantPool constant_pool) {
+        super(Constants.ATTR_INNER_CLASSES, name_index, length, constant_pool);
+        setInnerClasses(inner_classes);
+    }
+
+
+    /**
+     * Construct object from file stream.
+     *
+     * @param name_index Index in constant pool to CONSTANT_Utf8
+     * @param length Content length in bytes
+     * @param file Input stream
+     * @param constant_pool Array of constants
+     * @throws IOException
+     */
+    InnerClasses(int name_index, int length, DataInputStream file, ConstantPool constant_pool)
+            throws IOException {
+        this(name_index, length, (InnerClass[]) null, constant_pool);
+        number_of_classes = file.readUnsignedShort();
+        inner_classes = new InnerClass[number_of_classes];
+        for (int i = 0; i < number_of_classes; i++) {
+            inner_classes[i] = new InnerClass(file);
+        }
+    }
+
+
+    /**
+     * Dump source file attribute to file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    @Override
+    public final void dump( DataOutputStream file ) throws IOException {
+        super.dump(file);
+        file.writeShort(number_of_classes);
+        for (int i = 0; i < number_of_classes; i++) {
+            inner_classes[i].dump(file);
+        }
+    }
+
+
+    /**
+     * @param inner_classes the array of inner classes
+     */
+    public final void setInnerClasses( InnerClass[] inner_classes ) {
+        this.inner_classes = inner_classes;
+        number_of_classes = (inner_classes == null) ? 0 : inner_classes.length;
+    }
+
+
+    /**
+     * @return String representation.
+     */
+    @Override
+    public final String toString() {
+        StringBuffer buf = new StringBuffer();
+        for (int i = 0; i < number_of_classes; i++) {
+            buf.append(inner_classes[i].toString(constant_pool)).append("\n");
+        }
+        return buf.toString();
+    }
+
+
+    /**
+     * @return deep copy of this attribute
+     */
+    @Override
+    public Attribute copy( ConstantPool _constant_pool ) {
+        InnerClasses c = (InnerClasses) clone();
+        c.inner_classes = new InnerClass[number_of_classes];
+        for (int i = 0; i < number_of_classes; i++) {
+            c.inner_classes[i] = inner_classes[i].copy();
+        }
+        c.constant_pool = _constant_pool;
+        return c;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/JavaClass.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/JavaClass.java
new file mode 100644
index 0000000..1357ccf
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/JavaClass.java
@@ -0,0 +1,285 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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.classfile;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.apache.tomcat.util.bcel.Constants;
+import org.apache.tomcat.util.bcel.util.BCELComparator;
+
+/**
+ * Represents a Java class, i.e., the data structures, constant pool,
+ * fields, methods and commands contained in a Java .class file.
+ * See <a href="ftp://java.sun.com/docs/specs/">JVM specification</a> for details.
+ * The intent of this class is to represent a parsed or otherwise existing
+ * class file.  Those interested in programatically generating classes
+ * should see the <a href="../generic/ClassGen.html">ClassGen</a> class.
+
+ * @version $Id: JavaClass.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ */
+public class JavaClass extends AccessFlags
+        implements Cloneable, Comparable<JavaClass> {
+
+    private static final long serialVersionUID = 7029227708237523236L;
+    private String file_name;
+    private String source_file_name = "<Unknown>";
+    private String class_name;
+    private String superclass_name;
+    private int major, minor; // Compiler version
+    private ConstantPool constant_pool; // Constant pool
+    private int[] interfaces; // implemented interfaces
+    private String[] interface_names;
+    private Field[] fields; // Fields, i.e., variables of class
+    private Method[] methods; // methods defined in the class
+    private Attribute[] attributes; // attributes defined in the class
+    private AnnotationEntry[] annotations;   // annotations defined on the class
+
+
+    //  Annotations are collected from certain attributes, don't do it more than necessary!
+    private boolean annotationsOutOfDate = true;
+    
+    private static BCELComparator _cmp = new BCELComparator() {
+
+        @Override
+        public boolean equals( Object o1, Object o2 ) {
+            JavaClass THIS = (JavaClass) o1;
+            JavaClass THAT = (JavaClass) o2;
+            return THIS.getClassName().equals(THAT.getClassName());
+        }
+
+
+        @Override
+        public int hashCode( Object o ) {
+            JavaClass THIS = (JavaClass) o;
+            return THIS.getClassName().hashCode();
+        }
+    };
+
+
+    /**
+     * Constructor gets all contents as arguments.
+     *
+     * @param class_name_index Index into constant pool referencing a
+     * ConstantClass that represents this class.
+     * @param superclass_name_index Index into constant pool referencing a
+     * ConstantClass that represents this class's superclass.
+     * @param file_name File name
+     * @param major Major compiler version
+     * @param minor Minor compiler version
+     * @param access_flags Access rights defined by bit flags
+     * @param constant_pool Array of constants
+     * @param interfaces Implemented interfaces
+     * @param fields Class fields
+     * @param methods Class methods
+     * @param attributes Class attributes
+     */
+    public JavaClass(int class_name_index, int superclass_name_index, String file_name, int major,
+            int minor, int access_flags, ConstantPool constant_pool, int[] interfaces,
+            Field[] fields, Method[] methods, Attribute[] attributes) {
+        if (interfaces == null) {
+            interfaces = new int[0];
+        }
+        if (attributes == null) {
+            attributes = new Attribute[0];
+        }
+        if (fields == null) {
+            fields = new Field[0];
+        }
+        if (methods == null) {
+            methods = new Method[0];
+        }
+        this.file_name = file_name;
+        this.major = major;
+        this.minor = minor;
+        this.access_flags = access_flags;
+        this.constant_pool = constant_pool;
+        this.interfaces = interfaces;
+        this.fields = fields;
+        this.methods = methods;
+        this.attributes = attributes;
+        annotationsOutOfDate = true;
+        // Get source file name if available
+        for (int i = 0; i < attributes.length; i++) {
+            if (attributes[i] instanceof SourceFile) {
+                source_file_name = ((SourceFile) attributes[i]).getSourceFileName();
+                break;
+            }
+        }
+        /* According to the specification the following entries must be of type
+         * `ConstantClass' but we check that anyway via the 
+         * `ConstPool.getConstant' method.
+         */
+        class_name = constant_pool.getConstantString(class_name_index, Constants.CONSTANT_Class);
+        class_name = Utility.compactClassName(class_name, false);
+        if (superclass_name_index > 0) {
+            // May be zero -> class is java.lang.Object
+            superclass_name = constant_pool.getConstantString(superclass_name_index,
+                    Constants.CONSTANT_Class);
+            superclass_name = Utility.compactClassName(superclass_name, false);
+        } else {
+            superclass_name = "java.lang.Object";
+        }
+        interface_names = new String[interfaces.length];
+        for (int i = 0; i < interfaces.length; i++) {
+            String str = constant_pool.getConstantString(interfaces[i], Constants.CONSTANT_Class);
+            interface_names[i] = Utility.compactClassName(str, false);
+        }
+    }
+
+
+    /**
+     * @return Attributes of the class.
+     */
+    public Attribute[] getAttributes() {
+        return attributes;
+    }
+    
+    public AnnotationEntry[] getAnnotationEntries() {
+        if (annotationsOutOfDate) { 
+            // Find attributes that contain annotation data
+            Attribute[] attrs = getAttributes();
+            List<AnnotationEntry> accumulatedAnnotations = new ArrayList<AnnotationEntry>();
+            for (int i = 0; i < attrs.length; i++) {
+                Attribute attribute = attrs[i];
+                if (attribute instanceof Annotations) {
+                    Annotations runtimeAnnotations = (Annotations)attribute;
+                    for(int j = 0; j < runtimeAnnotations.getAnnotationEntries().length; j++)
+                        accumulatedAnnotations.add(runtimeAnnotations.getAnnotationEntries()[j]);
+                }
+            }
+            annotations = accumulatedAnnotations.toArray(new AnnotationEntry[accumulatedAnnotations.size()]);
+            annotationsOutOfDate = false;
+        }
+        return annotations;
+    }
+
+    /**
+     * @return Class name.
+     */
+    public String getClassName() {
+        return class_name;
+    }
+
+
+    /**
+     * @return String representing class contents.
+     */
+    @Override
+    public String toString() {
+        String access = Utility.accessToString(access_flags, true);
+        access = access.equals("") ? "" : (access + " ");
+        StringBuffer buf = new StringBuffer(128);
+        buf.append(access).append(Utility.classOrInterface(access_flags)).append(" ").append(
+                class_name).append(" extends ").append(
+                Utility.compactClassName(superclass_name, false)).append('\n');
+        int size = interfaces.length;
+        if (size > 0) {
+            buf.append("implements\t\t");
+            for (int i = 0; i < size; i++) {
+                buf.append(interface_names[i]);
+                if (i < size - 1) {
+                    buf.append(", ");
+                }
+            }
+            buf.append('\n');
+        }
+        buf.append("filename\t\t").append(file_name).append('\n');
+        buf.append("compiled from\t\t").append(source_file_name).append('\n');
+        buf.append("compiler version\t").append(major).append(".").append(minor).append('\n');
+        buf.append("access flags\t\t").append(access_flags).append('\n');
+        buf.append("constant pool\t\t").append(constant_pool.getLength()).append(" entries\n");
+        buf.append("ACC_SUPER flag\t\t").append(isSuper()).append("\n");
+        if (attributes.length > 0) {
+            buf.append("\nAttribute(s):\n");
+            for (int i = 0; i < attributes.length; i++) {
+                buf.append(indent(attributes[i]));
+            }
+        }
+        AnnotationEntry[] annotations = getAnnotationEntries();
+        if (annotations!=null && annotations.length>0) {
+            buf.append("\nAnnotation(s):\n");
+            for (int i=0; i<annotations.length; i++) 
+                buf.append(indent(annotations[i]));
+        }
+        if (fields.length > 0) {
+            buf.append("\n").append(fields.length).append(" fields:\n");
+            for (int i = 0; i < fields.length; i++) {
+                buf.append("\t").append(fields[i]).append('\n');
+            }
+        }
+        if (methods.length > 0) {
+            buf.append("\n").append(methods.length).append(" methods:\n");
+            for (int i = 0; i < methods.length; i++) {
+                buf.append("\t").append(methods[i]).append('\n');
+            }
+        }
+        return buf.toString();
+    }
+
+
+    private static final String indent( Object obj ) {
+        StringTokenizer tok = new StringTokenizer(obj.toString(), "\n");
+        StringBuffer buf = new StringBuffer();
+        while (tok.hasMoreTokens()) {
+            buf.append("\t").append(tok.nextToken()).append("\n");
+        }
+        return buf.toString();
+    }
+
+
+    public final boolean isSuper() {
+        return (access_flags & Constants.ACC_SUPER) != 0;
+    }
+
+    /**
+     * Return value as defined by given BCELComparator strategy.
+     * By default two JavaClass objects are said to be equal when
+     * their class names are equal.
+     * 
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals( Object obj ) {
+        return _cmp.equals(this, obj);
+    }
+
+
+    /**
+     * Return the natural ordering of two JavaClasses.
+     * This ordering is based on the class name
+     */
+    @Override
+    public int compareTo(JavaClass obj) {
+        return getClassName().compareTo(obj.getClassName());
+    }
+
+
+    /**
+     * Return value as defined by given BCELComparator strategy.
+     * By default return the hashcode of the class name.
+     * 
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        return _cmp.hashCode(this);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/LineNumber.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/LineNumber.java
new file mode 100644
index 0000000..04f9ce3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/LineNumber.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.bcel.classfile;
+
+import java.io.DataInput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.Serializable;
+
+/**
+ * This class represents a (PC offset, line number) pair, i.e., a line number in
+ * the source that corresponds to a relative address in the byte code. This
+ * is used for debugging purposes.
+ *
+ * @version $Id: LineNumber.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     LineNumberTable
+ */
+public final class LineNumber implements Cloneable, Serializable {
+
+    private static final long serialVersionUID = 3393830630264494355L;
+    private int start_pc; // Program Counter (PC) corresponds to line
+    private int line_number; // number in source file
+
+
+    /**
+     * Construct object from file stream.
+     * @param file Input stream
+     * @throws IOException
+     */
+    LineNumber(DataInput file) throws IOException {
+        this(file.readUnsignedShort(), file.readUnsignedShort());
+    }
+
+
+    /**
+     * @param start_pc Program Counter (PC) corresponds to
+     * @param line_number line number in source file
+     */
+    public LineNumber(int start_pc, int line_number) {
+        this.start_pc = start_pc;
+        this.line_number = line_number;
+    }
+
+
+    /**
+     * Dump line number/pc pair to file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    public final void dump( DataOutputStream file ) throws IOException {
+        file.writeShort(start_pc);
+        file.writeShort(line_number);
+    }
+
+
+    /**
+     * @return String representation
+     */
+    @Override
+    public final String toString() {
+        return "LineNumber(" + start_pc + ", " + line_number + ")";
+    }
+
+
+    /**
+     * @return deep copy of this object
+     */
+    public LineNumber copy() {
+        try {
+            return (LineNumber) clone();
+        } catch (CloneNotSupportedException e) {
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/LineNumberTable.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/LineNumberTable.java
new file mode 100644
index 0000000..0d4f823
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/LineNumberTable.java
@@ -0,0 +1,139 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.tomcat.util.bcel.classfile;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * This class represents a table of line numbers for debugging
+ * purposes. This attribute is used by the <em>Code</em> attribute. It
+ * contains pairs of PCs and line numbers.
+ *
+ * @version $Id: LineNumberTable.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     Code
+ * @see LineNumber
+ */
+public final class LineNumberTable extends Attribute {
+
+    private static final long serialVersionUID = 6585122636118666124L;
+    private int line_number_table_length;
+    private LineNumber[] line_number_table; // Table of line/numbers pairs
+
+
+    /*
+     * @param name_index Index of name
+     * @param length Content length in bytes
+     * @param line_number_table Table of line/numbers pairs
+     * @param constant_pool Array of constants
+     */
+    public LineNumberTable(int name_index, int length, LineNumber[] line_number_table,
+            ConstantPool constant_pool) {
+        super(Constants.ATTR_LINE_NUMBER_TABLE, name_index, length, constant_pool);
+        setLineNumberTable(line_number_table);
+    }
+
+
+    /**
+     * Construct object from file stream.
+     * @param name_index Index of name
+     * @param length Content length in bytes
+     * @param file Input stream
+     * @param constant_pool Array of constants
+     * @throws IOException
+     */
+    LineNumberTable(int name_index, int length, DataInputStream file, ConstantPool constant_pool)
+            throws IOException {
+        this(name_index, length, (LineNumber[]) null, constant_pool);
+        line_number_table_length = (file.readUnsignedShort());
+        line_number_table = new LineNumber[line_number_table_length];
+        for (int i = 0; i < line_number_table_length; i++) {
+            line_number_table[i] = new LineNumber(file);
+        }
+    }
+
+
+    /**
+     * Dump line number table attribute to file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    @Override
+    public final void dump( DataOutputStream file ) throws IOException {
+        super.dump(file);
+        file.writeShort(line_number_table_length);
+        for (int i = 0; i < line_number_table_length; i++) {
+            line_number_table[i].dump(file);
+        }
+    }
+
+
+    /**
+     * @param line_number_table the line number entries for this table
+     */
+    public final void setLineNumberTable( LineNumber[] line_number_table ) {
+        this.line_number_table = line_number_table;
+        line_number_table_length = (line_number_table == null) ? 0 : line_number_table.length;
+    }
+
+
+    /**
+     * @return String representation.
+     */
+    @Override
+    public final String toString() {
+        StringBuffer buf = new StringBuffer();
+        StringBuffer line = new StringBuffer();
+        String newLine = System.getProperty("line.separator", "\n");
+        for (int i = 0; i < line_number_table_length; i++) {
+            line.append(line_number_table[i].toString());
+            if (i < line_number_table_length - 1) {
+                line.append(", ");
+            }
+            if (line.length() > 72) {
+                line.append(newLine);
+                buf.append(line.toString());
+                line.setLength(0);
+            }
+        }
+        buf.append(line);
+        return buf.toString();
+    }
+
+
+    /**
+     * @return deep copy of this attribute
+     */
+    @Override
+    public Attribute copy( ConstantPool _constant_pool ) {
+        LineNumberTable c = (LineNumberTable) clone();
+        c.line_number_table = new LineNumber[line_number_table_length];
+        for (int i = 0; i < line_number_table_length; i++) {
+            c.line_number_table[i] = line_number_table[i].copy();
+        }
+        c.constant_pool = _constant_pool;
+        return c;
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/LocalVariable.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/LocalVariable.java
new file mode 100644
index 0000000..6c25dc7
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/LocalVariable.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.tomcat.util.bcel.classfile;
+
+import java.io.DataInput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.Serializable;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * This class represents a local variable within a method. It contains its
+ * scope, name, signature and index on the method's frame.
+ *
+ * @version $Id: LocalVariable.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     LocalVariableTable
+ */
+public final class LocalVariable implements Constants, Cloneable, Serializable {
+
+    private static final long serialVersionUID = -914189896372081589L;
+    private int start_pc; // Range in which the variable is valid
+    private int length;
+    private int name_index; // Index in constant pool of variable name
+    private int signature_index; // Index of variable signature
+    private int index; /* Variable is `index'th local variable on
+     * this method's frame.
+     */
+    private ConstantPool constant_pool;
+
+
+    /**
+     * Construct object from file stream.
+     * @param file Input stream
+     * @throws IOException
+     */
+    LocalVariable(DataInput file, ConstantPool constant_pool) throws IOException {
+        this(file.readUnsignedShort(), file.readUnsignedShort(), file.readUnsignedShort(), file
+                .readUnsignedShort(), file.readUnsignedShort(), constant_pool);
+    }
+
+
+    /**
+     * @param start_pc Range in which the variable
+     * @param length ... is valid
+     * @param name_index Index in constant pool of variable name
+     * @param signature_index Index of variable's signature
+     * @param index Variable is `index'th local variable on the method's frame
+     * @param constant_pool Array of constants
+     */
+    public LocalVariable(int start_pc, int length, int name_index, int signature_index, int index,
+            ConstantPool constant_pool) {
+        this.start_pc = start_pc;
+        this.length = length;
+        this.name_index = name_index;
+        this.signature_index = signature_index;
+        this.index = index;
+        this.constant_pool = constant_pool;
+    }
+
+
+    /**
+     * Dump local variable to file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    public final void dump( DataOutputStream file ) throws IOException {
+        file.writeShort(start_pc);
+        file.writeShort(length);
+        file.writeShort(name_index);
+        file.writeShort(signature_index);
+        file.writeShort(index);
+    }
+
+
+    /**
+     * @return Variable name.
+     */
+    public final String getName() {
+        ConstantUtf8 c;
+        c = (ConstantUtf8) constant_pool.getConstant(name_index, CONSTANT_Utf8);
+        return c.getBytes();
+    }
+
+
+    /**
+     * @return Signature.
+     */
+    public final String getSignature() {
+        ConstantUtf8 c;
+        c = (ConstantUtf8) constant_pool.getConstant(signature_index, CONSTANT_Utf8);
+        return c.getBytes();
+    }
+
+
+    /**
+     * @return index of register where variable is stored
+     */
+    public final int getIndex() {
+        return index;
+    }
+
+
+    /**
+     * @return string representation.
+     */
+    @Override
+    public final String toString() {
+        String name = getName(), signature = Utility.signatureToString(getSignature());
+        return "LocalVariable(start_pc = " + start_pc + ", length = " + length + ", index = "
+                + index + ":" + signature + " " + name + ")";
+    }
+
+
+    /**
+     * @return deep copy of this object
+     */
+    public LocalVariable copy() {
+        try {
+            return (LocalVariable) clone();
+        } catch (CloneNotSupportedException e) {
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/LocalVariableTable.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/LocalVariableTable.java
new file mode 100644
index 0000000..a2437b8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/LocalVariableTable.java
@@ -0,0 +1,147 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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.classfile;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * This class represents colection of local variables in a
+ * method. This attribute is contained in the <em>Code</em> attribute.
+ *
+ * @version $Id: LocalVariableTable.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     Code
+ * @see LocalVariable
+ */
+public class LocalVariableTable extends Attribute {
+
+    private static final long serialVersionUID = -3904314258294133920L;
+    private int local_variable_table_length; // Table of local
+    private LocalVariable[] local_variable_table; // variables
+
+
+    /**
+     * @param name_index Index in constant pool to `LocalVariableTable'
+     * @param length Content length in bytes
+     * @param local_variable_table Table of local variables
+     * @param constant_pool Array of constants
+     */
+    public LocalVariableTable(int name_index, int length, LocalVariable[] local_variable_table,
+            ConstantPool constant_pool) {
+        super(Constants.ATTR_LOCAL_VARIABLE_TABLE, name_index, length, constant_pool);
+        setLocalVariableTable(local_variable_table);
+    }
+
+
+    /**
+     * Construct object from file stream.
+     * @param name_index Index in constant pool
+     * @param length Content length in bytes
+     * @param file Input stream
+     * @param constant_pool Array of constants
+     * @throws IOException
+     */
+    LocalVariableTable(int name_index, int length, DataInputStream file, ConstantPool constant_pool)
+            throws IOException {
+        this(name_index, length, (LocalVariable[]) null, constant_pool);
+        local_variable_table_length = (file.readUnsignedShort());
+        local_variable_table = new LocalVariable[local_variable_table_length];
+        for (int i = 0; i < local_variable_table_length; i++) {
+            local_variable_table[i] = new LocalVariable(file, constant_pool);
+        }
+    }
+
+
+    /**
+     * Dump local variable table attribute to file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    @Override
+    public final void dump( DataOutputStream file ) throws IOException {
+        super.dump(file);
+        file.writeShort(local_variable_table_length);
+        for (int i = 0; i < local_variable_table_length; i++) {
+            local_variable_table[i].dump(file);
+        }
+    }
+
+
+    /** 
+     * 
+     * @param index the variable slot
+     * 
+     * @return the first LocalVariable that matches the slot or null if not found
+     * 
+     * @deprecated since 5.2 because multiple variables can share the
+     *             same slot, use getLocalVariable(int index, int pc) instead.
+     */
+    public final LocalVariable getLocalVariable( int index ) {
+        for (int i = 0; i < local_variable_table_length; i++) {
+            if (local_variable_table[i].getIndex() == index) {
+                return local_variable_table[i];
+            }
+        }
+        return null;
+    }
+
+    public final void setLocalVariableTable( LocalVariable[] local_variable_table ) {
+        this.local_variable_table = local_variable_table;
+        local_variable_table_length = (local_variable_table == null)
+                ? 0
+                : local_variable_table.length;
+    }
+
+
+    /**
+     * @return String representation.
+     */
+    @Override
+    public final String toString() {
+        StringBuffer buf = new StringBuffer();
+        for (int i = 0; i < local_variable_table_length; i++) {
+            buf.append(local_variable_table[i].toString());
+            if (i < local_variable_table_length - 1) {
+                buf.append('\n');
+            }
+        }
+        return buf.toString();
+    }
+
+
+    /**
+     * @return deep copy of this attribute
+     */
+    @Override
+    public Attribute copy( ConstantPool _constant_pool ) {
+        LocalVariableTable c = (LocalVariableTable) clone();
+        c.local_variable_table = new LocalVariable[local_variable_table_length];
+        for (int i = 0; i < local_variable_table_length; i++) {
+            c.local_variable_table[i] = local_variable_table[i].copy();
+        }
+        c.constant_pool = _constant_pool;
+        return c;
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/LocalVariableTypeTable.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/LocalVariableTypeTable.java
new file mode 100644
index 0000000..445be64
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/LocalVariableTypeTable.java
@@ -0,0 +1,121 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.tomcat.util.bcel.classfile;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+// The new table is used when generic types are about...
+
+//LocalVariableTable_attribute {
+//     u2 attribute_name_index;
+//     u4 attribute_length;
+//     u2 local_variable_table_length;
+//     {  u2 start_pc;
+//        u2 length;
+//        u2 name_index;
+//        u2 descriptor_index;
+//        u2 index;
+//     } local_variable_table[local_variable_table_length];
+//   }
+
+//LocalVariableTypeTable_attribute {
+//    u2 attribute_name_index;
+//    u4 attribute_length;
+//    u2 local_variable_type_table_length;
+//    { 
+//      u2 start_pc;
+//      u2 length;
+//      u2 name_index;
+//      u2 signature_index;
+//      u2 index;
+//    } local_variable_type_table[local_variable_type_table_length];
+//  }
+// J5TODO: Needs some testing !
+public class LocalVariableTypeTable extends Attribute {
+  private static final long serialVersionUID = -5466082154076451597L;
+private int             local_variable_type_table_length; // Table of local
+  private LocalVariable[] local_variable_type_table;        // variables
+
+  public LocalVariableTypeTable(int name_index, int length,
+                                LocalVariable[] local_variable_table,
+                                ConstantPool    constant_pool)
+  {
+    super(Constants.ATTR_LOCAL_VARIABLE_TYPE_TABLE, name_index, length, constant_pool);
+    setLocalVariableTable(local_variable_table);
+  }    
+
+  LocalVariableTypeTable(int nameIdx, int len, DataInputStream dis,ConstantPool cpool) throws IOException {
+    this(nameIdx, len, (LocalVariable[])null, cpool);
+
+    local_variable_type_table_length = (dis.readUnsignedShort());
+    local_variable_type_table = new LocalVariable[local_variable_type_table_length];
+
+    for(int i=0; i < local_variable_type_table_length; i++)
+      local_variable_type_table[i] = new LocalVariable(dis, cpool);
+  }
+
+  @Override
+  public final void dump(DataOutputStream file) throws IOException
+  {
+    super.dump(file);
+    file.writeShort(local_variable_type_table_length);
+    for(int i=0; i < local_variable_type_table_length; i++)
+      local_variable_type_table[i].dump(file);
+  }
+
+  public final void setLocalVariableTable(LocalVariable[] local_variable_table)
+  {
+    this.local_variable_type_table = local_variable_table;
+    local_variable_type_table_length = (local_variable_table == null)? 0 :
+      local_variable_table.length;
+  }
+
+  /**
+   * @return String representation.
+   */ 
+  @Override
+  public final String toString() {
+    StringBuffer buf = new StringBuffer("");
+
+    for(int i=0; i < local_variable_type_table_length; i++) {
+      buf.append(local_variable_type_table[i].toString());
+
+      if(i < local_variable_type_table_length - 1) buf.append('\n');
+    }
+
+    return buf.toString();
+  }
+
+  /**
+   * @return deep copy of this attribute
+   */
+  @Override
+  public Attribute copy(ConstantPool constant_pool) {
+    LocalVariableTypeTable c = (LocalVariableTypeTable)clone();
+
+    c.local_variable_type_table = new LocalVariable[local_variable_type_table_length];
+    for(int i=0; i < local_variable_type_table_length; i++)
+      c.local_variable_type_table[i] = local_variable_type_table[i].copy();
+
+    c.constant_pool = constant_pool;
+    return c;
+  }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Method.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Method.java
new file mode 100644
index 0000000..ebeeb1b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Method.java
@@ -0,0 +1,175 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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.classfile;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+import org.apache.tomcat.util.bcel.util.BCELComparator;
+
+/**
+ * This class represents the method info structure, i.e., the representation 
+ * for a method in the class. See JVM specification for details.
+ * A method has access flags, a name, a signature and a number of attributes.
+ *
+ * @version $Id: Method.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ */
+public final class Method extends FieldOrMethod {
+
+    private static final long serialVersionUID = -7447828891136739513L;
+    private static BCELComparator _cmp = new BCELComparator() {
+
+        @Override
+        public boolean equals( Object o1, Object o2 ) {
+            Method THIS = (Method) o1;
+            Method THAT = (Method) o2;
+            return THIS.getName().equals(THAT.getName())
+                    && THIS.getSignature().equals(THAT.getSignature());
+        }
+
+
+        @Override
+        public int hashCode( Object o ) {
+            Method THIS = (Method) o;
+            return THIS.getSignature().hashCode() ^ THIS.getName().hashCode();
+        }
+    };
+
+
+    /**
+     * Empty constructor, all attributes have to be defined via `setXXX'
+     * methods. Use at your own risk.
+     */
+    public Method() {
+    }
+
+
+    /**
+     * Construct object from file stream.
+     * @param file Input stream
+     * @throws IOException
+     * @throws ClassFormatException
+     */
+    Method(DataInputStream file, ConstantPool constant_pool) throws IOException,
+            ClassFormatException {
+        super(file, constant_pool);
+    }
+
+
+    /**
+     * @return Code attribute of method, if any
+     */
+    public final Code getCode() {
+        for (int i = 0; i < attributes_count; i++) {
+            if (attributes[i] instanceof Code) {
+                return (Code) attributes[i];
+            }
+        }
+        return null;
+    }
+
+
+    /**
+     * @return ExceptionTable attribute of method, if any, i.e., list all
+     * exceptions the method may throw not exception handlers!
+     */
+    public final ExceptionTable getExceptionTable() {
+        for (int i = 0; i < attributes_count; i++) {
+            if (attributes[i] instanceof ExceptionTable) {
+                return (ExceptionTable) attributes[i];
+            }
+        }
+        return null;
+    }
+
+
+    /** @return LocalVariableTable of code attribute if any, i.e. the call is forwarded
+     * to the Code atribute.
+     */
+    public final LocalVariableTable getLocalVariableTable() {
+        Code code = getCode();
+        if (code == null) {
+            return null;
+        }
+        return code.getLocalVariableTable();
+    }
+
+
+    /**
+     * Return string representation close to declaration format,
+     * `public static void main(String[] args) throws IOException', e.g.
+     *
+     * @return String representation of the method.
+     */
+    @Override
+    public final String toString() {
+        ConstantUtf8 c;
+        String name, signature, access; // Short cuts to constant pool
+        StringBuffer buf;
+        access = Utility.accessToString(access_flags);
+        // Get name and signature from constant pool
+        c = (ConstantUtf8) constant_pool.getConstant(signature_index, Constants.CONSTANT_Utf8);
+        signature = c.getBytes();
+        c = (ConstantUtf8) constant_pool.getConstant(name_index, Constants.CONSTANT_Utf8);
+        name = c.getBytes();
+        signature = Utility.methodSignatureToString(signature, name, access, true,
+                getLocalVariableTable());
+        buf = new StringBuffer(signature);
+        for (int i = 0; i < attributes_count; i++) {
+            Attribute a = attributes[i];
+            if (!((a instanceof Code) || (a instanceof ExceptionTable))) {
+                buf.append(" [").append(a.toString()).append("]");
+            }
+        }
+        ExceptionTable e = getExceptionTable();
+        if (e != null) {
+            String str = e.toString();
+            if (!str.equals("")) {
+                buf.append("\n\t\tthrows ").append(str);
+            }
+        }
+        return buf.toString();
+    }
+
+
+    /**
+     * Return value as defined by given BCELComparator strategy.
+     * By default two method objects are said to be equal when
+     * their names and signatures are equal.
+     * 
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals( Object obj ) {
+        return _cmp.equals(this, obj);
+    }
+
+
+    /**
+     * Return value as defined by given BCELComparator strategy.
+     * By default return the hashcode of the method's name XOR signature.
+     * 
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        return _cmp.hashCode(this);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/PMGClass.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/PMGClass.java
new file mode 100644
index 0000000..bf2b473
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/PMGClass.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.tomcat.util.bcel.classfile;
+
+import java.io.DataInput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * This class is derived from <em>Attribute</em> and represents a reference
+ * to a PMG attribute.
+ *
+ * @version $Id: PMGClass.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     Attribute
+ */
+public final class PMGClass extends Attribute {
+
+    private static final long serialVersionUID = -1876065562391587509L;
+    private int pmg_class_index, pmg_index;
+
+
+    /**
+     * Construct object from file stream.
+     * @param name_index Index in constant pool to CONSTANT_Utf8
+     * @param length Content length in bytes
+     * @param file Input stream
+     * @param constant_pool Array of constants
+     * @throws IOException
+     */
+    PMGClass(int name_index, int length, DataInput file, ConstantPool constant_pool)
+            throws IOException {
+        this(name_index, length, file.readUnsignedShort(), file.readUnsignedShort(), constant_pool);
+    }
+
+
+    /**
+     * @param name_index Index in constant pool to CONSTANT_Utf8
+     * @param length Content length in bytes
+     * @param pmg_index index in constant pool for source file name
+     * @param pmg_class_index Index in constant pool to CONSTANT_Utf8
+     * @param constant_pool Array of constants
+     */
+    public PMGClass(int name_index, int length, int pmg_index, int pmg_class_index,
+            ConstantPool constant_pool) {
+        super(Constants.ATTR_PMG, name_index, length, constant_pool);
+        this.pmg_index = pmg_index;
+        this.pmg_class_index = pmg_class_index;
+    }
+
+
+    /**
+     * Dump source file attribute to file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    @Override
+    public final void dump( DataOutputStream file ) throws IOException {
+        super.dump(file);
+        file.writeShort(pmg_index);
+        file.writeShort(pmg_class_index);
+    }
+
+
+    /**
+     * @return PMG name.
+     */
+    public final String getPMGName() {
+        ConstantUtf8 c = (ConstantUtf8) constant_pool.getConstant(pmg_index,
+                Constants.CONSTANT_Utf8);
+        return c.getBytes();
+    }
+
+
+    /**
+     * @return PMG class name.
+     */
+    public final String getPMGClassName() {
+        ConstantUtf8 c = (ConstantUtf8) constant_pool.getConstant(pmg_class_index,
+                Constants.CONSTANT_Utf8);
+        return c.getBytes();
+    }
+
+
+    /**
+     * @return String representation
+     */
+    @Override
+    public final String toString() {
+        return "PMGClass(" + getPMGName() + ", " + getPMGClassName() + ")";
+    }
+
+
+    /**
+     * @return deep copy of this attribute
+     */
+    @Override
+    public Attribute copy( ConstantPool _constant_pool ) {
+        return (PMGClass) clone();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ParameterAnnotationEntry.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ParameterAnnotationEntry.java
new file mode 100644
index 0000000..2cf5e8a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ParameterAnnotationEntry.java
@@ -0,0 +1,51 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.tomcat.util.bcel.classfile;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * represents one parameter annotation in the parameter annotation table
+ * 
+ * @version $Id: ParameterAnnotationEntry
+ * @author  <A HREF="mailto:dbrosius@qis.net">D. Brosius</A>
+ * @since 5.3
+ */
+public class ParameterAnnotationEntry implements Constants {
+
+    private int annotation_table_length;
+    private AnnotationEntry[] annotation_table;
+
+
+    /**
+     * Construct object from file stream.
+     * @param file Input stream
+     * @throws IOException
+     */
+    ParameterAnnotationEntry(DataInputStream file, ConstantPool constant_pool) throws IOException {
+        annotation_table_length = (file.readUnsignedShort());
+        annotation_table = new AnnotationEntry[annotation_table_length];
+        for (int i = 0; i < annotation_table_length; i++) {
+            annotation_table[i] = AnnotationEntry.read(file, constant_pool);
+        }
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ParameterAnnotations.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ParameterAnnotations.java
new file mode 100644
index 0000000..2947bf4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/ParameterAnnotations.java
@@ -0,0 +1,81 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.tomcat.util.bcel.classfile;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+
+/**
+ * base class for parameter annotations
+ * 
+ * @version $Id: ParameterAnnotations
+ * @author  <A HREF="mailto:dbrosius@qis.net">D. Brosius</A>
+ * @since 5.3
+ */
+public abstract class ParameterAnnotations extends Attribute {
+
+    private static final long serialVersionUID = -8831779739803248091L;
+    private int num_parameters;
+    private ParameterAnnotationEntry[] parameter_annotation_table; // Table of parameter annotations
+
+
+    /**
+     * @param parameter_annotation_type the subclass type of the parameter annotation
+     * @param name_index Index pointing to the name <em>Code</em>
+     * @param length Content length in bytes
+     * @param file Input stream
+     * @param constant_pool Array of constants
+     */
+    ParameterAnnotations(byte parameter_annotation_type, int name_index, int length,
+            DataInputStream file, ConstantPool constant_pool) throws IOException {
+        this(parameter_annotation_type, name_index, length, (ParameterAnnotationEntry[]) null,
+                constant_pool);
+        num_parameters = (file.readUnsignedByte());
+        parameter_annotation_table = new ParameterAnnotationEntry[num_parameters];
+        for (int i = 0; i < num_parameters; i++) {
+            parameter_annotation_table[i] = new ParameterAnnotationEntry(file, constant_pool);
+        }
+    }
+
+
+    /**
+     * @param parameter_annotation_type the subclass type of the parameter annotation
+     * @param name_index Index pointing to the name <em>Code</em>
+     * @param length Content length in bytes
+     * @param parameter_annotation_table the actual parameter annotations
+     * @param constant_pool Array of constants
+     */
+    public ParameterAnnotations(byte parameter_annotation_type, int name_index, int length,
+            ParameterAnnotationEntry[] parameter_annotation_table, ConstantPool constant_pool) {
+        super(parameter_annotation_type, name_index, length, constant_pool);
+        setParameterAnnotationTable(parameter_annotation_table);
+    }
+
+
+    /**
+     * @param parameter_annotation_table the entries to set in this parameter annotation
+     */
+    public final void setParameterAnnotationTable(
+            ParameterAnnotationEntry[] parameter_annotation_table ) {
+        this.parameter_annotation_table = parameter_annotation_table;
+        num_parameters = (parameter_annotation_table == null)
+                ? 0
+                : parameter_annotation_table.length;
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/RuntimeInvisibleAnnotations.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/RuntimeInvisibleAnnotations.java
new file mode 100644
index 0000000..e6959e9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/RuntimeInvisibleAnnotations.java
@@ -0,0 +1,72 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.tomcat.util.bcel.classfile;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * represents an annotation that is represented in the class file but is not
+ * provided to the JVM.
+ * 
+ * @version $Id: RuntimeInvisibleAnnotations
+ * @author <A HREF="mailto:dbrosius@qis.net">D. Brosius</A>
+ * @since 5.3
+ */
+public class RuntimeInvisibleAnnotations extends Annotations
+{
+    private static final long serialVersionUID = -7962627688723310248L;
+
+    /**
+     * @param name_index
+     *            Index pointing to the name <em>Code</em>
+     * @param length
+     *            Content length in bytes
+     * @param file
+     *            Input stream
+     * @param constant_pool
+     *            Array of constants
+     */
+    RuntimeInvisibleAnnotations(int name_index, int length,
+                                DataInputStream file, ConstantPool constant_pool)
+                                throws IOException
+    {
+        super(Constants.ATTR_RUNTIMEIN_VISIBLE_ANNOTATIONS, name_index, length,
+                file, constant_pool);
+    }
+
+    /**
+     * @return deep copy of this attribute
+     */
+    @Override
+    public Attribute copy(ConstantPool constant_pool)
+    {
+        Annotations c = (Annotations) clone();
+        return c;
+    }
+
+    @Override
+    public final void dump(DataOutputStream dos) throws IOException
+    {
+        super.dump(dos);
+        writeAnnotations(dos);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/RuntimeInvisibleParameterAnnotations.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/RuntimeInvisibleParameterAnnotations.java
new file mode 100644
index 0000000..547df6b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/RuntimeInvisibleParameterAnnotations.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.tomcat.util.bcel.classfile;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * represents a parameter annotation that is represented in the class file
+ * but is not provided to the JVM.
+ * 
+ * @version $Id: RuntimeInvisibleParameterAnnotations
+ * @author  <A HREF="mailto:dbrosius@qis.net">D. Brosius</A>
+ * @since 5.3
+ */
+public class RuntimeInvisibleParameterAnnotations extends ParameterAnnotations {
+
+    private static final long serialVersionUID = -6819370369102352536L;
+
+
+    /**
+     * @param name_index Index pointing to the name <em>Code</em>
+     * @param length Content length in bytes
+     * @param file Input stream
+     * @param constant_pool Array of constants
+     */
+    RuntimeInvisibleParameterAnnotations(int name_index, int length, DataInputStream file,
+            ConstantPool constant_pool) throws IOException {
+        super(Constants.ATTR_RUNTIMEIN_VISIBLE_PARAMETER_ANNOTATIONS, name_index, length, file,
+                constant_pool);
+    }
+
+
+    /**
+     * @return deep copy of this attribute
+     */
+    @Override
+    public Attribute copy( ConstantPool constant_pool ) {
+        Annotations c = (Annotations) clone();
+        return c;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/RuntimeVisibleAnnotations.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/RuntimeVisibleAnnotations.java
new file mode 100644
index 0000000..245cc9f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/RuntimeVisibleAnnotations.java
@@ -0,0 +1,72 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.tomcat.util.bcel.classfile;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * represents an annotation that is represented in the class file and is
+ * provided to the JVM.
+ * 
+ * @version $Id: RuntimeVisibleAnnotations
+ * @author <A HREF="mailto:dbrosius@qis.net">D. Brosius</A>
+ * @since 5.3
+ */
+public class RuntimeVisibleAnnotations extends Annotations
+{
+    private static final long serialVersionUID = 2912284875689024413L;
+
+    /**
+     * @param name_index
+     *            Index pointing to the name <em>Code</em>
+     * @param length
+     *            Content length in bytes
+     * @param file
+     *            Input stream
+     * @param constant_pool
+     *            Array of constants
+     */
+    public RuntimeVisibleAnnotations(int name_index, int length,
+            DataInputStream file, ConstantPool constant_pool)
+            throws IOException
+    {
+        super(Constants.ATTR_RUNTIME_VISIBLE_ANNOTATIONS, name_index, length,
+                file, constant_pool);
+    }
+
+    /**
+     * @return deep copy of this attribute
+     */
+    @Override
+    public Attribute copy(ConstantPool constant_pool)
+    {
+        Annotations c = (Annotations) clone();
+        return c;
+    }
+
+    @Override
+    public final void dump(DataOutputStream dos) throws IOException
+    {
+        super.dump(dos);
+        writeAnnotations(dos);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/RuntimeVisibleParameterAnnotations.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/RuntimeVisibleParameterAnnotations.java
new file mode 100644
index 0000000..0fc4640
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/RuntimeVisibleParameterAnnotations.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.tomcat.util.bcel.classfile;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * represents a parameter annotation that is represented in the class file
+ * and is provided to the JVM.
+ * 
+ * @version $Id: RuntimeVisibleParameterAnnotations
+ * @author  <A HREF="mailto:dbrosius@qis.net">D. Brosius</A>
+ * @since 5.3
+ */
+public class RuntimeVisibleParameterAnnotations extends ParameterAnnotations {
+
+    private static final long serialVersionUID = 7633756460868573992L;
+
+
+    /**
+     * @param name_index Index pointing to the name <em>Code</em>
+     * @param length Content length in bytes
+     * @param file Input stream
+     * @param constant_pool Array of constants
+     */
+    RuntimeVisibleParameterAnnotations(int name_index, int length, DataInputStream file,
+            ConstantPool constant_pool) throws IOException {
+        super(Constants.ATTR_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, name_index, length, file,
+                constant_pool);
+    }
+
+
+    /**
+     * @return deep copy of this attribute
+     */
+    @Override
+    public Attribute copy( ConstantPool constant_pool ) {
+        Annotations c = (Annotations) clone();
+        return c;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Signature.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Signature.java
new file mode 100644
index 0000000..e564557
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Signature.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.bcel.classfile;
+
+import java.io.DataInput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * This class is derived from <em>Attribute</em> and represents a reference
+ * to a GJ attribute.
+ *
+ * @version $Id: Signature.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     Attribute
+ */
+public final class Signature extends Attribute {
+
+    private static final long serialVersionUID = 7493781777025829964L;
+    private int signature_index;
+
+
+    /**
+     * Construct object from file stream.
+     * @param name_index Index in constant pool to CONSTANT_Utf8
+     * @param length Content length in bytes
+     * @param file Input stream
+     * @param constant_pool Array of constants
+     * @throws IOException
+     */
+    Signature(int name_index, int length, DataInput file, ConstantPool constant_pool)
+            throws IOException {
+        this(name_index, length, file.readUnsignedShort(), constant_pool);
+    }
+
+
+    /**
+     * @param name_index Index in constant pool to CONSTANT_Utf8
+     * @param length Content length in bytes
+     * @param signature_index Index in constant pool to CONSTANT_Utf8
+     * @param constant_pool Array of constants
+     */
+    public Signature(int name_index, int length, int signature_index, ConstantPool constant_pool) {
+        super(Constants.ATTR_SIGNATURE, name_index, length, constant_pool);
+        this.signature_index = signature_index;
+    }
+
+
+    /**
+     * Dump source file attribute to file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    @Override
+    public final void dump( DataOutputStream file ) throws IOException {
+        super.dump(file);
+        file.writeShort(signature_index);
+    }
+
+
+    /**
+     * @return GJ signature.
+     */
+    public final String getSignature() {
+        ConstantUtf8 c = (ConstantUtf8) constant_pool.getConstant(signature_index,
+                Constants.CONSTANT_Utf8);
+        return c.getBytes();
+    }
+
+    /**
+     * @return String representation
+     */
+    @Override
+    public final String toString() {
+        String s = getSignature();
+        return "Signature(" + s + ")";
+    }
+
+
+    /**
+     * @return deep copy of this attribute
+     */
+    @Override
+    public Attribute copy( ConstantPool _constant_pool ) {
+        return (Signature) clone();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/SimpleElementValue.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/SimpleElementValue.java
new file mode 100644
index 0000000..d866193
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/SimpleElementValue.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.tomcat.util.bcel.classfile;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+public class SimpleElementValue extends ElementValue
+{
+    private int index;
+
+    public SimpleElementValue(int type, int index, ConstantPool cpool)
+    {
+        super(type, cpool);
+        this.index = index;
+    }
+
+    /**
+     * @return Value entry index in the cpool
+     */
+    public int getIndex()
+    {
+        return index;
+    }
+    
+
+    @Override
+    public String toString()
+    {
+        return stringifyValue();
+    }
+
+    // Whatever kind of value it is, return it as a string
+    @Override
+    public String stringifyValue()
+    {
+        switch (type)
+        {
+        case PRIMITIVE_INT:
+            ConstantInteger c = (ConstantInteger) cpool.getConstant(getIndex(),
+                    Constants.CONSTANT_Integer);
+            return Integer.toString(c.getBytes());
+        case PRIMITIVE_LONG:
+            ConstantLong j = (ConstantLong) cpool.getConstant(getIndex(),
+                    Constants.CONSTANT_Long);
+            return Long.toString(j.getBytes());
+        case PRIMITIVE_DOUBLE:
+            ConstantDouble d = (ConstantDouble) cpool.getConstant(getIndex(),
+                    Constants.CONSTANT_Double);
+            return Double.toString(d.getBytes());
+        case PRIMITIVE_FLOAT:
+            ConstantFloat f = (ConstantFloat) cpool.getConstant(getIndex(),
+                    Constants.CONSTANT_Float);
+            return Float.toString(f.getBytes());
+        case PRIMITIVE_SHORT:
+            ConstantInteger s = (ConstantInteger) cpool.getConstant(getIndex(),
+                    Constants.CONSTANT_Integer);
+            return Integer.toString(s.getBytes());
+        case PRIMITIVE_BYTE:
+            ConstantInteger b = (ConstantInteger) cpool.getConstant(getIndex(),
+                    Constants.CONSTANT_Integer);
+            return Integer.toString(b.getBytes());
+        case PRIMITIVE_CHAR:
+            ConstantInteger ch = (ConstantInteger) cpool.getConstant(
+                    getIndex(), Constants.CONSTANT_Integer);
+            return String.valueOf((char)ch.getBytes());
+        case PRIMITIVE_BOOLEAN:
+            ConstantInteger bo = (ConstantInteger) cpool.getConstant(
+                    getIndex(), Constants.CONSTANT_Integer);
+            if (bo.getBytes() == 0) {
+                return "false";
+            }
+            return "true";
+        case STRING:
+            ConstantUtf8 cu8 = (ConstantUtf8) cpool.getConstant(getIndex(),
+                    Constants.CONSTANT_Utf8);
+            return cu8.getBytes();
+        default:
+            throw new RuntimeException(
+                    "SimpleElementValue class does not know how to stringify type "
+                            + type);
+        }
+    }
+
+    @Override
+    public void dump(DataOutputStream dos) throws IOException
+    {
+        dos.writeByte(type); // u1 kind of value
+        switch (type)
+        {
+        case PRIMITIVE_INT:
+        case PRIMITIVE_BYTE:
+        case PRIMITIVE_CHAR:
+        case PRIMITIVE_FLOAT:
+        case PRIMITIVE_LONG:
+        case PRIMITIVE_BOOLEAN:
+        case PRIMITIVE_SHORT:
+        case PRIMITIVE_DOUBLE:
+        case STRING:
+            dos.writeShort(getIndex());
+            break;
+        default:
+            throw new RuntimeException(
+                    "SimpleElementValue doesnt know how to write out type "
+                            + type);
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/SourceFile.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/SourceFile.java
new file mode 100644
index 0000000..c8f6530
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/SourceFile.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.tomcat.util.bcel.classfile;
+
+import java.io.DataInput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * This class is derived from <em>Attribute</em> and represents a reference
+ * to the source file of this class.  At most one SourceFile attribute
+ * should appear per classfile.  The intention of this class is that it is
+ * instantiated from the <em>Attribute.readAttribute()</em> method.
+ *
+ * @version $Id: SourceFile.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     Attribute
+ */
+public final class SourceFile extends Attribute {
+
+    private static final long serialVersionUID = 332346699609443704L;
+    private int sourcefile_index;
+
+
+    /**
+     * Construct object from file stream.
+     * @param name_index Index in constant pool to CONSTANT_Utf8
+     * @param length Content length in bytes
+     * @param file Input stream
+     * @param constant_pool Array of constants
+     * @throws IOException
+     */
+    SourceFile(int name_index, int length, DataInput file, ConstantPool constant_pool)
+            throws IOException {
+        this(name_index, length, file.readUnsignedShort(), constant_pool);
+    }
+
+
+    /**
+     * @param name_index Index in constant pool to CONSTANT_Utf8, which
+     * should represent the string "SourceFile".
+     * @param length Content length in bytes, the value should be 2.
+     * @param constant_pool The constant pool that this attribute is
+     * associated with.
+     * @param sourcefile_index Index in constant pool to CONSTANT_Utf8.  This
+     * string will be interpreted as the name of the file from which this
+     * class was compiled.  It will not be interpreted as indicating the name
+     * of the directory contqining the file or an absolute path; this
+     * information has to be supplied the consumer of this attribute - in
+     * many cases, the JVM.
+     */
+    public SourceFile(int name_index, int length, int sourcefile_index, ConstantPool constant_pool) {
+        super(Constants.ATTR_SOURCE_FILE, name_index, length, constant_pool);
+        this.sourcefile_index = sourcefile_index;
+    }
+
+
+    /**
+     * Dump source file attribute to file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    @Override
+    public final void dump( DataOutputStream file ) throws IOException {
+        super.dump(file);
+        file.writeShort(sourcefile_index);
+    }
+
+
+    /**
+     * @return Source file name.
+     */
+    public final String getSourceFileName() {
+        ConstantUtf8 c = (ConstantUtf8) constant_pool.getConstant(sourcefile_index,
+                Constants.CONSTANT_Utf8);
+        return c.getBytes();
+    }
+
+
+    /**
+     * @return String representation
+     */
+    @Override
+    public final String toString() {
+        return "SourceFile(" + getSourceFileName() + ")";
+    }
+
+
+    /**
+     * @return deep copy of this attribute
+     */
+    @Override
+    public Attribute copy( ConstantPool _constant_pool ) {
+        return (SourceFile) clone();
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/StackMap.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/StackMap.java
new file mode 100644
index 0000000..dcea97c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/StackMap.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.tomcat.util.bcel.classfile;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * This class represents a stack map attribute used for
+ * preverification of Java classes for the <a
+ * href="http://java.sun.com/j2me/"> Java 2 Micro Edition</a>
+ * (J2ME). This attribute is used by the <a
+ * href="http://java.sun.com/products/cldc/">KVM</a> and contained
+ * within the Code attribute of a method. See CLDC specification
+ * &sect;5.3.1.2
+ *
+ * @version $Id: StackMap.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     Code
+ * @see     StackMapEntry
+ * @see     StackMapType
+ */
+public final class StackMap extends Attribute {
+
+    private static final long serialVersionUID = 264958819110329590L;
+    private int map_length;
+    private StackMapEntry[] map; // Table of stack map entries
+
+
+    /*
+     * @param name_index Index of name
+     * @param length Content length in bytes
+     * @param map Table of stack map entries
+     * @param constant_pool Array of constants
+     */
+    public StackMap(int name_index, int length, StackMapEntry[] map, ConstantPool constant_pool) {
+        super(Constants.ATTR_STACK_MAP, name_index, length, constant_pool);
+        setStackMap(map);
+    }
+
+
+    /**
+     * Construct object from file stream.
+     * @param name_index Index of name
+     * @param length Content length in bytes
+     * @param file Input stream
+     * @param constant_pool Array of constants
+     * @throws IOException
+     */
+    StackMap(int name_index, int length, DataInputStream file, ConstantPool constant_pool)
+            throws IOException {
+        this(name_index, length, (StackMapEntry[]) null, constant_pool);
+        map_length = file.readUnsignedShort();
+        map = new StackMapEntry[map_length];
+        for (int i = 0; i < map_length; i++) {
+            map[i] = new StackMapEntry(file, constant_pool);
+        }
+    }
+
+
+    /**
+     * Dump line number table attribute to file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    @Override
+    public final void dump( DataOutputStream file ) throws IOException {
+        super.dump(file);
+        file.writeShort(map_length);
+        for (int i = 0; i < map_length; i++) {
+            map[i].dump(file);
+        }
+    }
+
+
+    /**
+     * @param map Array of stack map entries
+     */
+    public final void setStackMap( StackMapEntry[] map ) {
+        this.map = map;
+        map_length = (map == null) ? 0 : map.length;
+    }
+
+
+    /**
+     * @return String representation.
+     */
+    @Override
+    public final String toString() {
+        StringBuffer buf = new StringBuffer("StackMap(");
+        for (int i = 0; i < map_length; i++) {
+            buf.append(map[i].toString());
+            if (i < map_length - 1) {
+                buf.append(", ");
+            }
+        }
+        buf.append(')');
+        return buf.toString();
+    }
+
+
+    /**
+     * @return deep copy of this attribute
+     */
+    @Override
+    public Attribute copy( ConstantPool _constant_pool ) {
+        StackMap c = (StackMap) clone();
+        c.map = new StackMapEntry[map_length];
+        for (int i = 0; i < map_length; i++) {
+            c.map[i] = map[i].copy();
+        }
+        c.constant_pool = _constant_pool;
+        return c;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/StackMapEntry.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/StackMapEntry.java
new file mode 100644
index 0000000..790a2ce
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/StackMapEntry.java
@@ -0,0 +1,139 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.tomcat.util.bcel.classfile;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.Serializable;
+
+/**
+ * This class represents a stack map entry recording the types of
+ * local variables and the the of stack items at a given byte code offset.
+ * See CLDC specification &sect;5.3.1.2
+ *
+ * @version $Id: StackMapEntry.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     StackMap
+ * @see     StackMapType
+ */
+public final class StackMapEntry implements Cloneable, Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private int byte_code_offset;
+    private int number_of_locals;
+    private StackMapType[] types_of_locals;
+    private int number_of_stack_items;
+    private StackMapType[] types_of_stack_items;
+
+
+    /**
+     * Construct object from file stream.
+     * @param file Input stream
+     * @throws IOException
+     */
+    StackMapEntry(DataInputStream file, ConstantPool constant_pool) throws IOException {
+        this(file.readShort(), file.readShort(), null, -1, null);
+        types_of_locals = new StackMapType[number_of_locals];
+        for (int i = 0; i < number_of_locals; i++) {
+            types_of_locals[i] = new StackMapType(file, constant_pool);
+        }
+        number_of_stack_items = file.readShort();
+        types_of_stack_items = new StackMapType[number_of_stack_items];
+        for (int i = 0; i < number_of_stack_items; i++) {
+            types_of_stack_items[i] = new StackMapType(file, constant_pool);
+        }
+    }
+
+
+    public StackMapEntry(int byte_code_offset, int number_of_locals,
+            StackMapType[] types_of_locals, int number_of_stack_items,
+            StackMapType[] types_of_stack_items) {
+        this.byte_code_offset = byte_code_offset;
+        this.number_of_locals = number_of_locals;
+        this.types_of_locals = types_of_locals;
+        this.number_of_stack_items = number_of_stack_items;
+        this.types_of_stack_items = types_of_stack_items;
+    }
+
+
+    /**
+     * Dump stack map entry
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    public final void dump( DataOutputStream file ) throws IOException {
+        file.writeShort(byte_code_offset);
+        file.writeShort(number_of_locals);
+        for (int i = 0; i < number_of_locals; i++) {
+            types_of_locals[i].dump(file);
+        }
+        file.writeShort(number_of_stack_items);
+        for (int i = 0; i < number_of_stack_items; i++) {
+            types_of_stack_items[i].dump(file);
+        }
+    }
+
+
+    /**
+     * @return String representation.
+     */
+    @Override
+    public final String toString() {
+        StringBuffer buf = new StringBuffer(64);
+        buf.append("(offset=").append(byte_code_offset);
+        if (number_of_locals > 0) {
+            buf.append(", locals={");
+            for (int i = 0; i < number_of_locals; i++) {
+                buf.append(types_of_locals[i]);
+                if (i < number_of_locals - 1) {
+                    buf.append(", ");
+                }
+            }
+            buf.append("}");
+        }
+        if (number_of_stack_items > 0) {
+            buf.append(", stack items={");
+            for (int i = 0; i < number_of_stack_items; i++) {
+                buf.append(types_of_stack_items[i]);
+                if (i < number_of_stack_items - 1) {
+                    buf.append(", ");
+                }
+            }
+            buf.append("}");
+        }
+        buf.append(")");
+        return buf.toString();
+    }
+
+
+    /**
+     * @return deep copy of this object
+     */
+    public StackMapEntry copy() {
+        try {
+            return (StackMapEntry) clone();
+        } catch (CloneNotSupportedException e) {
+        }
+        return null;
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/StackMapTable.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/StackMapTable.java
new file mode 100644
index 0000000..543bc77
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/StackMapTable.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.tomcat.util.bcel.classfile;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * This class represents a stack map attribute used for
+ * preverification of Java classes for the <a
+ * href="http://java.sun.com/j2me/"> Java 2 Micro Edition</a>
+ * (J2ME). This attribute is used by the <a
+ * href="http://java.sun.com/products/cldc/">KVM</a> and contained
+ * within the Code attribute of a method. See CLDC specification
+ * &sect;5.3.1.2
+ *
+ * @version $Id: StackMapTable.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     Code
+ * @see     StackMapEntry
+ * @see     StackMapType
+ */
+public final class StackMapTable extends Attribute {
+
+    private static final long serialVersionUID = -2931695092763099621L;
+    private int map_length;
+    private StackMapTableEntry[] map; // Table of stack map entries
+
+
+    /*
+     * @param name_index Index of name
+     * @param length Content length in bytes
+     * @param map Table of stack map entries
+     * @param constant_pool Array of constants
+     */
+    public StackMapTable(int name_index, int length, StackMapTableEntry[] map, ConstantPool constant_pool) {
+        super(Constants.ATTR_STACK_MAP_TABLE, name_index, length, constant_pool);
+        setStackMapTable(map);
+    }
+
+
+    /**
+     * Construct object from file stream.
+     * @param name_index Index of name
+     * @param length Content length in bytes
+     * @param file Input stream
+     * @param constant_pool Array of constants
+     * @throws IOException
+     */
+    StackMapTable(int name_index, int length, DataInputStream file, ConstantPool constant_pool)
+            throws IOException {
+        this(name_index, length, (StackMapTableEntry[]) null, constant_pool);
+        map_length = file.readUnsignedShort();
+        map = new StackMapTableEntry[map_length];
+        for (int i = 0; i < map_length; i++) {
+            map[i] = new StackMapTableEntry(file, constant_pool);
+        }
+    }
+
+
+    /**
+     * Dump line number table attribute to file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    @Override
+    public final void dump( DataOutputStream file ) throws IOException {
+        super.dump(file);
+        file.writeShort(map_length);
+        for (int i = 0; i < map_length; i++) {
+            map[i].dump(file);
+        }
+    }
+
+
+    /**
+     * @param map Array of stack map entries
+     */
+    public final void setStackMapTable( StackMapTableEntry[] map ) {
+        this.map = map;
+        map_length = (map == null) ? 0 : map.length;
+    }
+
+
+    /**
+     * @return String representation.
+     */
+    @Override
+    public final String toString() {
+        StringBuffer buf = new StringBuffer("StackMapTable(");
+        for (int i = 0; i < map_length; i++) {
+            buf.append(map[i].toString());
+            if (i < map_length - 1) {
+                buf.append(", ");
+            }
+        }
+        buf.append(')');
+        return buf.toString();
+    }
+
+
+    /**
+     * @return deep copy of this attribute
+     */
+    @Override
+    public Attribute copy( ConstantPool _constant_pool ) {
+        StackMapTable c = (StackMapTable) clone();
+        c.map = new StackMapTableEntry[map_length];
+        for (int i = 0; i < map_length; i++) {
+            c.map[i] = map[i].copy();
+        }
+        c.constant_pool = _constant_pool;
+        return c;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/StackMapTableEntry.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/StackMapTableEntry.java
new file mode 100644
index 0000000..e27c27c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/StackMapTableEntry.java
@@ -0,0 +1,214 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.tomcat.util.bcel.classfile;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.Serializable;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * This class represents a stack map entry recording the types of
+ * local variables and the the of stack items at a given byte code offset.
+ * See CLDC specification &sect;5.3.1.2
+ *
+ * @version $Id: StackMapTableEntry.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     StackMap
+ * @see     StackMapType
+ */
+public final class StackMapTableEntry implements Cloneable, Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private int frame_type;
+    private int byte_code_offset_delta;
+    private int number_of_locals;
+    private StackMapType[] types_of_locals;
+    private int number_of_stack_items;
+    private StackMapType[] types_of_stack_items;
+
+
+    /**
+     * Construct object from file stream.
+     * @param file Input stream
+     * @throws IOException
+     */
+    StackMapTableEntry(DataInputStream file, ConstantPool constant_pool) throws IOException {
+        this(file.read(), -1, -1, null, -1, null);
+        
+        if (frame_type >= Constants.SAME_FRAME && frame_type <= Constants.SAME_FRAME_MAX) {
+            byte_code_offset_delta = frame_type - Constants.SAME_FRAME;
+        } else if (frame_type >= Constants.SAME_LOCALS_1_STACK_ITEM_FRAME && frame_type <= Constants.SAME_LOCALS_1_STACK_ITEM_FRAME_MAX) {
+            byte_code_offset_delta = frame_type - Constants.SAME_LOCALS_1_STACK_ITEM_FRAME;
+            number_of_stack_items = 1;
+            types_of_stack_items = new StackMapType[1];
+            types_of_stack_items[0] = new StackMapType(file, constant_pool);
+        } else if (frame_type == Constants.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
+            byte_code_offset_delta = file.readShort();
+            number_of_stack_items = 1;
+            types_of_stack_items = new StackMapType[1];
+            types_of_stack_items[0] = new StackMapType(file, constant_pool);
+        } else if (frame_type >= Constants.CHOP_FRAME && frame_type <= Constants.CHOP_FRAME_MAX) {
+            byte_code_offset_delta = file.readShort();
+        } else if (frame_type == Constants.SAME_FRAME_EXTENDED) {
+            byte_code_offset_delta = file.readShort();
+        } else if (frame_type >= Constants.APPEND_FRAME && frame_type <= Constants.APPEND_FRAME_MAX) {
+            byte_code_offset_delta = file.readShort();
+            number_of_locals = frame_type - 251;
+            types_of_locals = new StackMapType[number_of_locals];
+            for (int i = 0; i < number_of_locals; i++) {
+                types_of_locals[i] = new StackMapType(file, constant_pool);
+            }            
+        } else if (frame_type == Constants.FULL_FRAME) {        
+            byte_code_offset_delta = file.readShort();
+            number_of_locals = file.readShort();
+            types_of_locals = new StackMapType[number_of_locals];
+            for (int i = 0; i < number_of_locals; i++) {
+                types_of_locals[i] = new StackMapType(file, constant_pool);
+            }
+            number_of_stack_items = file.readShort();
+            types_of_stack_items = new StackMapType[number_of_stack_items];
+            for (int i = 0; i < number_of_stack_items; i++) {
+                types_of_stack_items[i] = new StackMapType(file, constant_pool);
+            }
+        } else {
+            /* Can't happen */
+            throw new ClassFormatException ("Invalid frame type found while parsing stack map table: " + frame_type);
+        }
+    }
+
+
+    public StackMapTableEntry(int tag, int byte_code_offset_delta, int number_of_locals,
+            StackMapType[] types_of_locals, int number_of_stack_items,
+            StackMapType[] types_of_stack_items) {
+        this.frame_type = tag;
+        this.byte_code_offset_delta = byte_code_offset_delta;
+        this.number_of_locals = number_of_locals;
+        this.types_of_locals = types_of_locals;
+        this.number_of_stack_items = number_of_stack_items;
+        this.types_of_stack_items = types_of_stack_items;
+    }
+
+
+    /**
+     * Dump stack map entry
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    public final void dump( DataOutputStream file ) throws IOException {
+        file.write(frame_type);
+        if (frame_type >= Constants.SAME_FRAME && frame_type <= Constants.SAME_FRAME_MAX) {
+            // nothing to be done
+        } else if (frame_type >= Constants.SAME_LOCALS_1_STACK_ITEM_FRAME && frame_type <= Constants.SAME_LOCALS_1_STACK_ITEM_FRAME_MAX) {
+            types_of_stack_items[0].dump(file);
+        } else if (frame_type == Constants.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
+            file.writeShort(byte_code_offset_delta);
+            types_of_stack_items[0].dump(file);
+        } else if (frame_type >= Constants.CHOP_FRAME && frame_type <= Constants.CHOP_FRAME_MAX) {
+            file.writeShort(byte_code_offset_delta);
+        } else if (frame_type == Constants.SAME_FRAME_EXTENDED) {
+            file.writeShort(byte_code_offset_delta);
+        } else if (frame_type >= Constants.APPEND_FRAME && frame_type <= Constants.APPEND_FRAME_MAX) {
+            file.writeShort(byte_code_offset_delta);
+            for (int i = 0; i < number_of_locals; i++) {
+                types_of_locals[i].dump(file);
+            }
+        } else if (frame_type == Constants.FULL_FRAME) {        
+            file.writeShort(byte_code_offset_delta);
+            file.writeShort(number_of_locals);
+            for (int i = 0; i < number_of_locals; i++) {
+                types_of_locals[i].dump(file);
+            }
+            file.writeShort(number_of_stack_items);
+            for (int i = 0; i < number_of_stack_items; i++) {
+                types_of_stack_items[i].dump(file);
+            }
+        } else {
+            /* Can't happen */
+            throw new ClassFormatException ("Invalid Stack map table tag: " + frame_type);
+        }
+    }
+
+
+    /**
+     * @return String representation.
+     */
+    @Override
+    public final String toString() {
+        StringBuffer buf = new StringBuffer(64);
+        buf.append("(");
+        if (frame_type >= Constants.SAME_FRAME && frame_type <= Constants.SAME_FRAME_MAX) {
+            buf.append("SAME");
+        } else if (frame_type >= Constants.SAME_LOCALS_1_STACK_ITEM_FRAME && frame_type <= Constants.SAME_LOCALS_1_STACK_ITEM_FRAME_MAX) {
+            buf.append("SAME_LOCALS_1_STACK");
+        } else if (frame_type == Constants.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
+            buf.append("SAME_LOCALS_1_STACK_EXTENDED");
+        } else if (frame_type >= Constants.CHOP_FRAME && frame_type <= Constants.CHOP_FRAME_MAX) {
+            buf.append("CHOP "+(251-frame_type));
+        } else if (frame_type == Constants.SAME_FRAME_EXTENDED) {
+            buf.append("SAME_EXTENDED");
+        } else if (frame_type >= Constants.APPEND_FRAME && frame_type <= Constants.APPEND_FRAME_MAX) {
+            buf.append("APPEND "+(frame_type-251));
+        } else if (frame_type == Constants.FULL_FRAME) {        
+            buf.append("FULL");
+        } else {
+            buf.append("UNKNOWN");
+        }
+        buf.append(", offset delta=").append(byte_code_offset_delta);
+        if (number_of_locals > 0) {
+            buf.append(", locals={");
+            for (int i = 0; i < number_of_locals; i++) {
+                buf.append(types_of_locals[i]);
+                if (i < number_of_locals - 1) {
+                    buf.append(", ");
+                }
+            }
+            buf.append("}");
+        }
+        if (number_of_stack_items > 0) {
+            buf.append(", stack items={");
+            for (int i = 0; i < number_of_stack_items; i++) {
+                buf.append(types_of_stack_items[i]);
+                if (i < number_of_stack_items - 1) {
+                    buf.append(", ");
+                }
+            }
+            buf.append("}");
+        }
+        buf.append(")");
+        return buf.toString();
+    }
+
+
+    /**
+     * @return deep copy of this object
+     */
+    public StackMapTableEntry copy() {
+        try {
+            return (StackMapTableEntry) clone();
+        } catch (CloneNotSupportedException e) {
+        }
+        return null;
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/StackMapType.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/StackMapType.java
new file mode 100644
index 0000000..9b06914
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/StackMapType.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.tomcat.util.bcel.classfile;
+
+import java.io.DataInput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.Serializable;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * This class represents the type of a local variable or item on stack
+ * used in the StackMap entries.
+ *
+ * @version $Id: StackMapType.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     StackMapEntry
+ * @see     StackMap
+ * @see     Constants
+ */
+public final class StackMapType implements Cloneable, Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private byte type;
+    private int index = -1; // Index to CONSTANT_Class or offset
+    private ConstantPool constant_pool;
+
+
+    /**
+     * Construct object from file stream.
+     * @param file Input stream
+     * @throws IOException
+     */
+    StackMapType(DataInput file, ConstantPool constant_pool) throws IOException {
+        this(file.readByte(), -1, constant_pool);
+        if (hasIndex()) {
+            setIndex(file.readShort());
+        }
+        setConstantPool(constant_pool);
+    }
+
+
+    /**
+     * @param type type tag as defined in the Constants interface
+     * @param index index to constant pool, or byte code offset
+     */
+    public StackMapType(byte type, int index, ConstantPool constant_pool) {
+        setType(type);
+        setIndex(index);
+        setConstantPool(constant_pool);
+    }
+
+
+    public void setType( byte t ) {
+        if ((t < Constants.ITEM_Bogus) || (t > Constants.ITEM_NewObject)) {
+            throw new RuntimeException("Illegal type for StackMapType: " + t);
+        }
+        type = t;
+    }
+
+
+    public void setIndex( int t ) {
+        index = t;
+    }
+
+
+    /** @return index to constant pool if type == ITEM_Object, or offset
+     * in byte code, if type == ITEM_NewObject, and -1 otherwise
+     */
+    public int getIndex() {
+        return index;
+    }
+
+
+    /**
+     * Dump type entries to file.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    public final void dump( DataOutputStream file ) throws IOException {
+        file.writeByte(type);
+        if (hasIndex()) {
+            file.writeShort(getIndex());
+        }
+    }
+
+
+    /** @return true, if type is either ITEM_Object or ITEM_NewObject
+     */
+    public final boolean hasIndex() {
+        return ((type == Constants.ITEM_Object) || (type == Constants.ITEM_NewObject));
+    }
+
+
+    private String printIndex() {
+        if (type == Constants.ITEM_Object) {
+            if (index < 0) {
+                return ", class=<unknown>";
+            }
+            return ", class=" + constant_pool.constantToString(index, Constants.CONSTANT_Class);
+        } else if (type == Constants.ITEM_NewObject) {
+            return ", offset=" + index;
+        } else {
+            return "";
+        }
+    }
+
+
+    /**
+     * @return String representation
+     */
+    @Override
+    public final String toString() {
+        return "(type=" + Constants.ITEM_NAMES[type] + printIndex() + ")";
+    }
+
+
+    /**
+     * @param constant_pool Constant pool to be used for this object.
+     */
+    public final void setConstantPool( ConstantPool constant_pool ) {
+        this.constant_pool = constant_pool;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Synthetic.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Synthetic.java
new file mode 100644
index 0000000..567f590
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Synthetic.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.tomcat.util.bcel.classfile;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * This class is derived from <em>Attribute</em> and declares this class as
+ * `synthetic', i.e., it needs special handling.  The JVM specification
+ * states "A class member that does not appear in the source code must be
+ * marked using a Synthetic attribute."  It may appear in the ClassFile
+ * attribute table, a field_info table or a method_info table.  This class
+ * is intended to be instantiated from the
+ * <em>Attribute.readAttribute()</em> method.
+ *
+ * @version $Id: Synthetic.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ * @see     Attribute
+ */
+public final class Synthetic extends Attribute {
+
+    private static final long serialVersionUID = -5129612853226360165L;
+    private byte[] bytes;
+
+
+    /**
+     * @param name_index Index in constant pool to CONSTANT_Utf8, which
+     * should represent the string "Synthetic".
+     * @param length Content length in bytes - should be zero.
+     * @param bytes Attribute contents
+     * @param constant_pool The constant pool this attribute is associated
+     * with.
+     */
+    public Synthetic(int name_index, int length, byte[] bytes, ConstantPool constant_pool) {
+        super(Constants.ATTR_SYNTHETIC, name_index, length, constant_pool);
+        this.bytes = bytes;
+    }
+
+
+    /**
+     * Construct object from file stream.
+     * @param name_index Index in constant pool to CONSTANT_Utf8
+     * @param length Content length in bytes
+     * @param file Input stream
+     * @param constant_pool Array of constants
+     * @throws IOException
+     */
+    Synthetic(int name_index, int length, DataInputStream file, ConstantPool constant_pool)
+            throws IOException {
+        this(name_index, length, (byte[]) null, constant_pool);
+        if (length > 0) {
+            bytes = new byte[length];
+            file.readFully(bytes);
+            System.err.println("Synthetic attribute with length > 0");
+        }
+    }
+
+
+    /**
+     * Dump source file attribute to file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    @Override
+    public final void dump( DataOutputStream file ) throws IOException {
+        super.dump(file);
+        if (length > 0) {
+            file.write(bytes, 0, length);
+        }
+    }
+
+
+    /**
+     * @return String representation.
+     */
+    @Override
+    public final String toString() {
+        StringBuffer buf = new StringBuffer("Synthetic");
+        if (length > 0) {
+            buf.append(" ").append(Utility.toHexString(bytes));
+        }
+        return buf.toString();
+    }
+
+
+    /**
+     * @return deep copy of this attribute
+     */
+    @Override
+    public Attribute copy( ConstantPool _constant_pool ) {
+        Synthetic c = (Synthetic) clone();
+        if (bytes != null) {
+            c.bytes = new byte[bytes.length];
+            System.arraycopy(bytes, 0, c.bytes, 0, bytes.length);
+        }
+        c.constant_pool = _constant_pool;
+        return c;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Unknown.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Unknown.java
new file mode 100644
index 0000000..d3ba9ec
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Unknown.java
@@ -0,0 +1,145 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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.classfile;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.tomcat.util.bcel.Constants;
+
+/**
+ * This class represents a reference to an unknown (i.e.,
+ * application-specific) attribute of a class.  It is instantiated from the
+ * <em>Attribute.readAttribute()</em> method.  Applications that need to
+ * read in application-specific attributes should create an <a
+ * href="./AttributeReader.html">AttributeReader</a> implementation and
+ * attach it via <a
+ * href="./Attribute.html#addAttributeReader(java.lang.String,
+ * org.apache.tomcat.util.bcel.classfile.AttributeReader)">Attribute.addAttributeReader</a>.
+
+ *
+ * @version $Id: Unknown.java,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+ * @see org.apache.tomcat.util.bcel.classfile.Attribute
+ * @see org.apache.tomcat.util.bcel.classfile.AttributeReader
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ */
+public final class Unknown extends Attribute {
+
+    private static final long serialVersionUID = -4152422704743201314L;
+    private byte[] bytes;
+    private String name;
+    private static final Map<String, Unknown> unknown_attributes =
+            new HashMap<String, Unknown>();
+
+
+    /**
+     * Create a non-standard attribute.
+     *
+     * @param name_index Index in constant pool
+     * @param length Content length in bytes
+     * @param bytes Attribute contents
+     * @param constant_pool Array of constants
+     */
+    public Unknown(int name_index, int length, byte[] bytes, ConstantPool constant_pool) {
+        super(Constants.ATTR_UNKNOWN, name_index, length, constant_pool);
+        this.bytes = bytes;
+        name = ((ConstantUtf8) constant_pool.getConstant(name_index, Constants.CONSTANT_Utf8))
+                .getBytes();
+        unknown_attributes.put(name, this);
+    }
+
+
+    /**
+     * Construct object from file stream.
+     * @param name_index Index in constant pool
+     * @param length Content length in bytes
+     * @param file Input stream
+     * @param constant_pool Array of constants
+     * @throws IOException
+     */
+    Unknown(int name_index, int length, DataInputStream file, ConstantPool constant_pool)
+            throws IOException {
+        this(name_index, length, (byte[]) null, constant_pool);
+        if (length > 0) {
+            bytes = new byte[length];
+            file.readFully(bytes);
+        }
+    }
+
+
+    /**
+     * Dump unknown bytes to file stream.
+     *
+     * @param file Output file stream
+     * @throws IOException
+     */
+    @Override
+    public final void dump( DataOutputStream file ) throws IOException {
+        super.dump(file);
+        if (length > 0) {
+            file.write(bytes, 0, length);
+        }
+    }
+
+
+    /**
+     * @return name of attribute.
+     */
+    @Override
+    public final String getName() {
+        return name;
+    }
+
+
+    /**
+     * @return String representation.
+     */
+    @Override
+    public final String toString() {
+        if (length == 0 || bytes == null) {
+            return "(Unknown attribute " + name + ")";
+        }
+        String hex;
+        if (length > 10) {
+            byte[] tmp = new byte[10];
+            System.arraycopy(bytes, 0, tmp, 0, 10);
+            hex = Utility.toHexString(tmp) + "... (truncated)";
+        } else {
+            hex = Utility.toHexString(bytes);
+        }
+        return "(Unknown attribute " + name + ": " + hex + ")";
+    }
+
+
+    /**
+     * @return deep copy of this attribute
+     */
+    @Override
+    public Attribute copy( ConstantPool _constant_pool ) {
+        Unknown c = (Unknown) clone();
+        if (bytes != null) {
+            c.bytes = new byte[bytes.length];
+            System.arraycopy(bytes, 0, c.bytes, 0, bytes.length);
+        }
+        c.constant_pool = _constant_pool;
+        return c;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Utility.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Utility.java
new file mode 100644
index 0000000..5290c4e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/Utility.java
@@ -0,0 +1,783 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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.classfile;
+
+import java.io.IOException;
+
+import org.apache.tomcat.util.bcel.Constants;
+import org.apache.tomcat.util.bcel.util.ByteSequence;
+
+/**
+ * Utility functions that do not really belong to any class in particular.
+ *
+ * @version $Id: Utility.java,v 1.1 2011/06/28 21:08:23 rherrmann Exp $
+ * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
+ */
+public abstract class Utility {
+
+    private static int unwrap( ThreadLocal<Integer> tl ) {
+        return tl.get().intValue();
+    }
+
+
+    private static void wrap( ThreadLocal<Integer> tl, int value ) {
+        tl.set(new Integer(value));
+    }
+
+    private static ThreadLocal<Integer> consumed_chars =
+            new ThreadLocal<Integer>() {
+        @Override
+        protected Integer initialValue() {
+            return new Integer(0);
+        }
+    };/* How many chars have been consumed
+     * during parsing in signatureToString().
+     * Read by methodSignatureToString().
+     * Set by side effect,but only internally.
+     */
+    private static boolean wide = false; /* The `WIDE' instruction is used in the
+     * byte code to allow 16-bit wide indices
+     * for local variables. This opcode
+     * precedes an `ILOAD', e.g.. The opcode
+     * immediately following takes an extra
+     * byte which is combined with the
+     * following byte to form a
+     * 16-bit value.
+     */
+
+
+    /**
+     * Convert bit field of flags into string such as `static final'.
+     *
+     * @param  access_flags Access flags
+     * @return String representation of flags
+     */
+    public static final String accessToString( int access_flags ) {
+        return accessToString(access_flags, false);
+    }
+
+
+    /**
+     * Convert bit field of flags into string such as `static final'.
+     *
+     * Special case: Classes compiled with new compilers and with the
+     * `ACC_SUPER' flag would be said to be "synchronized". This is
+     * because SUN used the same value for the flags `ACC_SUPER' and
+     * `ACC_SYNCHRONIZED'. 
+     *
+     * @param  access_flags Access flags
+     * @param  for_class access flags are for class qualifiers ?
+     * @return String representation of flags
+     */
+    public static final String accessToString( int access_flags, boolean for_class ) {
+        StringBuffer buf = new StringBuffer();
+        int p = 0;
+        for (int i = 0; p < Constants.MAX_ACC_FLAG; i++) { // Loop through known flags
+            p = pow2(i);
+            if ((access_flags & p) != 0) {
+                /* Special case: Classes compiled with new compilers and with the
+                 * `ACC_SUPER' flag would be said to be "synchronized". This is
+                 * because SUN used the same value for the flags `ACC_SUPER' and
+                 * `ACC_SYNCHRONIZED'.
+                 */
+                if (for_class && ((p == Constants.ACC_SUPER) || (p == Constants.ACC_INTERFACE))) {
+                    continue;
+                }
+                buf.append(Constants.ACCESS_NAMES[i]).append(" ");
+            }
+        }
+        return buf.toString().trim();
+    }
+
+
+    /**
+     * @param access_flags the class flags
+     * 
+     * @return "class" or "interface", depending on the ACC_INTERFACE flag
+     */
+    public static final String classOrInterface( int access_flags ) {
+        return ((access_flags & Constants.ACC_INTERFACE) != 0) ? "interface" : "class";
+    }
+
+
+    /**
+     * Disassemble a byte array of JVM byte codes starting from code line 
+     * `index' and return the disassembled string representation. Decode only
+     * `num' opcodes (including their operands), use -1 if you want to
+     * decompile everything.
+     *
+     * @param  code byte code array
+     * @param  constant_pool Array of constants
+     * @param  index offset in `code' array
+     * <EM>(number of opcodes, not bytes!)</EM>
+     * @param  length number of opcodes to decompile, -1 for all
+     * @param  verbose be verbose, e.g. print constant pool index
+     * @return String representation of byte codes
+     */
+    public static final String codeToString( byte[] code, ConstantPool constant_pool, int index,
+            int length, boolean verbose ) {
+        StringBuffer buf = new StringBuffer(code.length * 20); // Should be sufficient
+        ByteSequence stream = new ByteSequence(code);
+        try {
+            for (int i = 0; i < index; i++) {
+                codeToString(stream, constant_pool, verbose);
+            }
+            for (int i = 0; stream.available() > 0; i++) {
+                if ((length < 0) || (i < length)) {
+                    String indices = fillup(stream.getIndex() + ":", 6, true, ' ');
+                    buf.append(indices).append(codeToString(stream, constant_pool, verbose))
+                            .append('\n');
+                }
+            }
+        } catch (IOException e) {
+            System.out.println(buf.toString());
+            e.printStackTrace();
+            throw new ClassFormatException("Byte code error: " + e, e);
+        }
+        return buf.toString();
+    }
+
+
+    /**
+     * Disassemble a stream of byte codes and return the
+     * string representation.
+     *
+     * @param  bytes stream of bytes
+     * @param  constant_pool Array of constants
+     * @param  verbose be verbose, e.g. print constant pool index
+     * @return String representation of byte code
+     * 
+     * @throws IOException if a failure from reading from the bytes argument occurs
+     */
+    public static final String codeToString( ByteSequence bytes, ConstantPool constant_pool,
+            boolean verbose ) throws IOException {
+        short opcode = (short) bytes.readUnsignedByte();
+        int default_offset = 0, low, high, npairs;
+        int index, vindex, constant;
+        int[] match, jump_table;
+        int no_pad_bytes = 0, offset;
+        StringBuffer buf = new StringBuffer(Constants.OPCODE_NAMES[opcode]);
+        /* Special case: Skip (0-3) padding bytes, i.e., the
+         * following bytes are 4-byte-aligned
+         */
+        if ((opcode == Constants.TABLESWITCH) || (opcode == Constants.LOOKUPSWITCH)) {
+            int remainder = bytes.getIndex() % 4;
+            no_pad_bytes = (remainder == 0) ? 0 : 4 - remainder;
+            for (int i = 0; i < no_pad_bytes; i++) {
+                byte b;
+                if ((b = bytes.readByte()) != 0) {
+                    System.err.println("Warning: Padding byte != 0 in "
+                            + Constants.OPCODE_NAMES[opcode] + ":" + b);
+                }
+            }
+            // Both cases have a field default_offset in common
+            default_offset = bytes.readInt();
+        }
+        switch (opcode) {
+            /* Table switch has variable length arguments.
+             */
+            case Constants.TABLESWITCH:
+                low = bytes.readInt();
+                high = bytes.readInt();
+                offset = bytes.getIndex() - 12 - no_pad_bytes - 1;
+                default_offset += offset;
+                buf.append("\tdefault = ").append(default_offset).append(", low = ").append(low)
+                        .append(", high = ").append(high).append("(");
+                jump_table = new int[high - low + 1];
+                for (int i = 0; i < jump_table.length; i++) {
+                    jump_table[i] = offset + bytes.readInt();
+                    buf.append(jump_table[i]);
+                    if (i < jump_table.length - 1) {
+                        buf.append(", ");
+                    }
+                }
+                buf.append(")");
+                break;
+            /* Lookup switch has variable length arguments.
+             */
+            case Constants.LOOKUPSWITCH: {
+                npairs = bytes.readInt();
+                offset = bytes.getIndex() - 8 - no_pad_bytes - 1;
+                match = new int[npairs];
+                jump_table = new int[npairs];
+                default_offset += offset;
+                buf.append("\tdefault = ").append(default_offset).append(", npairs = ").append(
+                        npairs).append(" (");
+                for (int i = 0; i < npairs; i++) {
+                    match[i] = bytes.readInt();
+                    jump_table[i] = offset + bytes.readInt();
+                    buf.append("(").append(match[i]).append(", ").append(jump_table[i]).append(")");
+                    if (i < npairs - 1) {
+                        buf.append(", ");
+                    }
+                }
+                buf.append(")");
+            }
+                break;
+            /* Two address bytes + offset from start of byte stream form the
+             * jump target
+             */
+            case Constants.GOTO:
+            case Constants.IFEQ:
+            case Constants.IFGE:
+            case Constants.IFGT:
+            case Constants.IFLE:
+            case Constants.IFLT:
+            case Constants.JSR:
+            case Constants.IFNE:
+            case Constants.IFNONNULL:
+            case Constants.IFNULL:
+            case Constants.IF_ACMPEQ:
+            case Constants.IF_ACMPNE:
+            case Constants.IF_ICMPEQ:
+            case Constants.IF_ICMPGE:
+            case Constants.IF_ICMPGT:
+            case Constants.IF_ICMPLE:
+            case Constants.IF_ICMPLT:
+            case Constants.IF_ICMPNE:
+                buf.append("\t\t#").append((bytes.getIndex() - 1) + bytes.readShort());
+                break;
+            /* 32-bit wide jumps
+             */
+            case Constants.GOTO_W:
+            case Constants.JSR_W:
+                buf.append("\t\t#").append(((bytes.getIndex() - 1) + bytes.readInt()));
+                break;
+            /* Index byte references local variable (register)
+             */
+            case Constants.ALOAD:
+            case Constants.ASTORE:
+            case Constants.DLOAD:
+            case Constants.DSTORE:
+            case Constants.FLOAD:
+            case Constants.FSTORE:
+            case Constants.ILOAD:
+            case Constants.ISTORE:
+            case Constants.LLOAD:
+            case Constants.LSTORE:
+            case Constants.RET:
+                if (wide) {
+                    vindex = bytes.readUnsignedShort();
+                    wide = false; // Clear flag
+                } else {
+                    vindex = bytes.readUnsignedByte();
+                }
+                buf.append("\t\t%").append(vindex);
+                break;
+            /*
+             * Remember wide byte which is used to form a 16-bit address in the
+             * following instruction. Relies on that the method is called again with
+             * the following opcode.
+             */
+            case Constants.WIDE:
+                wide = true;
+                buf.append("\t(wide)");
+                break;
+            /* Array of basic type.
+             */
+            case Constants.NEWARRAY:
+                buf.append("\t\t<").append(Constants.TYPE_NAMES[bytes.readByte()]).append(">");
+                break;
+            /* Access object/class fields.
+             */
+            case Constants.GETFIELD:
+            case Constants.GETSTATIC:
+            case Constants.PUTFIELD:
+            case Constants.PUTSTATIC:
+                index = bytes.readUnsignedShort();
+                buf.append("\t\t").append(
+                        constant_pool.constantToString(index, Constants.CONSTANT_Fieldref)).append(
+                        (verbose ? " (" + index + ")" : ""));
+                break;
+            /* Operands are references to classes in constant pool
+             */
+            case Constants.NEW:
+            case Constants.CHECKCAST:
+                buf.append("\t");
+                //$FALL-THROUGH$
+            case Constants.INSTANCEOF:
+                index = bytes.readUnsignedShort();
+                buf.append("\t<").append(
+                        constant_pool.constantToString(index, Constants.CONSTANT_Class))
+                        .append(">").append((verbose ? " (" + index + ")" : ""));
+                break;
+            /* Operands are references to methods in constant pool
+             */
+            case Constants.INVOKESPECIAL:
+            case Constants.INVOKESTATIC:
+            case Constants.INVOKEVIRTUAL:
+                index = bytes.readUnsignedShort();
+                buf.append("\t").append(
+                        constant_pool.constantToString(index, Constants.CONSTANT_Methodref))
+                        .append((verbose ? " (" + index + ")" : ""));
+                break;
+            case Constants.INVOKEINTERFACE:
+                index = bytes.readUnsignedShort();
+                int nargs = bytes.readUnsignedByte(); // historical, redundant
+                buf.append("\t").append(
+                        constant_pool
+                                .constantToString(index, Constants.CONSTANT_InterfaceMethodref))
+                        .append(verbose ? " (" + index + ")\t" : "").append(nargs).append("\t")
+                        .append(bytes.readUnsignedByte()); // Last byte is a reserved space
+                break;
+            /* Operands are references to items in constant pool
+             */
+            case Constants.LDC_W:
+            case Constants.LDC2_W:
+                index = bytes.readUnsignedShort();
+                buf.append("\t\t").append(
+                        constant_pool.constantToString(index, constant_pool.getConstant(index)
+                                .getTag())).append((verbose ? " (" + index + ")" : ""));
+                break;
+            case Constants.LDC:
+                index = bytes.readUnsignedByte();
+                buf.append("\t\t").append(
+                        constant_pool.constantToString(index, constant_pool.getConstant(index)
+                                .getTag())).append((verbose ? " (" + index + ")" : ""));
+                break;
+            /* Array of references.
+             */
+            case Constants.ANEWARRAY:
+                index = bytes.readUnsignedShort();
+                buf.append("\t\t<").append(
+                        compactClassName(constant_pool.getConstantString(index,
+                                Constants.CONSTANT_Class), false)).append(">").append(
+                        (verbose ? " (" + index + ")" : ""));
+                break;
+            /* Multidimensional array of references.
+             */
+            case Constants.MULTIANEWARRAY: {
+                index = bytes.readUnsignedShort();
+                int dimensions = bytes.readUnsignedByte();
+                buf.append("\t<").append(
+                        compactClassName(constant_pool.getConstantString(index,
+                                Constants.CONSTANT_Class), false)).append(">\t").append(dimensions)
+                        .append((verbose ? " (" + index + ")" : ""));
+            }
+                break;
+            /* Increment local variable.
+             */
+            case Constants.IINC:
+                if (wide) {
+                    vindex = bytes.readUnsignedShort();
+                    constant = bytes.readShort();
+                    wide = false;
+                } else {
+                    vindex = bytes.readUnsignedByte();
+                    constant = bytes.readByte();
+                }
+                buf.append("\t\t%").append(vindex).append("\t").append(constant);
+                break;
+            default:
+                if (Constants.NO_OF_OPERANDS[opcode] > 0) {
+                    for (int i = 0; i < Constants.TYPE_OF_OPERANDS[opcode].length; i++) {
+                        buf.append("\t\t");
+                        switch (Constants.TYPE_OF_OPERANDS[opcode][i]) {
+                            case Constants.T_BYTE:
+                                buf.append(bytes.readByte());
+                                break;
+                            case Constants.T_SHORT:
+                                buf.append(bytes.readShort());
+                                break;
+                            case Constants.T_INT:
+                                buf.append(bytes.readInt());
+                                break;
+                            default: // Never reached
+                                System.err.println("Unreachable default case reached!");
+                                System.exit(-1);
+                        }
+                    }
+                }
+        }
+        return buf.toString();
+    }
+
+
+    /**
+     * Shorten long class names, <em>java/lang/String</em> becomes 
+     * <em>String</em>.
+     *
+     * @param str The long class name
+     * @return Compacted class name
+     */
+    public static final String compactClassName( String str ) {
+        return compactClassName(str, true);
+    }
+
+
+    /**
+     * Shorten long class name <em>str</em>, i.e., chop off the <em>prefix</em>,
+     * if the
+     * class name starts with this string and the flag <em>chopit</em> is true.
+     * Slashes <em>/</em> are converted to dots <em>.</em>.
+     *
+     * @param str The long class name
+     * @param prefix The prefix the get rid off
+     * @param chopit Flag that determines whether chopping is executed or not
+     * @return Compacted class name
+     */
+    public static final String compactClassName( String str, String prefix, boolean chopit ) {
+        int len = prefix.length();
+        str = str.replace('/', '.'); // Is `/' on all systems, even DOS
+        if (chopit) {
+            // If string starts with `prefix' and contains no further dots
+            if (str.startsWith(prefix) && (str.substring(len).indexOf('.') == -1)) {
+                str = str.substring(len);
+            }
+        }
+        return str;
+    }
+
+
+    /**
+     * Shorten long class names, <em>java/lang/String</em> becomes 
+     * <em>java.lang.String</em>,
+     * e.g.. If <em>chopit</em> is <em>true</em> the prefix <em>java.lang</em>
+     * is also removed.
+     *
+     * @param str The long class name
+     * @param chopit Flag that determines whether chopping is executed or not
+     * @return Compacted class name
+     */
+    public static final String compactClassName( String str, boolean chopit ) {
+        return compactClassName(str, "java.lang.", chopit);
+    }
+
+    /**
+     * A returntype signature represents the return value from a method.
+     * It is a series of bytes in the following grammar:
+     *
+     * <return_signature> ::= <field_type> | V
+     *
+     * The character V indicates that the method returns no value. Otherwise, the
+     * signature indicates the type of the return value.
+     * An argument signature represents an argument passed to a method:
+     *
+     * <argument_signature> ::= <field_type>
+     *
+     * A method signature represents the arguments that the method expects, and
+     * the value that it returns.
+     * <method_signature> ::= (<arguments_signature>) <return_signature>
+     * <arguments_signature>::= <argument_signature>*
+     *
+     * This method converts such a string into a Java type declaration like
+     * `void main(String[])' and throws a `ClassFormatException' when the parsed 
+     * type is invalid.
+     *
+     * @param  signature    Method signature
+     * @param  name         Method name
+     * @param  access       Method access rights
+     * @param chopit
+     * @param vars
+     * @return Java type declaration
+     * @throws  ClassFormatException  
+     */
+    public static final String methodSignatureToString( String signature, String name,
+            String access, boolean chopit, LocalVariableTable vars ) throws ClassFormatException {
+        StringBuffer buf = new StringBuffer("(");
+        String type;
+        int index;
+        int var_index = (access.indexOf("static") >= 0) ? 0 : 1;
+        try { // Read all declarations between for `(' and `)'
+            if (signature.charAt(0) != '(') {
+                throw new ClassFormatException("Invalid method signature: " + signature);
+            }
+            index = 1; // current string position
+            while (signature.charAt(index) != ')') {
+                String param_type = signatureToString(signature.substring(index), chopit);
+                buf.append(param_type);
+                if (vars != null) {
+                    LocalVariable l = vars.getLocalVariable(var_index);
+                    if (l != null) {
+                        buf.append(" ").append(l.getName());
+                    }
+                } else {
+                    buf.append(" arg").append(var_index);
+                }
+                if ("double".equals(param_type) || "long".equals(param_type)) {
+                    var_index += 2;
+                } else {
+                    var_index++;
+                }
+                buf.append(", ");
+                //corrected concurrent private static field acess
+                index += unwrap(consumed_chars); // update position
+            }
+            index++; // update position
+            // Read return type after `)'
+            type = signatureToString(signature.substring(index), chopit);
+        } catch (StringIndexOutOfBoundsException e) { // Should never occur
+            throw new ClassFormatException("Invalid method signature: " + signature, e);
+        }
+        if (buf.length() > 1) {
+            buf.setLength(buf.length() - 2);
+        }
+        buf.append(")");
+        return access + ((access.length() > 0) ? " " : "") + // May be an empty string
+                type + " " + name + buf.toString();
+    }
+
+
+    // Guess what this does
+    private static final int pow2( int n ) {
+        return 1 << n;
+    }
+
+
+    /**
+     * Replace all occurrences of <em>old</em> in <em>str</em> with <em>new</em>.
+     *
+     * @param str String to permute
+     * @param old String to be replaced
+     * @param new_ Replacement string
+     * @return new String object
+     */
+    public static final String replace( String str, String old, String new_ ) {
+        int index, old_index;
+        try {
+            if (str.indexOf(old) != -1) { // `old' found in str
+                StringBuffer buf = new StringBuffer();
+                old_index = 0; // String start offset
+                // While we have something to replace
+                while ((index = str.indexOf(old, old_index)) != -1) {
+                    buf.append(str.substring(old_index, index)); // append prefix
+                    buf.append(new_); // append replacement
+                    old_index = index + old.length(); // Skip `old'.length chars
+                }
+                buf.append(str.substring(old_index)); // append rest of string
+                str = buf.toString();
+            }
+        } catch (StringIndexOutOfBoundsException e) { // Should not occur
+            System.err.println(e);
+        }
+        return str;
+    }
+
+
+    /**
+     * Converts signature to string with all class names compacted.
+     *
+     * @param signature to convert
+     * @return Human readable signature
+     */
+    public static final String signatureToString( String signature ) {
+        return signatureToString(signature, true);
+    }
+
+
+    /**
+     * The field signature represents the value of an argument to a function or 
+     * the value of a variable. It is a series of bytes generated by the 
+     * following grammar:
+     *
+     * <PRE>
+     * <field_signature> ::= <field_type>
+     * <field_type>      ::= <base_type>|<object_type>|<array_type>
+     * <base_type>       ::= B|C|D|F|I|J|S|Z
+     * <object_type>     ::= L<fullclassname>;
+     * <array_type>      ::= [<field_type>
+     *
+     * The meaning of the base types is as follows:
+     * B byte signed byte
+     * C char character
+     * D double double precision IEEE float
+     * F float single precision IEEE float
+     * I int integer
+     * J long long integer
+     * L<fullclassname>; ... an object of the given class
+     * S short signed short
+     * Z boolean true or false
+     * [<field sig> ... array
+     * </PRE>
+     *
+     * This method converts this string into a Java type declaration such as
+     * `String[]' and throws a `ClassFormatException' when the parsed type is 
+     * invalid.
+     *
+     * @param  signature  Class signature
+     * @param chopit Flag that determines whether chopping is executed or not
+     * @return Java type declaration
+     * @throws ClassFormatException
+     */
+    public static final String signatureToString( String signature, boolean chopit ) {
+        //corrected concurrent private static field acess
+        wrap(consumed_chars, 1); // This is the default, read just one char like `B'
+        try {
+            switch (signature.charAt(0)) {
+                case 'B':
+                    return "byte";
+                case 'C':
+                    return "char";
+                case 'D':
+                    return "double";
+                case 'F':
+                    return "float";
+                case 'I':
+                    return "int";
+                case 'J':
+                    return "long";
+                case 'L': { // Full class name
+                    int index = signature.indexOf(';'); // Look for closing `;'
+                    if (index < 0) {
+                        throw new ClassFormatException("Invalid signature: " + signature);
+                    }
+                    //corrected concurrent private static field acess
+                    wrap(consumed_chars, index + 1); // "Lblabla;" `L' and `;' are removed
+                    return compactClassName(signature.substring(1, index), chopit);
+                }
+                case 'S':
+                    return "short";
+                case 'Z':
+                    return "boolean";
+                case '[': { // Array declaration
+                    int n;
+                    StringBuffer brackets;
+                    String type;
+                    int consumed_chars; // Shadows global var
+                    brackets = new StringBuffer(); // Accumulate []'s
+                    // Count opening brackets and look for optional size argument
+                    for (n = 0; signature.charAt(n) == '['; n++) {
+                        brackets.append("[]");
+                    }
+                    consumed_chars = n; // Remember value
+                    // The rest of the string denotes a `<field_type>'
+                    type = signatureToString(signature.substring(n), chopit);
+                    //corrected concurrent private static field acess
+                    //Utility.consumed_chars += consumed_chars; is replaced by:
+                    int _temp = unwrap(Utility.consumed_chars) + consumed_chars;
+                    wrap(Utility.consumed_chars, _temp);
+                    return type + brackets.toString();
+                }
+                case 'V':
+                    return "void";
+                default:
+                    throw new ClassFormatException("Invalid signature: `" + signature + "'");
+            }
+        } catch (StringIndexOutOfBoundsException e) { // Should never occur
+            throw new ClassFormatException("Invalid signature: " + signature, e);
+        }
+    }
+
+    /**
+     * Convert (signed) byte to (unsigned) short value, i.e., all negative
+     * values become positive.
+     */
+    private static final short byteToShort( byte b ) {
+        return (b < 0) ? (short) (256 + b) : (short) b;
+    }
+
+
+    /** Convert bytes into hexadecimal string
+     *
+     * @param bytes an array of bytes to convert to hexadecimal
+     * 
+     * @return bytes as hexadecimal string, e.g. 00 FA 12 ...
+     */
+    public static final String toHexString( byte[] bytes ) {
+        StringBuffer buf = new StringBuffer();
+        for (int i = 0; i < bytes.length; i++) {
+            short b = byteToShort(bytes[i]);
+            String hex = Integer.toString(b, 0x10);
+            if (b < 0x10) {
+                buf.append('0');
+            }
+            buf.append(hex);
+            if (i < bytes.length - 1) {
+                buf.append(' ');
+            }
+        }
+        return buf.toString();
+    }
+
+    /**
+     * Fillup char with up to length characters with char `fill' and justify it left or right.
+     *
+     * @param str string to format
+     * @param length length of desired string
+     * @param left_justify format left or right
+     * @param fill fill character
+     * @return formatted string
+     */
+    public static final String fillup( String str, int length, boolean left_justify, char fill ) {
+        int len = length - str.length();
+        char[] buf = new char[(len < 0) ? 0 : len];
+        for (int j = 0; j < buf.length; j++) {
+            buf[j] = fill;
+        }
+        if (left_justify) {
+            return str + new String(buf);
+        }
+        return new String(buf) + str;
+    }
+
+    // A-Z, g-z, _, $
+    private static final int FREE_CHARS = 48;
+    static int[] CHAR_MAP = new int[FREE_CHARS];
+    static int[] MAP_CHAR = new int[256]; // Reverse map
+    static {
+        int j = 0;
+        for (int i = 'A'; i <= 'Z'; i++) {
+            CHAR_MAP[j] = i;
+            MAP_CHAR[i] = j;
+            j++;
+        }
+        for (int i = 'g'; i <= 'z'; i++) {
+            CHAR_MAP[j] = i;
+            MAP_CHAR[i] = j;
+            j++;
+        }
+        CHAR_MAP[j] = '$';
+        MAP_CHAR['$'] = j;
+        j++;
+        CHAR_MAP[j] = '_';
+        MAP_CHAR['_'] = j;
+    }
+
+    /**
+     * Escape all occurences of newline chars '\n', quotes \", etc.
+     */
+    public static final String convertString( String label ) {
+        char[] ch = label.toCharArray();
+        StringBuffer buf = new StringBuffer();
+        for (int i = 0; i < ch.length; i++) {
+            switch (ch[i]) {
+                case '\n':
+                    buf.append("\\n");
+                    break;
+                case '\r':
+                    buf.append("\\r");
+                    break;
+                case '\"':
+                    buf.append("\\\"");
+                    break;
+                case '\'':
+                    buf.append("\\'");
+                    break;
+                case '\\':
+                    buf.append("\\\\");
+                    break;
+                default:
+                    buf.append(ch[i]);
+                    break;
+            }
+        }
+        return buf.toString();
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/package.html b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/package.html
new file mode 100644
index 0000000..7b5d42c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/classfile/package.html
@@ -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.
+-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+$Id: package.html,v 1.1 2011/06/28 21:08:24 rherrmann Exp $
+-->
+</head>
+<body bgcolor="white">
+<p>
+This package contains the classes that describe the structure of a
+Java class file and a class file parser.
+</p>
+</body>
+</html>
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/package.html b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/package.html
new file mode 100644
index 0000000..e946db4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/bcel/package.html
@@ -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.
+-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+$Id: package.html,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+-->
+</head>
+<body bgcolor="white">
+<p>
+This package contains basic classes for the
+<a href="http://jakarta.apache.org/bcel/">Byte Code Engineering Library</a>
+ and constants defined by the
+<a href="http://java.sun.com/docs/books/vmspec/html/VMSpecTOC.doc.html">
+ JVM specification</a>.
+</p>
+</body>
+</html>
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/res/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/res/LocalStrings.properties
new file mode 100644
index 0000000..7ee1f26
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/res/LocalStrings.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+hexUtil.bad=Bad hexadecimal digit
+hexUtil.odd=Odd number of hexadecimal digits
+httpDate.pe=invalid date format: {0}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/res/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/res/LocalStrings_es.properties
new file mode 100644
index 0000000..e5c5efa
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/res/LocalStrings_es.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+hexUtil.bad=D\u00edgito hexadecimal incorrecto
+hexUtil.odd=N\u00famero de d\u00edgitos hexadecimales incorrecto
+httpDate.pe=formato de fecha no v\u00e1lido: {0}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/res/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/res/LocalStrings_fr.properties
new file mode 100644
index 0000000..f4d753c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/res/LocalStrings_fr.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+hexUtil.bad=Mauvais digit hexad\u00e9cimal
+hexUtil.odd=Nombre impair de digits hexad\u00e9cimaux
+httpDate.pe=Format de date invalide: {0}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/res/LocalStrings_ja.properties b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/res/LocalStrings_ja.properties
new file mode 100644
index 0000000..a6b46bc
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/buf/res/LocalStrings_ja.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+hexUtil.bad=\u7121\u52b9\u306a16\u9032\u6570\u5024\u3067\u3059
+hexUtil.odd=\u5947\u6570\u6841\u306e16\u9032\u6570\u5024\u3067\u3059
+httpDate.pe=\u7121\u52b9\u306a\u65e5\u4ed8\u30d5\u30a9\u30fc\u30de\u30c3\u30c8\u3067\u3059: {0}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/AbstractObjectCreationFactory.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/AbstractObjectCreationFactory.java
new file mode 100644
index 0000000..35dad88
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/AbstractObjectCreationFactory.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.tomcat.util.digester;
+
+
+import org.xml.sax.Attributes;
+
+
+/**
+ * <p>Abstract base class for <code>ObjectCreationFactory</code>
+ * implementations.</p>
+ */
+public abstract class AbstractObjectCreationFactory implements ObjectCreationFactory {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The associated <code>Digester</code> instance that was set up by
+     * {@link FactoryCreateRule} upon initialization.
+     */
+    protected Digester digester = null;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * <p>Factory method called by {@link FactoryCreateRule} to supply an
+     * object based on the element's attributes.
+     *
+     * @param attributes the element's attributes
+     *
+     * @throws Exception any exception thrown will be propagated upwards
+     */
+    public abstract Object createObject(Attributes attributes) throws Exception;
+
+
+    /**
+     * <p>Returns the {@link Digester} that was set by the
+     * {@link FactoryCreateRule} upon initialization.
+     */
+    public Digester getDigester() {
+
+        return (this.digester);
+
+    }
+
+
+    /**
+     * <p>Set the {@link Digester} to allow the implementation to do logging,
+     * classloading based on the digester's classloader, etc.
+     *
+     * @param digester parent Digester object
+     */
+    public void setDigester(Digester digester) {
+
+        this.digester = digester;
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/AbstractRulesImpl.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/AbstractRulesImpl.java
new file mode 100644
index 0000000..f0527e5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/AbstractRulesImpl.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.digester;
+
+
+import java.util.List;
+
+
+/**
+ * <p><code>AbstractRuleImpl</code> provides basic services for <code>Rules</code> implementations.
+ * Extending this class should make it easier to create a <code>Rules</code> implementation.</p>
+ * 
+ * <p><code>AbstractRuleImpl</code> manages the <code>Digester</code> 
+ * and <code>namespaceUri</code> properties.
+ * If the subclass overrides {@link #registerRule} (rather than {@link #add}),
+ * then the <code>Digester</code> and <code>namespaceURI</code> of the <code>Rule</code>
+ * will be set correctly before it is passed to <code>registerRule</code>.
+ * The subclass can then perform whatever it needs to do to register the rule.</p>
+ *
+ * @since 1.5
+ */
+
+public abstract class AbstractRulesImpl implements Rules {
+
+    // ------------------------------------------------------------- Fields
+    
+    /** Digester using this <code>Rules</code> implementation */
+    private Digester digester;
+    /** Namespace uri to associate with subsequent <code>Rule</code>'s */
+    private String namespaceURI;
+    
+    // ------------------------------------------------------------- Properties
+
+    /**
+     * Return the Digester instance with which this Rules instance is
+     * associated.
+     */
+    public Digester getDigester() {
+        return digester;
+    }
+
+    /**
+     * Set the Digester instance with which this Rules instance is associated.
+     *
+     * @param digester The newly associated Digester instance
+     */
+    public void setDigester(Digester digester) {
+        this.digester = digester;
+    }
+
+    /**
+     * Return the namespace URI that will be applied to all subsequently
+     * added <code>Rule</code> objects.
+     */
+    public String getNamespaceURI() {
+        return namespaceURI;
+    }
+
+    /**
+     * Set the namespace URI that will be applied to all subsequently
+     * added <code>Rule</code> objects.
+     *
+     * @param namespaceURI Namespace URI that must match on all
+     *  subsequently added rules, or <code>null</code> for matching
+     *  regardless of the current namespace URI
+     */
+    public void setNamespaceURI(String namespaceURI) {
+        this.namespaceURI = namespaceURI;
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Registers a new Rule instance matching the specified pattern.
+     * This implementation sets the <code>Digester</code> and the
+     * <code>namespaceURI</code> on the <code>Rule</code> before calling {@link #registerRule}.
+     *
+     * @param pattern Nesting pattern to be matched for this Rule
+     * @param rule Rule instance to be registered
+     */
+    public void add(String pattern, Rule rule) {
+        // set up rule
+        if (this.digester != null) {
+            rule.setDigester(this.digester);
+        }
+        
+        if (this.namespaceURI != null) {
+            rule.setNamespaceURI(this.namespaceURI);
+        }
+        
+        registerRule(pattern, rule);
+        
+    }
+    
+    /** 
+     * Register rule at given pattern.
+     * The the Digester and namespaceURI properties of the given <code>Rule</code>
+     * can be assumed to have been set properly before this method is called.
+     *
+     * @param pattern Nesting pattern to be matched for this Rule
+     * @param rule Rule instance to be registered
+     */ 
+    protected abstract void registerRule(String pattern, Rule rule);
+
+    /**
+     * Clear all existing Rule instance registrations.
+     */
+    public abstract void clear();
+
+
+    /**
+     * Return a List of all registered Rule instances that match the specified
+     * nesting pattern, or a zero-length List if there are no matches.  If more
+     * than one Rule instance matches, they <strong>must</strong> be returned
+     * in the order originally registered through the <code>add()</code>
+     * method.
+     *
+     * @param namespaceURI Namespace URI for which to select matching rules,
+     *  or <code>null</code> to match regardless of namespace URI
+     * @param pattern Nesting pattern to be matched
+     */
+    public abstract List<Rule> match(String namespaceURI, String pattern);
+
+
+    /**
+     * Return a List of all registered Rule instances, or a zero-length List
+     * if there are no registered Rule instances.  If more than one Rule
+     * instance has been registered, they <strong>must</strong> be returned
+     * in the order originally registered through the <code>add()</code>
+     * method.
+     */
+    public abstract List<Rule> rules();
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/ArrayStack.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/ArrayStack.java
new file mode 100644
index 0000000..71817be
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/ArrayStack.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.tomcat.util.digester;
+
+import java.util.ArrayList;
+import java.util.EmptyStackException;
+
+/**
+ * <p>Imported copy of the <code>ArrayStack</code> class from
+ * Commons Collections, which was the only direct dependency from Digester.</p>
+ *
+ * <p><strong>WARNNG</strong> - This class is public solely to allow it to be
+ * used from subpackages of <code>org.apache.commons.digester</code>.
+ * It should not be considered part of the public API of Commons Digester.
+ * If you want to use such a class yourself, you should use the one from
+ * Commons Collections directly.</p>
+ *
+ * <p>An implementation of the {@link java.util.Stack} API that is based on an
+ * <code>ArrayList</code> instead of a <code>Vector</code>, so it is not
+ * synchronized to protect against multi-threaded access.  The implementation
+ * is therefore operates faster in environments where you do not need to
+ * worry about multiple thread contention.</p>
+ *
+ * <p>Unlike <code>Stack</code>, <code>ArrayStack</code> accepts null entries.
+ * </p>
+ *
+ * @see java.util.Stack
+ * @since Digester 1.6 (from Commons Collections 1.0)
+ */
+public class ArrayStack<E> extends ArrayList<E> {
+
+    /** Ensure serialization compatibility */    
+    private static final long serialVersionUID = 2130079159931574599L;
+
+    /**
+     * Constructs a new empty <code>ArrayStack</code>. The initial size
+     * is controlled by <code>ArrayList</code> and is currently 10.
+     */
+    public ArrayStack() {
+        super();
+    }
+
+    /**
+     * Constructs a new empty <code>ArrayStack</code> with an initial size.
+     * 
+     * @param initialSize  the initial size to use
+     * @throws IllegalArgumentException  if the specified initial size
+     *  is negative
+     */
+    public ArrayStack(int initialSize) {
+        super(initialSize);
+    }
+
+    /**
+     * Return <code>true</code> if this stack is currently empty.
+     * <p>
+     * This method exists for compatibility with <code>java.util.Stack</code>.
+     * New users of this class should use <code>isEmpty</code> instead.
+     * 
+     * @return true if the stack is currently empty
+     */
+    public boolean empty() {
+        return isEmpty();
+    }
+
+    /**
+     * Returns the top item off of this stack without removing it.
+     *
+     * @return the top item on the stack
+     * @throws EmptyStackException  if the stack is empty
+     */
+    public E peek() throws EmptyStackException {
+        int n = size();
+        if (n <= 0) {
+            throw new EmptyStackException();
+        } else {
+            return get(n - 1);
+        }
+    }
+
+    /**
+     * Returns the n'th item down (zero-relative) from the top of this
+     * stack without removing it.
+     *
+     * @param n  the number of items down to go
+     * @return the n'th item on the stack, zero relative
+     * @throws EmptyStackException  if there are not enough items on the
+     *  stack to satisfy this request
+     */
+    public E peek(int n) throws EmptyStackException {
+        int m = (size() - n) - 1;
+        if (m < 0) {
+            throw new EmptyStackException();
+        } else {
+            return get(m);
+        }
+    }
+
+    /**
+     * Pops the top item off of this stack and return it.
+     *
+     * @return the top item on the stack
+     * @throws EmptyStackException  if the stack is empty
+     */
+    public E pop() throws EmptyStackException {
+        int n = size();
+        if (n <= 0) {
+            throw new EmptyStackException();
+        } else {
+            return remove(n - 1);
+        }
+    }
+
+    /**
+     * Pushes a new item onto the top of this stack. The pushed item is also
+     * returned. This is equivalent to calling <code>add</code>.
+     *
+     * @param item  the item to be added
+     * @return the item just pushed
+     */
+    public E push(E item) {
+        add(item);
+        return item;
+    }
+
+
+    /**
+     * Returns the one-based position of the distance from the top that the
+     * specified object exists on this stack, where the top-most element is
+     * considered to be at distance <code>1</code>.  If the object is not
+     * present on the stack, return <code>-1</code> instead.  The
+     * <code>equals()</code> method is used to compare to the items
+     * in this stack.
+     *
+     * @param object  the object to be searched for
+     * @return the 1-based depth into the stack of the object, or -1 if not found
+     */
+    public int search(E object) {
+        int i = size() - 1;        // Current index
+        int n = 1;                 // Current distance
+        while (i >= 0) {
+            Object current = get(i);
+            if ((object == null && current == null) ||
+                (object != null && object.equals(current))) {
+                return n;
+            }
+            i--;
+            n++;
+        }
+        return -1;
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/CallMethodRule.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/CallMethodRule.java
new file mode 100644
index 0000000..42123da
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/CallMethodRule.java
@@ -0,0 +1,585 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.digester;
+
+
+import org.apache.tomcat.util.IntrospectionUtils;
+import org.xml.sax.Attributes;
+
+
+/**
+ * <p>Rule implementation that calls a method on an object on the stack
+ * (normally the top/parent object), passing arguments collected from 
+ * subsequent <code>CallParamRule</code> rules or from the body of this
+ * element. </p>
+ *
+ * <p>By using {@link #CallMethodRule(String methodName)} 
+ * a method call can be made to a method which accepts no
+ * arguments.</p>
+ *
+ * <p>Incompatible method parameter types are converted 
+ * using <code>org.apache.commons.beanutils.ConvertUtils</code>.
+ * </p>
+ *
+ * <p>This rule now uses
+ * <a href="http://commons.apache.org/beanutils/apidocs/org/apache/commons/beanutils/MethodUtils.html">
+ * org.apache.commons.beanutils.MethodUtils#invokeMethod
+ * </a> by default.
+ * This increases the kinds of methods successfully and allows primitives
+ * to be matched by passing in wrapper classes.
+ * There are rare cases when org.apache.commons.beanutils.MethodUtils#invokeExactMethod 
+ * (the old default) is required.
+ * This method is much stricter in its reflection.
+ * Setting the <code>UseExactMatch</code> to true reverts to the use of this 
+ * method.</p>
+ *
+ * <p>Note that the target method is invoked when the  <i>end</i> of
+ * the tag the CallMethodRule fired on is encountered, <i>not</i> when the
+ * last parameter becomes available. This implies that rules which fire on
+ * tags nested within the one associated with the CallMethodRule will 
+ * fire before the CallMethodRule invokes the target method. This behaviour is
+ * not configurable. </p>
+ *
+ * <p>Note also that if a CallMethodRule is expecting exactly one parameter
+ * and that parameter is not available (eg CallParamRule is used with an
+ * attribute name but the attribute does not exist) then the method will
+ * not be invoked. If a CallMethodRule is expecting more than one parameter,
+ * then it is always invoked, regardless of whether the parameters were
+ * available or not (missing parameters are passed as null values).</p>
+ */
+
+public class CallMethodRule extends Rule {
+
+    // ----------------------------------------------------------- Constructors
+
+    /**
+     * Construct a "call method" rule with the specified method name.  The
+     * parameter types (if any) default to java.lang.String.
+     *
+     * @param methodName Method name of the parent method to call
+     * @param paramCount The number of parameters to collect, or
+     *  zero for a single argument from the body of this element.
+     */
+    public CallMethodRule(String methodName,
+                          int paramCount) {
+        this(0, methodName, paramCount);
+    }
+
+    /**
+     * Construct a "call method" rule with the specified method name.  The
+     * parameter types (if any) default to java.lang.String.
+     *
+     * @param targetOffset location of the target object. Positive numbers are
+     * relative to the top of the digester object stack. Negative numbers 
+     * are relative to the bottom of the stack. Zero implies the top
+     * object on the stack.
+     * @param methodName Method name of the parent method to call
+     * @param paramCount The number of parameters to collect, or
+     *  zero for a single argument from the body of this element.
+     */
+    public CallMethodRule(int targetOffset,
+                          String methodName,
+                          int paramCount) {
+
+        this.targetOffset = targetOffset;
+        this.methodName = methodName;
+        this.paramCount = paramCount;        
+        if (paramCount == 0) {
+            this.paramTypes = new Class[] { String.class };
+        } else {
+            this.paramTypes = new Class[paramCount];
+            for (int i = 0; i < this.paramTypes.length; i++) {
+                this.paramTypes[i] = String.class;
+            }
+        }
+
+    }
+
+    /**
+     * Construct a "call method" rule with the specified method name.  
+     * The method should accept no parameters.
+     *
+     * @param methodName Method name of the parent method to call
+     */
+    public CallMethodRule(String methodName) {
+    
+        this(0, methodName, 0, (Class[]) null);
+    
+    }
+    
+
+    /**
+     * Construct a "call method" rule with the specified method name.  
+     * The method should accept no parameters.
+     *
+     * @param targetOffset location of the target object. Positive numbers are
+     * relative to the top of the digester object stack. Negative numbers 
+     * are relative to the bottom of the stack. Zero implies the top
+     * object on the stack.
+     * @param methodName Method name of the parent method to call
+     */
+    public CallMethodRule(int targetOffset, String methodName) {
+    
+        this(targetOffset, methodName, 0, (Class[]) null);
+    
+    }
+    
+
+    /**
+     * Construct a "call method" rule with the specified method name and
+     * parameter types. If <code>paramCount</code> is set to zero the rule
+     * will use the body of this element as the single argument of the
+     * method, unless <code>paramTypes</code> is null or empty, in this
+     * case the rule will call the specified method with no arguments.
+     *
+     * @param methodName Method name of the parent method to call
+     * @param paramCount The number of parameters to collect, or
+     *  zero for a single argument from the body of this element
+     * @param paramTypes The Java class names of the arguments
+     *  (if you wish to use a primitive type, specify the corresponding
+     *  Java wrapper class instead, such as <code>java.lang.Boolean</code>
+     *  for a <code>boolean</code> parameter)
+     */
+    public CallMethodRule(
+                            String methodName,
+                            int paramCount, 
+                            String paramTypes[]) {
+        this(0, methodName, paramCount, paramTypes);
+    }
+
+    /**
+     * Construct a "call method" rule with the specified method name and
+     * parameter types. If <code>paramCount</code> is set to zero the rule
+     * will use the body of this element as the single argument of the
+     * method, unless <code>paramTypes</code> is null or empty, in this
+     * case the rule will call the specified method with no arguments.
+     *
+     * @param targetOffset location of the target object. Positive numbers are
+     * relative to the top of the digester object stack. Negative numbers 
+     * are relative to the bottom of the stack. Zero implies the top
+     * object on the stack.
+     * @param methodName Method name of the parent method to call
+     * @param paramCount The number of parameters to collect, or
+     *  zero for a single argument from the body of this element
+     * @param paramTypes The Java class names of the arguments
+     *  (if you wish to use a primitive type, specify the corresponding
+     *  Java wrapper class instead, such as <code>java.lang.Boolean</code>
+     *  for a <code>boolean</code> parameter)
+     */
+    public CallMethodRule(  int targetOffset,
+                            String methodName,
+                            int paramCount, 
+                            String paramTypes[]) {
+
+        this.targetOffset = targetOffset;
+        this.methodName = methodName;
+        this.paramCount = paramCount;
+        if (paramTypes == null) {
+            this.paramTypes = new Class[paramCount];
+            for (int i = 0; i < this.paramTypes.length; i++) {
+                this.paramTypes[i] = "abc".getClass();
+            }
+        } else {
+            // copy the parameter class names into an array
+            // the classes will be loaded when the digester is set 
+            this.paramClassNames = new String[paramTypes.length];
+            for (int i = 0; i < this.paramClassNames.length; i++) {
+                this.paramClassNames[i] = paramTypes[i];
+            }
+        }
+
+    }
+
+
+    /**
+     * Construct a "call method" rule with the specified method name and
+     * parameter types. If <code>paramCount</code> is set to zero the rule
+     * will use the body of this element as the single argument of the
+     * method, unless <code>paramTypes</code> is null or empty, in this
+     * case the rule will call the specified method with no arguments.
+     *
+     * @param methodName Method name of the parent method to call
+     * @param paramCount The number of parameters to collect, or
+     *  zero for a single argument from the body of this element
+     * @param paramTypes The Java classes that represent the
+     *  parameter types of the method arguments
+     *  (if you wish to use a primitive type, specify the corresponding
+     *  Java wrapper class instead, such as <code>java.lang.Boolean.TYPE</code>
+     *  for a <code>boolean</code> parameter)
+     */
+    public CallMethodRule(
+                            String methodName,
+                            int paramCount, 
+                            Class<?> paramTypes[]) {
+        this(0, methodName, paramCount, paramTypes);
+    }
+
+    /**
+     * Construct a "call method" rule with the specified method name and
+     * parameter types. If <code>paramCount</code> is set to zero the rule
+     * will use the body of this element as the single argument of the
+     * method, unless <code>paramTypes</code> is null or empty, in this
+     * case the rule will call the specified method with no arguments.
+     *
+     * @param targetOffset location of the target object. Positive numbers are
+     * relative to the top of the digester object stack. Negative numbers 
+     * are relative to the bottom of the stack. Zero implies the top
+     * object on the stack.
+     * @param methodName Method name of the parent method to call
+     * @param paramCount The number of parameters to collect, or
+     *  zero for a single argument from the body of this element
+     * @param paramTypes The Java classes that represent the
+     *  parameter types of the method arguments
+     *  (if you wish to use a primitive type, specify the corresponding
+     *  Java wrapper class instead, such as <code>java.lang.Boolean.TYPE</code>
+     *  for a <code>boolean</code> parameter)
+     */
+    public CallMethodRule(  int targetOffset,
+                            String methodName,
+                            int paramCount, 
+                            Class<?> paramTypes[]) {
+
+        this.targetOffset = targetOffset;
+        this.methodName = methodName;
+        this.paramCount = paramCount;
+        if (paramTypes == null) {
+            this.paramTypes = new Class[paramCount];
+            for (int i = 0; i < this.paramTypes.length; i++) {
+                this.paramTypes[i] = "abc".getClass();
+            }
+        } else {
+            this.paramTypes = new Class[paramTypes.length];
+            for (int i = 0; i < this.paramTypes.length; i++) {
+                this.paramTypes[i] = paramTypes[i];
+            }
+        }
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The body text collected from this element.
+     */
+    protected String bodyText = null;
+
+
+    /** 
+     * location of the target object for the call, relative to the
+     * top of the digester object stack. The default value of zero
+     * means the target object is the one on top of the stack.
+     */
+    protected int targetOffset = 0;
+
+    /**
+     * The method name to call on the parent object.
+     */
+    protected String methodName = null;
+
+
+    /**
+     * The number of parameters to collect from <code>MethodParam</code> rules.
+     * If this value is zero, a single parameter will be collected from the
+     * body of this element.
+     */
+    protected int paramCount = 0;
+
+
+    /**
+     * The parameter types of the parameters to be collected.
+     */
+    protected Class<?> paramTypes[] = null;
+
+    /**
+     * The names of the classes of the parameters to be collected.
+     * This attribute allows creation of the classes to be postponed until the digester is set.
+     */
+    protected String paramClassNames[] = null;
+    
+    /**
+     * Should <code>MethodUtils.invokeExactMethod</code> be used for reflection.
+     */
+    protected boolean useExactMatch = false;
+    
+    // --------------------------------------------------------- Public Methods
+    
+    /**
+     * Should <code>MethodUtils.invokeExactMethod</code>
+     * be used for the reflection.
+     */
+    public boolean getUseExactMatch() {
+        return useExactMatch;
+    }
+    
+    /**
+     * Set whether <code>MethodUtils.invokeExactMethod</code>
+     * should be used for the reflection.
+     */    
+    public void setUseExactMatch(boolean useExactMatch)
+    { 
+        this.useExactMatch = useExactMatch;
+    }
+
+    /**
+     * Set the associated digester.
+     * If needed, this class loads the parameter classes from their names.
+     */
+    @Override
+    public void setDigester(Digester digester)
+    {
+        // call superclass
+        super.setDigester(digester);
+        // if necessary, load parameter classes
+        if (this.paramClassNames != null) {
+            this.paramTypes = new Class[paramClassNames.length];
+            for (int i = 0; i < this.paramClassNames.length; i++) {
+                try {
+                    this.paramTypes[i] =
+                            digester.getClassLoader().loadClass(this.paramClassNames[i]);
+                } catch (ClassNotFoundException e) {
+                    // use the digester log
+                    digester.getLogger().error("(CallMethodRule) Cannot load class " + this.paramClassNames[i], e);
+                    this.paramTypes[i] = null; // Will cause NPE later
+                }
+            }
+        }
+    }
+
+    /**
+     * Process the start of this element.
+     *
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the parser is not namespace aware or the element has
+     *   no namespace
+     * @param name the local name if the parser is namespace aware, or just 
+     *   the element name otherwise
+     * @param attributes The attribute list for this element
+     */
+    @Override
+    public void begin(String namespace, String name, Attributes attributes)
+            throws Exception {
+
+        // Push an array to capture the parameter values if necessary
+        if (paramCount > 0) {
+            Object parameters[] = new Object[paramCount];
+            for (int i = 0; i < parameters.length; i++) {
+                parameters[i] = null;
+            }
+            digester.pushParams(parameters);
+        }
+
+    }
+
+
+    /**
+     * Process the body text of this element.
+     *
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the parser is not namespace aware or the element has
+     *   no namespace
+     * @param name the local name if the parser is namespace aware, or just 
+     *   the element name otherwise
+     * @param bodyText The body text of this element
+     */
+    @Override
+    public void body(String namespace, String name, String bodyText)
+            throws Exception {
+
+        if (paramCount == 0) {
+            this.bodyText = bodyText.trim();
+        }
+
+    }
+
+
+    /**
+     * Process the end of this element.
+     * 
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the parser is not namespace aware or the element has
+     *   no namespace
+     * @param name the local name if the parser is namespace aware, or just 
+     *   the element name otherwise
+     */
+    @Override
+    public void end(String namespace, String name) throws Exception {
+
+        // Retrieve or construct the parameter values array
+        Object parameters[] = null;
+        if (paramCount > 0) {
+
+            parameters = (Object[]) digester.popParams();
+            
+            if (digester.log.isTraceEnabled()) {
+                for (int i=0,size=parameters.length;i<size;i++) {
+                    digester.log.trace("[CallMethodRule](" + i + ")" + parameters[i]) ;
+                }
+            }
+            
+            // In the case where the parameter for the method
+            // is taken from an attribute, and that attribute
+            // isn't actually defined in the source XML file,
+            // skip the method call
+            if (paramCount == 1 && parameters[0] == null) {
+                return;
+            }
+
+        } else if (paramTypes != null && paramTypes.length != 0) {
+
+            // In the case where the parameter for the method
+            // is taken from the body text, but there is no
+            // body text included in the source XML file,
+            // skip the method call
+            if (bodyText == null) {
+                return;
+            }
+
+            parameters = new Object[1];
+            parameters[0] = bodyText;
+            if (paramTypes.length == 0) {
+                paramTypes = new Class[1];
+                paramTypes[0] = "abc".getClass();
+            }
+
+        }
+
+        // Construct the parameter values array we will need
+        // We only do the conversion if the param value is a String and
+        // the specified paramType is not String. 
+        Object paramValues[] = new Object[paramTypes.length];
+        for (int i = 0; i < paramTypes.length; i++) {
+            // convert nulls and convert stringy parameters 
+            // for non-stringy param types
+            if(
+                parameters[i] == null ||
+                 (parameters[i] instanceof String && 
+                   !String.class.isAssignableFrom(paramTypes[i]))) {
+                
+                paramValues[i] =
+                        IntrospectionUtils.convert((String) parameters[i], paramTypes[i]);
+            } else {
+                paramValues[i] = parameters[i];
+            }
+        }
+
+        // Determine the target object for the method call
+        Object target;
+        if (targetOffset >= 0) {
+            target = digester.peek(targetOffset);
+        } else {
+            target = digester.peek( digester.getCount() + targetOffset );
+        }
+        
+        if (target == null) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("[CallMethodRule]{");
+            sb.append(digester.match);
+            sb.append("} Call target is null (");
+            sb.append("targetOffset=");
+            sb.append(targetOffset);
+            sb.append(",stackdepth=");
+            sb.append(digester.getCount());
+            sb.append(")");
+            throw new org.xml.sax.SAXException(sb.toString());
+        }
+        
+        // Invoke the required method on the top object
+        if (digester.log.isDebugEnabled()) {
+            StringBuilder sb = new StringBuilder("[CallMethodRule]{");
+            sb.append(digester.match);
+            sb.append("} Call ");
+            sb.append(target.getClass().getName());
+            sb.append(".");
+            sb.append(methodName);
+            sb.append("(");
+            for (int i = 0; i < paramValues.length; i++) {
+                if (i > 0) {
+                    sb.append(",");
+                }
+                if (paramValues[i] == null) {
+                    sb.append("null");
+                } else {
+                    sb.append(paramValues[i].toString());
+                }
+                sb.append("/");
+                if (paramTypes[i] == null) {
+                    sb.append("null");
+                } else {
+                    sb.append(paramTypes[i].getName());
+                }
+            }
+            sb.append(")");
+            digester.log.debug(sb.toString());
+        }
+        Object result = IntrospectionUtils.callMethodN(target, methodName,
+                paramValues, paramTypes);   
+        processMethodCallResult(result);
+    }
+
+
+    /**
+     * Clean up after parsing is complete.
+     */
+    @Override
+    public void finish() throws Exception {
+
+        bodyText = null;
+
+    }
+
+    /**
+     * Subclasses may override this method to perform additional processing of the 
+     * invoked method's result.
+     *
+     * @param result the Object returned by the method invoked, possibly null
+     */
+    protected void processMethodCallResult(Object result) {
+        // do nothing
+    }
+
+    /**
+     * Render a printable version of this Rule.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("CallMethodRule[");
+        sb.append("methodName=");
+        sb.append(methodName);
+        sb.append(", paramCount=");
+        sb.append(paramCount);
+        sb.append(", paramTypes={");
+        if (paramTypes != null) {
+            for (int i = 0; i < paramTypes.length; i++) {
+                if (i > 0) {
+                    sb.append(", ");
+                }
+                sb.append(paramTypes[i].getName());
+            }
+        }
+        sb.append("}");
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/CallParamRule.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/CallParamRule.java
new file mode 100644
index 0000000..ec19fd1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/CallParamRule.java
@@ -0,0 +1,242 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.digester;
+
+
+import org.xml.sax.Attributes;
+
+
+/**
+ * <p>Rule implementation that saves a parameter for use by a surrounding 
+ * <code>CallMethodRule<code>.</p>
+ *
+ * <p>This parameter may be:
+ * <ul>
+ * <li>from an attribute of the current element
+ * See {@link #CallParamRule(int paramIndex, String attributeName)}
+ * <li>from current the element body
+ * See {@link #CallParamRule(int paramIndex)}
+ * <li>from the top object on the stack. 
+ * See {@link #CallParamRule(int paramIndex, boolean fromStack)}
+ * <li>the current path being processed (separate <code>Rule</code>). 
+ * See {@link PathCallParamRule}
+ * </ul>
+ * </p>
+ */
+
+public class CallParamRule extends Rule {
+
+    // ----------------------------------------------------------- Constructors
+
+    /**
+     * Construct a "call parameter" rule that will save the body text of this
+     * element as the parameter value.
+     *
+     * @param paramIndex The zero-relative parameter number
+     */
+    public CallParamRule(int paramIndex) {
+
+        this(paramIndex, null);
+
+    }
+
+
+    /**
+     * Construct a "call parameter" rule that will save the value of the
+     * specified attribute as the parameter value.
+     *
+     * @param paramIndex The zero-relative parameter number
+     * @param attributeName The name of the attribute to save
+     */
+    public CallParamRule(int paramIndex,
+                         String attributeName) {
+
+        this.paramIndex = paramIndex;
+        this.attributeName = attributeName;
+
+    }
+
+
+    /**
+     * Construct a "call parameter" rule.
+     *
+     * @param paramIndex The zero-relative parameter number
+     * @param fromStack should this parameter be taken from the top of the stack?
+     */    
+    public CallParamRule(int paramIndex, boolean fromStack) {
+    
+        this.paramIndex = paramIndex;  
+        this.fromStack = fromStack;
+
+    }
+    
+    /**
+     * Constructs a "call parameter" rule which sets a parameter from the stack.
+     * If the stack contains too few objects, then the parameter will be set to null.
+     *
+     * @param paramIndex The zero-relative parameter number
+     * @param stackIndex the index of the object which will be passed as a parameter. 
+     * The zeroth object is the top of the stack, 1 is the next object down and so on.
+     */    
+    public CallParamRule(int paramIndex, int stackIndex) {
+    
+        this.paramIndex = paramIndex;  
+        this.fromStack = true;
+        this.stackIndex = stackIndex;
+    }
+ 
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The attribute from which to save the parameter value
+     */
+    protected String attributeName = null;
+
+
+    /**
+     * The zero-relative index of the parameter we are saving.
+     */
+    protected int paramIndex = 0;
+
+
+    /**
+     * Is the parameter to be set from the stack?
+     */
+    protected boolean fromStack = false;
+    
+    /**
+     * The position of the object from the top of the stack
+     */
+    protected int stackIndex = 0;
+
+    /** 
+     * Stack is used to allow nested body text to be processed.
+     * Lazy creation.
+     */
+    protected ArrayStack<String> bodyTextStack;
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Process the start of this element.
+     *
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the parser is not namespace aware or the element has
+     *   no namespace
+     * @param name the local name if the parser is namespace aware, or just 
+     *   the element name otherwise
+     * @param attributes The attribute list for this element
+     */
+    @Override
+    public void begin(String namespace, String name, Attributes attributes)
+            throws Exception {
+
+        Object param = null;
+        
+        if (attributeName != null) {
+        
+            param = attributes.getValue(attributeName);
+            
+        } else if(fromStack) {
+        
+            param = digester.peek(stackIndex);
+            
+            if (digester.log.isDebugEnabled()) {
+            
+                StringBuilder sb = new StringBuilder("[CallParamRule]{");
+                sb.append(digester.match);
+                sb.append("} Save from stack; from stack?").append(fromStack);
+                sb.append("; object=").append(param);
+                digester.log.debug(sb.toString());
+            }   
+        }
+        
+        // Have to save the param object to the param stack frame here.
+        // Can't wait until end(). Otherwise, the object will be lost.
+        // We can't save the object as instance variables, as 
+        // the instance variables will be overwritten
+        // if this CallParamRule is reused in subsequent nesting.
+        
+        if(param != null) {
+            Object parameters[] = (Object[]) digester.peekParams();
+            parameters[paramIndex] = param;
+        }
+    }
+
+
+    /**
+     * Process the body text of this element.
+     *
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the parser is not namespace aware or the element has
+     *   no namespace
+     * @param name the local name if the parser is namespace aware, or just 
+     *   the element name otherwise
+     * @param bodyText The body text of this element
+     */
+    @Override
+    public void body(String namespace, String name, String bodyText)
+            throws Exception {
+
+        if (attributeName == null && !fromStack) {
+            // We must wait to set the parameter until end
+            // so that we can make sure that the right set of parameters
+            // is at the top of the stack
+            if (bodyTextStack == null) {
+                bodyTextStack = new ArrayStack<String>();
+            }
+            bodyTextStack.push(bodyText.trim());
+        }
+
+    }
+    
+    /**
+     * Process any body texts now.
+     */
+    @Override
+    public void end(String namespace, String name) {
+        if (bodyTextStack != null && !bodyTextStack.empty()) {
+            // what we do now is push one parameter onto the top set of parameters
+            Object parameters[] = (Object[]) digester.peekParams();
+            parameters[paramIndex] = bodyTextStack.pop();
+        }
+    }
+
+    /**
+     * Render a printable version of this Rule.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("CallParamRule[");
+        sb.append("paramIndex=");
+        sb.append(paramIndex);
+        sb.append(", attributeName=");
+        sb.append(attributeName);
+        sb.append(", from stack=");
+        sb.append(fromStack);
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/Digester.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/Digester.java
new file mode 100644
index 0000000..46f870c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/Digester.java
@@ -0,0 +1,2786 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.digester;
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.reflect.InvocationTargetException;
+import java.util.EmptyStackException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+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.xml.sax.Attributes;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.DefaultHandler;
+
+
+/**
+ * <p>A <strong>Digester</strong> processes an XML input stream by matching a
+ * series of element nesting patterns to execute Rules that have been added
+ * prior to the start of parsing.  This package was inspired by the
+ * <code>XmlMapper</code> class that was part of Tomcat 3.0 and 3.1,
+ * but is organized somewhat differently.</p>
+ *
+ * <p>See the <a href="package-summary.html#package_description">Digester
+ * Developer Guide</a> for more information.</p>
+ *
+ * <p><strong>IMPLEMENTATION NOTE</strong> - A single Digester instance may
+ * only be used within the context of a single thread at a time, and a call
+ * to <code>parse()</code> must be completed before another can be initiated
+ * even from the same thread.</p>
+ *
+ * <p><strong>IMPLEMENTATION NOTE</strong> - A bug in Xerces 2.0.2 prevents
+ * the support of XML schema. You need Xerces 2.1/2.3 and up to make
+ * this class working with XML schema</p>
+ */
+
+public class Digester extends DefaultHandler {
+
+    
+    // ---------------------------------------------------------- Static Fields    
+    private static class SystemPropertySource 
+        implements IntrospectionUtils.PropertySource {
+        @Override
+        public String getProperty( String key ) {
+            return System.getProperty(key);
+        }
+    }
+
+    protected static IntrospectionUtils.PropertySource source[] = 
+        new IntrospectionUtils.PropertySource[] { new SystemPropertySource() };
+    
+    static {
+        String className = System.getProperty("org.apache.tomcat.util.digester.PROPERTY_SOURCE");
+        if (className!=null) {
+            IntrospectionUtils.PropertySource[] sources = new IntrospectionUtils.PropertySource[2];
+            sources[1] = source[0];
+            ClassLoader[] cls = new ClassLoader[] {Digester.class.getClassLoader(),Thread.currentThread().getContextClassLoader()};
+            boolean initialized = false;
+            for (int i=0; i<cls.length && (!initialized); i++) {
+                try {
+                    Class<?> clazz = Class.forName(className,true,cls[i]);
+                    IntrospectionUtils.PropertySource src = (IntrospectionUtils.PropertySource)clazz.newInstance();
+                    sources[0] = src;
+                    initialized = true;
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    LogFactory.getLog("org.apache.commons.digester.Digester").
+                        error("Unable to load property source["+className+"].",t);
+                }
+            }
+            if (initialized) source = sources;
+        }
+    }
+
+
+    // --------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new Digester with default properties.
+     */
+    public Digester() {
+
+        super();
+
+    }
+
+
+    /**
+     * Construct a new Digester, allowing a SAXParser to be passed in.  This
+     * allows Digester to be used in environments which are unfriendly to
+     * JAXP1.1 (such as WebLogic 6.0).  Thanks for the request to change go to
+     * James House (james@interobjective.com).  This may help in places where
+     * you are able to load JAXP 1.1 classes yourself.
+     */
+    public Digester(SAXParser parser) {
+
+        super();
+
+        this.parser = parser;
+
+    }
+
+
+    /**
+     * Construct a new Digester, allowing an XMLReader to be passed in.  This
+     * allows Digester to be used in environments which are unfriendly to
+     * JAXP1.1 (such as WebLogic 6.0).  Note that if you use this option you
+     * have to configure namespace and validation support yourself, as these
+     * properties only affect the SAXParser and empty constructor.
+     */
+    public Digester(XMLReader reader) {
+
+        super();
+
+        this.reader = reader;
+
+    }
+
+
+    // --------------------------------------------------- Instance Variables
+
+
+    /**
+     * The body text of the current element.
+     */
+    protected StringBuilder bodyText = new StringBuilder();
+
+
+    /**
+     * The stack of body text string buffers for surrounding elements.
+     */
+    protected ArrayStack<StringBuilder> bodyTexts =
+        new ArrayStack<StringBuilder>();
+
+
+    /**
+     * Stack whose elements are List objects, each containing a list of
+     * Rule objects as returned from Rules.getMatch(). As each xml element
+     * in the input is entered, the matching rules are pushed onto this
+     * stack. After the end tag is reached, the matches are popped again.
+     * The depth of is stack is therefore exactly the same as the current
+     * "nesting" level of the input xml. 
+     *
+     * @since 1.6
+     */
+    protected ArrayStack<List<Rule>> matches = new ArrayStack<List<Rule>>(10);
+    
+    /**
+     * The class loader to use for instantiating application objects.
+     * If not specified, the context class loader, or the class loader
+     * used to load Digester itself, is used, based on the value of the
+     * <code>useContextClassLoader</code> variable.
+     */
+    protected ClassLoader classLoader = null;
+
+
+    /**
+     * Has this Digester been configured yet.
+     */
+    protected boolean configured = false;
+
+
+    /**
+     * The EntityResolver used by the SAX parser. By default it use this class
+     */
+    protected EntityResolver entityResolver;
+    
+    /**
+     * The URLs of entityValidator that have been registered, keyed by the public
+     * identifier that corresponds.
+     */
+    protected HashMap<String,String> entityValidator =
+        new HashMap<String,String>();
+
+
+    /**
+     * The application-supplied error handler that is notified when parsing
+     * warnings, errors, or fatal errors occur.
+     */
+    protected ErrorHandler errorHandler = null;
+
+
+    /**
+     * The SAXParserFactory that is created the first time we need it.
+     */
+    protected SAXParserFactory factory = null;
+
+    /**
+     * The Locator associated with our parser.
+     */
+    protected Locator locator = null;
+
+
+    /**
+     * The current match pattern for nested element processing.
+     */
+    protected String match = "";
+
+
+    /**
+     * Do we want a "namespace aware" parser.
+     */
+    protected boolean namespaceAware = false;
+
+
+    /**
+     * Registered namespaces we are currently processing.  The key is the
+     * namespace prefix that was declared in the document.  The value is an
+     * ArrayStack of the namespace URIs this prefix has been mapped to --
+     * the top Stack element is the most current one.  (This architecture
+     * is required because documents can declare nested uses of the same
+     * prefix for different Namespace URIs).
+     */
+    protected HashMap<String,ArrayStack<String>> namespaces =
+        new HashMap<String,ArrayStack<String>>();
+
+
+    /**
+     * The parameters stack being utilized by CallMethodRule and
+     * CallParamRule rules.
+     */
+    protected ArrayStack<Object> params = new ArrayStack<Object>();
+
+
+    /**
+     * The SAXParser we will use to parse the input stream.
+     */
+    protected SAXParser parser = null;
+
+
+    /**
+     * The public identifier of the DTD we are currently parsing under
+     * (if any).
+     */
+    protected String publicId = null;
+
+
+    /**
+     * The XMLReader used to parse digester rules.
+     */
+    protected XMLReader reader = null;
+
+
+    /**
+     * The "root" element of the stack (in other words, the last object
+     * that was popped.
+     */
+    protected Object root = null;
+
+
+    /**
+     * The <code>Rules</code> implementation containing our collection of
+     * <code>Rule</code> instances and associated matching policy.  If not
+     * established before the first rule is added, a default implementation
+     * will be provided.
+     */
+    protected Rules rules = null;
+
+    /**
+     * The object stack being constructed.
+     */
+    protected ArrayStack<Object> stack = new ArrayStack<Object>();
+
+
+    /**
+     * Do we want to use the Context ClassLoader when loading classes
+     * for instantiating new objects.  Default is <code>false</code>.
+     */
+    protected boolean useContextClassLoader = false;
+
+
+    /**
+     * Do we want to use a validating parser.
+     */
+    protected boolean validating = false;
+
+
+    /**
+     * Warn on missing attributes and elements.
+     */
+    protected boolean rulesValidation = false;
+
+    
+    /**
+     * Fake attributes map (attributes are often used for object creation).
+     */
+    protected Map<Class<?>, List<String>> fakeAttributes = null;
+
+
+    /**
+     * The Log to which most logging calls will be made.
+     */
+    protected Log log =
+        LogFactory.getLog("org.apache.commons.digester.Digester");
+
+
+    /**
+     * The Log to which all SAX event related logging calls will be made.
+     */
+    protected Log saxLog =
+        LogFactory.getLog("org.apache.commons.digester.Digester.sax");
+    
+        
+    /**
+     * The schema language supported. By default, we use this one.
+     */
+    protected static final String W3C_XML_SCHEMA =
+        "http://www.w3.org/2001/XMLSchema";
+    
+    /** Stacks used for interrule communication, indexed by name String */
+    private HashMap<String,ArrayStack<Object>> stacksByName =
+        new HashMap<String,ArrayStack<Object>>();
+    
+    // ------------------------------------------------------------- Properties
+
+    /**
+     * Return the currently mapped namespace URI for the specified prefix,
+     * if any; otherwise return <code>null</code>.  These mappings come and
+     * go dynamically as the document is parsed.
+     *
+     * @param prefix Prefix to look up
+     */
+    public String findNamespaceURI(String prefix) {
+        
+        ArrayStack<String> stack = namespaces.get(prefix);
+        if (stack == null) {
+            return (null);
+        }
+        try {
+            return stack.peek();
+        } catch (EmptyStackException e) {
+            return (null);
+        }
+
+    }
+
+
+    /**
+     * Return the class loader to be used for instantiating application objects
+     * when required.  This is determined based upon the following rules:
+     * <ul>
+     * <li>The class loader set by <code>setClassLoader()</code>, if any</li>
+     * <li>The thread context class loader, if it exists and the
+     *     <code>useContextClassLoader</code> property is set to true</li>
+     * <li>The class loader used to load the Digester class itself.
+     * </ul>
+     */
+    public ClassLoader getClassLoader() {
+
+        if (this.classLoader != null) {
+            return (this.classLoader);
+        }
+        if (this.useContextClassLoader) {
+            ClassLoader classLoader =
+                    Thread.currentThread().getContextClassLoader();
+            if (classLoader != null) {
+                return (classLoader);
+            }
+        }
+        return (this.getClass().getClassLoader());
+
+    }
+
+
+    /**
+     * Set the class loader to be used for instantiating application objects
+     * when required.
+     *
+     * @param classLoader The new class loader to use, or <code>null</code>
+     *  to revert to the standard rules
+     */
+    public void setClassLoader(ClassLoader classLoader) {
+
+        this.classLoader = classLoader;
+
+    }
+
+
+    /**
+     * Return the current depth of the element stack.
+     */
+    public int getCount() {
+
+        return (stack.size());
+
+    }
+
+
+    /**
+     * Return the name of the XML element that is currently being processed.
+     */
+    public String getCurrentElementName() {
+
+        String elementName = match;
+        int lastSlash = elementName.lastIndexOf('/');
+        if (lastSlash >= 0) {
+            elementName = elementName.substring(lastSlash + 1);
+        }
+        return (elementName);
+
+    }
+
+
+    /**
+     * Return the error handler for this Digester.
+     */
+    public ErrorHandler getErrorHandler() {
+
+        return (this.errorHandler);
+
+    }
+
+
+    /**
+     * Set the error handler for this Digester.
+     *
+     * @param errorHandler The new error handler
+     */
+    public void setErrorHandler(ErrorHandler errorHandler) {
+
+        this.errorHandler = errorHandler;
+
+    }
+
+
+    /**
+     * Return the SAXParserFactory we will use, creating one if necessary.
+     * @throws ParserConfigurationException 
+     * @throws SAXNotSupportedException 
+     * @throws SAXNotRecognizedException 
+     */
+    public SAXParserFactory getFactory()
+    throws SAXNotRecognizedException, SAXNotSupportedException,
+    ParserConfigurationException {
+
+        if (factory == null) {
+            factory = SAXParserFactory.newInstance();
+            factory.setNamespaceAware(namespaceAware);
+            factory.setValidating(validating);
+            if (validating) {
+                // Enable DTD validation
+                factory.setFeature(
+                        "http://xml.org/sax/features/validation",
+                        true);
+                // Enable schema validation
+                factory.setFeature(
+                        "http://apache.org/xml/features/validation/schema",
+                        true);
+            }
+        }
+        return (factory);
+
+    }
+
+
+    /**
+     * Returns a flag indicating whether the requested feature is supported
+     * by the underlying implementation of <code>org.xml.sax.XMLReader</code>.
+     * See <a href="http://www.saxproject.org/apidoc/xml/sax/package-summary.html#package-description"
+     * http://www.saxproject.org/apidoc/xml/sax/package-summary.html#package-description</a>
+     * for information about the standard SAX2 feature flags.
+     *
+     * @param feature Name of the feature to inquire about
+     *
+     * @exception ParserConfigurationException if a parser configuration error
+     *  occurs
+     * @exception SAXNotRecognizedException if the property name is
+     *  not recognized
+     * @exception SAXNotSupportedException if the property name is
+     *  recognized but not supported
+     */
+    public boolean getFeature(String feature)
+        throws ParserConfigurationException, SAXNotRecognizedException,
+        SAXNotSupportedException {
+
+        return (getFactory().getFeature(feature));
+
+    }
+
+
+    /**
+     * Sets a flag indicating whether the requested feature is supported
+     * by the underlying implementation of <code>org.xml.sax.XMLReader</code>.
+     * See <a href="http://www.saxproject.org/apidoc/xml/sax/package-summary.html#package-description"
+     * http://www.saxproject.org/apidoc/xml/sax/package-summary.html#package-description</a>
+     * for information about the standard SAX2 feature flags.  In order to be
+     * effective, this method must be called <strong>before</strong> the
+     * <code>getParser()</code> method is called for the first time, either
+     * directly or indirectly.
+     *
+     * @param feature Name of the feature to set the status for
+     * @param value The new value for this feature
+     *
+     * @exception ParserConfigurationException if a parser configuration error
+     *  occurs
+     * @exception SAXNotRecognizedException if the property name is
+     *  not recognized
+     * @exception SAXNotSupportedException if the property name is
+     *  recognized but not supported
+     */
+    public void setFeature(String feature, boolean value)
+        throws ParserConfigurationException, SAXNotRecognizedException,
+        SAXNotSupportedException {
+
+        getFactory().setFeature(feature, value);
+
+    }
+
+
+    /**
+     * Return the current Logger associated with this instance of the Digester
+     */
+    public Log getLogger() {
+
+        return log;
+
+    }
+
+
+    /**
+     * Set the current logger for this Digester.
+     */
+    public void setLogger(Log log) {
+
+        this.log = log;
+
+    }
+
+    /**
+     * Gets the logger used for logging SAX-related information.
+     * <strong>Note</strong> the output is finely grained.
+     *
+     * @since 1.6
+     */
+    public Log getSAXLogger() {
+        
+        return saxLog;
+    }
+    
+
+    /**
+     * Sets the logger used for logging SAX-related information.
+     * <strong>Note</strong> the output is finely grained.
+     * @param saxLog Log, not null
+     *
+     * @since 1.6
+     */    
+    public void setSAXLogger(Log saxLog) {
+    
+        this.saxLog = saxLog;
+    }
+
+    /**
+     * Return the current rule match path
+     */
+    public String getMatch() {
+
+        return match;
+
+    }
+
+
+    /**
+     * Return the "namespace aware" flag for parsers we create.
+     */
+    public boolean getNamespaceAware() {
+
+        return (this.namespaceAware);
+
+    }
+
+
+    /**
+     * Set the "namespace aware" flag for parsers we create.
+     *
+     * @param namespaceAware The new "namespace aware" flag
+     */
+    public void setNamespaceAware(boolean namespaceAware) {
+
+        this.namespaceAware = namespaceAware;
+
+    }
+
+    
+    /**
+     * Set the public id of the current file being parse.
+     * @param publicId the DTD/Schema public's id.
+     */
+    public void setPublicId(String publicId){
+        this.publicId = publicId;
+    }
+    
+    
+    /**
+     * Return the public identifier of the DTD we are currently
+     * parsing under, if any.
+     */
+    public String getPublicId() {
+
+        return (this.publicId);
+
+    }
+
+
+    /**
+     * Return the namespace URI that will be applied to all subsequently
+     * added <code>Rule</code> objects.
+     */
+    public String getRuleNamespaceURI() {
+
+        return (getRules().getNamespaceURI());
+
+    }
+
+
+    /**
+     * Set the namespace URI that will be applied to all subsequently
+     * added <code>Rule</code> objects.
+     *
+     * @param ruleNamespaceURI Namespace URI that must match on all
+     *  subsequently added rules, or <code>null</code> for matching
+     *  regardless of the current namespace URI
+     */
+    public void setRuleNamespaceURI(String ruleNamespaceURI) {
+
+        getRules().setNamespaceURI(ruleNamespaceURI);
+
+    }
+
+
+    /**
+     * Return the SAXParser we will use to parse the input stream.  If there
+     * is a problem creating the parser, return <code>null</code>.
+     */
+    public SAXParser getParser() {
+
+        // Return the parser we already created (if any)
+        if (parser != null) {
+            return (parser);
+        }
+
+        // Create a new parser
+        try {
+            parser = getFactory().newSAXParser();
+        } catch (Exception e) {
+            log.error("Digester.getParser: ", e);
+            return (null);
+        }
+
+        return (parser);
+
+    }
+
+
+    /**
+     * Return the current value of the specified property for the underlying
+     * <code>XMLReader</code> implementation.
+     * See <a href="http://www.saxproject.org/apidoc/xml/sax/package-summary.html#package-description"
+     * http://www.saxproject.org/apidoc/xml/sax/package-summary.html#package-description</a>
+     * for information about the standard SAX2 properties.
+     *
+     * @param property Property name to be retrieved
+     *
+     * @exception SAXNotRecognizedException if the property name is
+     *  not recognized
+     * @exception SAXNotSupportedException if the property name is
+     *  recognized but not supported
+     */
+    public Object getProperty(String property)
+        throws SAXNotRecognizedException, SAXNotSupportedException {
+
+        return (getParser().getProperty(property));
+
+    }
+
+
+    /**
+     * Set the current value of the specified property for the underlying
+     * <code>XMLReader</code> implementation.
+     * See <a href="http://www.saxproject.org/apidoc/xml/sax/package-summary.html#package-description"
+     * http://www.saxproject.org/apidoc/xml/sax/package-summary.html#package-description</a>
+     * for information about the standard SAX2 properties.
+     *
+     * @param property Property name to be set
+     * @param value Property value to be set
+     *
+     * @exception SAXNotRecognizedException if the property name is
+     *  not recognized
+     * @exception SAXNotSupportedException if the property name is
+     *  recognized but not supported
+     */
+    public void setProperty(String property, Object value)
+        throws SAXNotRecognizedException, SAXNotSupportedException {
+
+        getParser().setProperty(property, value);
+
+    }
+
+
+    /**
+     * Return the <code>Rules</code> implementation object containing our
+     * rules collection and associated matching policy.  If none has been
+     * established, a default implementation will be created and returned.
+     */
+    public Rules getRules() {
+
+        if (this.rules == null) {
+            this.rules = new RulesBase();
+            this.rules.setDigester(this);
+        }
+        return (this.rules);
+
+    }
+
+    
+    /**
+     * Set the <code>Rules</code> implementation object containing our
+     * rules collection and associated matching policy.
+     *
+     * @param rules New Rules implementation
+     */
+    public void setRules(Rules rules) {
+
+        this.rules = rules;
+        this.rules.setDigester(this);
+
+    }
+
+
+    /**
+     * Return the boolean as to whether the context classloader should be used.
+     */
+    public boolean getUseContextClassLoader() {
+
+        return useContextClassLoader;
+
+    }
+
+
+    /**
+     * Determine whether to use the Context ClassLoader (the one found by
+     * calling <code>Thread.currentThread().getContextClassLoader()</code>)
+     * to resolve/load classes that are defined in various rules.  If not
+     * using Context ClassLoader, then the class-loading defaults to
+     * using the calling-class' ClassLoader.
+     *
+     * @param use determines whether to use Context ClassLoader.
+     */
+    public void setUseContextClassLoader(boolean use) {
+
+        useContextClassLoader = use;
+
+    }
+
+
+    /**
+     * Return the validating parser flag.
+     */
+    public boolean getValidating() {
+
+        return (this.validating);
+
+    }
+
+
+    /**
+     * Set the validating parser flag.  This must be called before
+     * <code>parse()</code> is called the first time.
+     *
+     * @param validating The new validating parser flag.
+     */
+    public void setValidating(boolean validating) {
+
+        this.validating = validating;
+
+    }
+
+
+    /**
+     * Return the rules validation flag.
+     */
+    public boolean getRulesValidation() {
+
+        return (this.rulesValidation);
+
+    }
+
+
+    /**
+     * Set the rules validation flag.  This must be called before
+     * <code>parse()</code> is called the first time.
+     *
+     * @param rulesValidation The new rules validation flag.
+     */
+    public void setRulesValidation(boolean rulesValidation) {
+
+        this.rulesValidation = rulesValidation;
+
+    }
+
+
+    /**
+     * Return the fake attributes list.
+     */
+    public Map<Class<?>, List<String>> getFakeAttributes() {
+
+        return (this.fakeAttributes);
+
+    }
+
+
+    /**
+     * Determine if an attribute is a fake attribute.
+     */
+    public boolean isFakeAttribute(Object object, String name) {
+
+        if (fakeAttributes == null) {
+            return false;
+        }
+        List<String> result = fakeAttributes.get(object.getClass());
+        if (result == null) {
+            result = fakeAttributes.get(Object.class);
+        }
+        if (result == null) {
+            return false;
+        } else {
+            return result.contains(name);
+        }
+
+    }
+
+
+    /**
+     * Set the fake attributes.
+     *
+     * @param fakeAttributes The new fake attributes.
+     */
+    public void setFakeAttributes(Map<Class<?>, List<String>> fakeAttributes) {
+
+        this.fakeAttributes = fakeAttributes;
+
+    }
+
+
+    /**
+     * Return the XMLReader to be used for parsing the input document.
+     *
+     * FIX ME: there is a bug in JAXP/XERCES that prevent the use of a 
+     * parser that contains a schema with a DTD.
+     * @exception SAXException if no XMLReader can be instantiated
+     */
+    public XMLReader getXMLReader() throws SAXException {
+        if (reader == null){
+            reader = getParser().getXMLReader();
+        }        
+                               
+        reader.setDTDHandler(this);           
+        reader.setContentHandler(this);        
+        
+        if (entityResolver == null){
+            reader.setEntityResolver(this);
+        } else {
+            reader.setEntityResolver(entityResolver);           
+        }
+        
+        reader.setErrorHandler(this);
+        return reader;
+    }
+
+    // ------------------------------------------------- ContentHandler Methods
+
+
+    /**
+     * Process notification of character data received from the body of
+     * an XML element.
+     *
+     * @param buffer The characters from the XML document
+     * @param start Starting offset into the buffer
+     * @param length Number of characters from the buffer
+     *
+     * @exception SAXException if a parsing error is to be reported
+     */
+    @Override
+    public void characters(char buffer[], int start, int length)
+            throws SAXException {
+
+        if (saxLog.isDebugEnabled()) {
+            saxLog.debug("characters(" + new String(buffer, start, length) + ")");
+        }
+
+        bodyText.append(buffer, start, length);
+
+    }
+
+
+    /**
+     * Process notification of the end of the document being reached.
+     *
+     * @exception SAXException if a parsing error is to be reported
+     */
+    @Override
+    public void endDocument() throws SAXException {
+
+        if (saxLog.isDebugEnabled()) {
+            if (getCount() > 1) {
+                saxLog.debug("endDocument():  " + getCount() +
+                             " elements left");
+            } else {
+                saxLog.debug("endDocument()");
+            }
+        }
+
+        while (getCount() > 1) {
+            pop();
+        }
+
+        // Fire "finish" events for all defined rules
+        Iterator<Rule> rules = getRules().rules().iterator();
+        while (rules.hasNext()) {
+            Rule rule = rules.next();
+            try {
+                rule.finish();
+            } catch (Exception e) {
+                log.error("Finish event threw exception", e);
+                throw createSAXException(e);
+            } catch (Error e) {
+                log.error("Finish event threw error", e);
+                throw e;
+            }
+        }
+
+        // Perform final cleanup
+        clear();
+
+    }
+
+
+    /**
+     * Process notification of the end of an XML element being reached.
+     *
+     * @param namespaceURI - The Namespace URI, or the empty string if the
+     *   element has no Namespace URI or if Namespace processing is not
+     *   being performed.
+     * @param localName - The local name (without prefix), or the empty
+     *   string if Namespace processing is not being performed.
+     * @param qName - The qualified XML 1.0 name (with prefix), or the
+     *   empty string if qualified names are not available.
+     * @exception SAXException if a parsing error is to be reported
+     */
+    @Override
+    public void endElement(String namespaceURI, String localName,
+                           String qName) throws SAXException {
+
+        boolean debug = log.isDebugEnabled();
+
+        if (debug) {
+            if (saxLog.isDebugEnabled()) {
+                saxLog.debug("endElement(" + namespaceURI + "," + localName +
+                        "," + qName + ")");
+            }
+            log.debug("  match='" + match + "'");
+            log.debug("  bodyText='" + bodyText + "'");
+        }
+
+        // Parse system properties
+        bodyText = updateBodyText(bodyText);
+
+        // the actual element name is either in localName or qName, depending 
+        // on whether the parser is namespace aware
+        String name = localName;
+        if ((name == null) || (name.length() < 1)) {
+            name = qName;
+        }
+
+        // Fire "body" events for all relevant rules
+        List<Rule> rules = matches.pop();
+        if ((rules != null) && (rules.size() > 0)) {
+            String bodyText = this.bodyText.toString();
+            for (int i = 0; i < rules.size(); i++) {
+                try {
+                    Rule rule = rules.get(i);
+                    if (debug) {
+                        log.debug("  Fire body() for " + rule);
+                    }
+                    rule.body(namespaceURI, name, bodyText);
+                } catch (Exception e) {
+                    log.error("Body event threw exception", e);
+                    throw createSAXException(e);
+                } catch (Error e) {
+                    log.error("Body event threw error", e);
+                    throw e;
+                }
+            }
+        } else {
+            if (debug) {
+                log.debug("  No rules found matching '" + match + "'.");
+            }
+            if (rulesValidation) {
+                log.warn("  No rules found matching '" + match + "'.");
+            }
+        }
+
+        // Recover the body text from the surrounding element
+        bodyText = bodyTexts.pop();
+        if (debug) {
+            log.debug("  Popping body text '" + bodyText.toString() + "'");
+        }
+
+        // Fire "end" events for all relevant rules in reverse order
+        if (rules != null) {
+            for (int i = 0; i < rules.size(); i++) {
+                int j = (rules.size() - i) - 1;
+                try {
+                    Rule rule = rules.get(j);
+                    if (debug) {
+                        log.debug("  Fire end() for " + rule);
+                    }
+                    rule.end(namespaceURI, name);
+                } catch (Exception e) {
+                    log.error("End event threw exception", e);
+                    throw createSAXException(e);
+                } catch (Error e) {
+                    log.error("End event threw error", e);
+                    throw e;
+                }
+            }
+        }
+
+        // Recover the previous match expression
+        int slash = match.lastIndexOf('/');
+        if (slash >= 0) {
+            match = match.substring(0, slash);
+        } else {
+            match = "";
+        }
+
+    }
+
+
+    /**
+     * Process notification that a namespace prefix is going out of scope.
+     *
+     * @param prefix Prefix that is going out of scope
+     *
+     * @exception SAXException if a parsing error is to be reported
+     */
+    @Override
+    public void endPrefixMapping(String prefix) throws SAXException {
+
+        if (saxLog.isDebugEnabled()) {
+            saxLog.debug("endPrefixMapping(" + prefix + ")");
+        }
+
+        // Deregister this prefix mapping
+        ArrayStack<String> stack = namespaces.get(prefix);
+        if (stack == null) {
+            return;
+        }
+        try {
+            stack.pop();
+            if (stack.empty())
+                namespaces.remove(prefix);
+        } catch (EmptyStackException e) {
+            throw createSAXException("endPrefixMapping popped too many times");
+        }
+
+    }
+
+
+    /**
+     * Process notification of ignorable whitespace received from the body of
+     * an XML element.
+     *
+     * @param buffer The characters from the XML document
+     * @param start Starting offset into the buffer
+     * @param len Number of characters from the buffer
+     *
+     * @exception SAXException if a parsing error is to be reported
+     */
+    @Override
+    public void ignorableWhitespace(char buffer[], int start, int len)
+            throws SAXException {
+
+        if (saxLog.isDebugEnabled()) {
+            saxLog.debug("ignorableWhitespace(" +
+                    new String(buffer, start, len) + ")");
+        }
+
+        // No processing required
+
+    }
+
+
+    /**
+     * Process notification of a processing instruction that was encountered.
+     *
+     * @param target The processing instruction target
+     * @param data The processing instruction data (if any)
+     *
+     * @exception SAXException if a parsing error is to be reported
+     */
+    @Override
+    public void processingInstruction(String target, String data)
+            throws SAXException {
+
+        if (saxLog.isDebugEnabled()) {
+            saxLog.debug("processingInstruction('" + target + "','" + data + "')");
+        }
+
+        // No processing is required
+
+    }
+
+
+    /**
+     * Gets the document locator associated with our parser.
+     *
+     * @return the Locator supplied by the document parser
+     */
+    public Locator getDocumentLocator() {
+
+        return locator;
+
+    }
+
+    /**
+     * Sets the document locator associated with our parser.
+     *
+     * @param locator The new locator
+     */
+    @Override
+    public void setDocumentLocator(Locator locator) {
+
+        if (saxLog.isDebugEnabled()) {
+            saxLog.debug("setDocumentLocator(" + locator + ")");
+        }
+
+        this.locator = locator;
+
+    }
+
+
+    /**
+     * Process notification of a skipped entity.
+     *
+     * @param name Name of the skipped entity
+     *
+     * @exception SAXException if a parsing error is to be reported
+     */
+    @Override
+    public void skippedEntity(String name) throws SAXException {
+
+        if (saxLog.isDebugEnabled()) {
+            saxLog.debug("skippedEntity(" + name + ")");
+        }
+
+        // No processing required
+
+    }
+
+
+    /**
+     * Process notification of the beginning of the document being reached.
+     *
+     * @exception SAXException if a parsing error is to be reported
+     */
+    @Override
+    public void startDocument() throws SAXException {
+
+        if (saxLog.isDebugEnabled()) {
+            saxLog.debug("startDocument()");
+        }
+
+        // ensure that the digester is properly configured, as 
+        // the digester could be used as a SAX ContentHandler
+        // rather than via the parse() methods.
+        configure();
+    }
+
+
+    /**
+     * Process notification of the start of an XML element being reached.
+     *
+     * @param namespaceURI The Namespace URI, or the empty string if the element
+     *   has no Namespace URI or if Namespace processing is not being performed.
+     * @param localName The local name (without prefix), or the empty
+     *   string if Namespace processing is not being performed.
+     * @param qName The qualified name (with prefix), or the empty
+     *   string if qualified names are not available.\
+     * @param list The attributes attached to the element. If there are
+     *   no attributes, it shall be an empty Attributes object. 
+     * @exception SAXException if a parsing error is to be reported
+     */
+    @Override
+    public void startElement(String namespaceURI, String localName,
+                             String qName, Attributes list)
+            throws SAXException {
+        boolean debug = log.isDebugEnabled();
+        
+        if (saxLog.isDebugEnabled()) {
+            saxLog.debug("startElement(" + namespaceURI + "," + localName + "," +
+                    qName + ")");
+        }
+        
+        // Parse system properties
+        list = updateAttributes(list);
+        
+        // Save the body text accumulated for our surrounding element
+        bodyTexts.push(bodyText);
+        if (debug) {
+            log.debug("  Pushing body text '" + bodyText.toString() + "'");
+        }
+        bodyText = new StringBuilder();
+
+        // the actual element name is either in localName or qName, depending 
+        // on whether the parser is namespace aware
+        String name = localName;
+        if ((name == null) || (name.length() < 1)) {
+            name = qName;
+        }
+
+        // Compute the current matching rule
+        StringBuilder sb = new StringBuilder(match);
+        if (match.length() > 0) {
+            sb.append('/');
+        }
+        sb.append(name);
+        match = sb.toString();
+        if (debug) {
+            log.debug("  New match='" + match + "'");
+        }
+
+        // Fire "begin" events for all relevant rules
+        List<Rule> rules = getRules().match(namespaceURI, match);
+        matches.push(rules);
+        if ((rules != null) && (rules.size() > 0)) {
+            for (int i = 0; i < rules.size(); i++) {
+                try {
+                    Rule rule = rules.get(i);
+                    if (debug) {
+                        log.debug("  Fire begin() for " + rule);
+                    }
+                    rule.begin(namespaceURI, name, list);
+                } catch (Exception e) {
+                    log.error("Begin event threw exception", e);
+                    throw createSAXException(e);
+                } catch (Error e) {
+                    log.error("Begin event threw error", e);
+                    throw e;
+                }
+            }
+        } else {
+            if (debug) {
+                log.debug("  No rules found matching '" + match + "'.");
+            }
+        }
+
+    }
+
+
+    /**
+     * Process notification that a namespace prefix is coming in to scope.
+     *
+     * @param prefix Prefix that is being declared
+     * @param namespaceURI Corresponding namespace URI being mapped to
+     *
+     * @exception SAXException if a parsing error is to be reported
+     */
+    @Override
+    public void startPrefixMapping(String prefix, String namespaceURI)
+            throws SAXException {
+
+        if (saxLog.isDebugEnabled()) {
+            saxLog.debug("startPrefixMapping(" + prefix + "," + namespaceURI + ")");
+        }
+
+        // Register this prefix mapping
+        ArrayStack<String> stack = namespaces.get(prefix);
+        if (stack == null) {
+            stack = new ArrayStack<String>();
+            namespaces.put(prefix, stack);
+        }
+        stack.push(namespaceURI);
+
+    }
+
+
+    // ----------------------------------------------------- DTDHandler Methods
+
+
+    /**
+     * Receive notification of a notation declaration event.
+     *
+     * @param name The notation name
+     * @param publicId The public identifier (if any)
+     * @param systemId The system identifier (if any)
+     */
+    @Override
+    public void notationDecl(String name, String publicId, String systemId) {
+
+        if (saxLog.isDebugEnabled()) {
+            saxLog.debug("notationDecl(" + name + "," + publicId + "," +
+                    systemId + ")");
+        }
+
+    }
+
+
+    /**
+     * Receive notification of an unparsed entity declaration event.
+     *
+     * @param name The unparsed entity name
+     * @param publicId The public identifier (if any)
+     * @param systemId The system identifier (if any)
+     * @param notation The name of the associated notation
+     */
+    @Override
+    public void unparsedEntityDecl(String name, String publicId,
+                                   String systemId, String notation) {
+
+        if (saxLog.isDebugEnabled()) {
+            saxLog.debug("unparsedEntityDecl(" + name + "," + publicId + "," +
+                    systemId + "," + notation + ")");
+        }
+
+    }
+
+
+    // ----------------------------------------------- EntityResolver Methods
+
+    /**
+     * Set the <code>EntityResolver</code> used by SAX when resolving
+     * public id and system id.
+     * This must be called before the first call to <code>parse()</code>.
+     * @param entityResolver a class that implement the <code>EntityResolver</code> interface.
+     */
+    public void setEntityResolver(EntityResolver entityResolver){
+        this.entityResolver = entityResolver;
+    }
+    
+    
+    /**
+     * Return the Entity Resolver used by the SAX parser.
+     * @return Return the Entity Resolver used by the SAX parser.
+     */
+    public EntityResolver getEntityResolver(){
+        return entityResolver;
+    }
+
+    /**
+     * Resolve the requested external entity.
+     *
+     * @param publicId The public identifier of the entity being referenced
+     * @param systemId The system identifier of the entity being referenced
+     *
+     * @exception SAXException if a parsing exception occurs
+     * 
+     */
+    @Override
+    public InputSource resolveEntity(String publicId, String systemId)
+            throws SAXException {     
+                
+        if (saxLog.isDebugEnabled()) {
+            saxLog.debug("resolveEntity('" + publicId + "', '" + systemId + "')");
+        }
+        
+        if (publicId != null)
+            this.publicId = publicId;
+                                       
+        // Has this system identifier been registered?
+        String entityURL = null;
+        if (publicId != null) {
+            entityURL = entityValidator.get(publicId);
+        }
+         
+        if (entityURL == null) { 
+            if (systemId == null) {
+                // cannot resolve
+                if (log.isDebugEnabled()) {
+                    log.debug(" Cannot resolve entity: '" + publicId + "'");
+                }
+                return (null);
+                
+            } else {
+                // try to resolve using system ID
+                if (log.isDebugEnabled()) {
+                    log.debug(" Trying to resolve using system ID '" + systemId + "'");
+                } 
+                entityURL = systemId;
+            }
+        }
+        
+        // Return an input source to our alternative URL
+        if (log.isDebugEnabled()) {
+            log.debug(" Resolving to alternate DTD '" + entityURL + "'");
+        }  
+        
+        try {
+            return (new InputSource(entityURL));
+        } catch (Exception e) {
+            throw createSAXException(e);
+        }
+    }
+
+
+    // ------------------------------------------------- ErrorHandler Methods
+
+
+    /**
+     * Forward notification of a parsing error to the application supplied
+     * error handler (if any).
+     *
+     * @param exception The error information
+     *
+     * @exception SAXException if a parsing exception occurs
+     */
+    @Override
+    public void error(SAXParseException exception) throws SAXException {
+
+        log.error("Parse Error at line " + exception.getLineNumber() +
+                " column " + exception.getColumnNumber() + ": " +
+                exception.getMessage(), exception);
+        if (errorHandler != null) {
+            errorHandler.error(exception);
+        }
+
+    }
+
+
+    /**
+     * Forward notification of a fatal parsing error to the application
+     * supplied error handler (if any).
+     *
+     * @param exception The fatal error information
+     *
+     * @exception SAXException if a parsing exception occurs
+     */
+    @Override
+    public void fatalError(SAXParseException exception) throws SAXException {
+
+        log.error("Parse Fatal Error at line " + exception.getLineNumber() +
+                " column " + exception.getColumnNumber() + ": " +
+                exception.getMessage(), exception);
+        if (errorHandler != null) {
+            errorHandler.fatalError(exception);
+        }
+
+    }
+
+
+    /**
+     * Forward notification of a parse warning to the application supplied
+     * error handler (if any).
+     *
+     * @param exception The warning information
+     *
+     * @exception SAXException if a parsing exception occurs
+     */
+    @Override
+    public void warning(SAXParseException exception) throws SAXException {
+         if (errorHandler != null) {
+            log.warn("Parse Warning Error at line " + exception.getLineNumber() +
+                " column " + exception.getColumnNumber() + ": " +
+                exception.getMessage(), exception);
+            
+            errorHandler.warning(exception);
+        }
+
+    }
+
+
+    // ------------------------------------------------------- Public Methods
+
+    /**
+     * Parse the content of the specified file using this Digester.  Returns
+     * the root element from the object stack (if any).
+     *
+     * @param file File containing the XML data to be parsed
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception SAXException if a parsing exception occurs
+     */
+    public Object parse(File file) throws IOException, SAXException {
+
+        configure();
+        InputSource input = new InputSource(new FileInputStream(file));
+        input.setSystemId("file://" + file.getAbsolutePath());
+        getXMLReader().parse(input);
+        return (root);
+
+    }   
+    /**
+     * Parse the content of the specified input source using this Digester.
+     * Returns the root element from the object stack (if any).
+     *
+     * @param input Input source containing the XML data to be parsed
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception SAXException if a parsing exception occurs
+     */
+    public Object parse(InputSource input) throws IOException, SAXException {
+ 
+        configure();
+        getXMLReader().parse(input);
+        return (root);
+
+    }
+
+
+    /**
+     * Parse the content of the specified input stream using this Digester.
+     * Returns the root element from the object stack (if any).
+     *
+     * @param input Input stream containing the XML data to be parsed
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception SAXException if a parsing exception occurs
+     */
+    public Object parse(InputStream input) throws IOException, SAXException {
+
+        configure();
+        InputSource is = new InputSource(input);
+        getXMLReader().parse(is);
+        return (root);
+
+    }
+
+
+    /**
+     * Parse the content of the specified reader using this Digester.
+     * Returns the root element from the object stack (if any).
+     *
+     * @param reader Reader containing the XML data to be parsed
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception SAXException if a parsing exception occurs
+     */
+    public Object parse(Reader reader) throws IOException, SAXException {
+
+        configure();
+        InputSource is = new InputSource(reader);
+        getXMLReader().parse(is);
+        return (root);
+
+    }
+
+
+    /**
+     * Parse the content of the specified URI using this Digester.
+     * Returns the root element from the object stack (if any).
+     *
+     * @param uri URI containing the XML data to be parsed
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception SAXException if a parsing exception occurs
+     */
+    public Object parse(String uri) throws IOException, SAXException {
+
+        configure();
+        InputSource is = new InputSource(uri);
+        getXMLReader().parse(is);
+        return (root);
+
+    }
+
+
+    /**
+     * <p>Register the specified DTD URL for the specified public identifier.
+     * This must be called before the first call to <code>parse()</code>.
+     * </p><p>
+     * <code>Digester</code> contains an internal <code>EntityResolver</code>
+     * implementation. This maps <code>PUBLICID</code>'s to URLs 
+     * (from which the resource will be loaded). A common use case for this
+     * method is to register local URLs (possibly computed at runtime by a 
+     * classloader) for DTDs. This allows the performance advantage of using
+     * a local version without having to ensure every <code>SYSTEM</code>
+     * URI on every processed xml document is local. This implementation provides
+     * only basic functionality. If more sophisticated features are required,
+     * using {@link #setEntityResolver} to set a custom resolver is recommended.
+     * </p><p>
+     * <strong>Note:</strong> This method will have no effect when a custom 
+     * <code>EntityResolver</code> has been set. (Setting a custom 
+     * <code>EntityResolver</code> overrides the internal implementation.) 
+     * </p>
+     * @param publicId Public identifier of the DTD to be resolved
+     * @param entityURL The URL to use for reading this DTD
+     */
+    public void register(String publicId, String entityURL) {
+
+        if (log.isDebugEnabled()) {
+            log.debug("register('" + publicId + "', '" + entityURL + "'");
+        }
+        entityValidator.put(publicId, entityURL);
+
+    }
+
+
+    // --------------------------------------------------------- Rule Methods
+
+
+    /**
+     * <p>Register a new Rule matching the specified pattern.
+     * This method sets the <code>Digester</code> property on the rule.</p>
+     *
+     * @param pattern Element matching pattern
+     * @param rule Rule to be registered
+     */
+    public void addRule(String pattern, Rule rule) {
+
+        rule.setDigester(this);
+        getRules().add(pattern, rule);
+
+    }
+
+
+    /**
+     * Register a set of Rule instances defined in a RuleSet.
+     *
+     * @param ruleSet The RuleSet instance to configure from
+     */
+    public void addRuleSet(RuleSet ruleSet) {
+
+        String oldNamespaceURI = getRuleNamespaceURI();
+        String newNamespaceURI = ruleSet.getNamespaceURI();
+        if (log.isDebugEnabled()) {
+            if (newNamespaceURI == null) {
+                log.debug("addRuleSet() with no namespace URI");
+            } else {
+                log.debug("addRuleSet() with namespace URI " + newNamespaceURI);
+            }
+        }
+        setRuleNamespaceURI(newNamespaceURI);
+        ruleSet.addRuleInstances(this);
+        setRuleNamespaceURI(oldNamespaceURI);
+
+    }
+
+
+    /**
+     * Add an "call method" rule for a method which accepts no arguments.
+     *
+     * @param pattern Element matching pattern
+     * @param methodName Method name to be called
+     * @see CallMethodRule
+     */
+    public void addCallMethod(String pattern, String methodName) {
+
+        addRule(
+                pattern,
+                new CallMethodRule(methodName));
+
+    }
+
+    /**
+     * Add an "call method" rule for the specified parameters.
+     *
+     * @param pattern Element matching pattern
+     * @param methodName Method name to be called
+     * @param paramCount Number of expected parameters (or zero
+     *  for a single parameter from the body of this element)
+     * @see CallMethodRule
+     */
+    public void addCallMethod(String pattern, String methodName,
+                              int paramCount) {
+
+        addRule(pattern,
+                new CallMethodRule(methodName, paramCount));
+
+    }
+
+
+    /**
+     * Add an "call method" rule for the specified parameters.
+     * If <code>paramCount</code> is set to zero the rule will use
+     * the body of the matched element as the single argument of the
+     * method, unless <code>paramTypes</code> is null or empty, in this
+     * case the rule will call the specified method with no arguments.
+     *
+     * @param pattern Element matching pattern
+     * @param methodName Method name to be called
+     * @param paramCount Number of expected parameters (or zero
+     *  for a single parameter from the body of this element)
+     * @param paramTypes Set of Java class names for the types
+     *  of the expected parameters
+     *  (if you wish to use a primitive type, specify the corresponding
+     *  Java wrapper class instead, such as <code>java.lang.Boolean</code>
+     *  for a <code>boolean</code> parameter)
+     * @see CallMethodRule
+     */
+    public void addCallMethod(String pattern, String methodName,
+                              int paramCount, String paramTypes[]) {
+
+        addRule(pattern,
+                new CallMethodRule(
+                                    methodName,
+                                    paramCount, 
+                                    paramTypes));
+
+    }
+
+
+    /**
+     * Add an "call method" rule for the specified parameters.
+     * If <code>paramCount</code> is set to zero the rule will use
+     * the body of the matched element as the single argument of the
+     * method, unless <code>paramTypes</code> is null or empty, in this
+     * case the rule will call the specified method with no arguments.
+     *
+     * @param pattern Element matching pattern
+     * @param methodName Method name to be called
+     * @param paramCount Number of expected parameters (or zero
+     *  for a single parameter from the body of this element)
+     * @param paramTypes The Java class names of the arguments
+     *  (if you wish to use a primitive type, specify the corresponding
+     *  Java wrapper class instead, such as <code>java.lang.Boolean</code>
+     *  for a <code>boolean</code> parameter)
+     * @see CallMethodRule
+     */
+    public void addCallMethod(String pattern, String methodName,
+                              int paramCount, Class<?> paramTypes[]) {
+
+        addRule(pattern,
+                new CallMethodRule(
+                                    methodName,
+                                    paramCount, 
+                                    paramTypes));
+
+    }
+
+
+    /**
+     * Add a "call parameter" rule for the specified parameters.
+     *
+     * @param pattern Element matching pattern
+     * @param paramIndex Zero-relative parameter index to set
+     *  (from the body of this element)
+     * @see CallParamRule
+     */
+    public void addCallParam(String pattern, int paramIndex) {
+
+        addRule(pattern,
+                new CallParamRule(paramIndex));
+
+    }
+
+
+    /**
+     * Add a "call parameter" rule for the specified parameters.
+     *
+     * @param pattern Element matching pattern
+     * @param paramIndex Zero-relative parameter index to set
+     *  (from the specified attribute)
+     * @param attributeName Attribute whose value is used as the
+     *  parameter value
+     * @see CallParamRule
+     */
+    public void addCallParam(String pattern, int paramIndex,
+                             String attributeName) {
+
+        addRule(pattern,
+                new CallParamRule(paramIndex, attributeName));
+
+    }
+
+
+    /**
+     * Add a "call parameter" rule.
+     * This will either take a parameter from the stack 
+     * or from the current element body text. 
+     *
+     * @param paramIndex The zero-relative parameter number
+     * @param fromStack Should the call parameter be taken from the top of the stack?
+     * @see CallParamRule
+     */    
+    public void addCallParam(String pattern, int paramIndex, boolean fromStack) {
+    
+        addRule(pattern,
+                new CallParamRule(paramIndex, fromStack));
+      
+    }
+
+    /**
+     * Add a "call parameter" rule that sets a parameter from the stack.
+     * This takes a parameter from the given position on the stack.
+     *
+     * @param paramIndex The zero-relative parameter number
+     * @param stackIndex set the call parameter to the stackIndex'th object down the stack,
+     * where 0 is the top of the stack, 1 the next element down and so on
+     * @see CallMethodRule
+     */    
+    public void addCallParam(String pattern, int paramIndex, int stackIndex) {
+    
+        addRule(pattern,
+                new CallParamRule(paramIndex, stackIndex));
+      
+    }
+    
+    /**
+     * Add a "call parameter" rule that sets a parameter from the current 
+     * <code>Digester</code> matching path.
+     * This is sometimes useful when using rules that support wildcards.
+     *
+     * @param pattern the pattern that this rule should match
+     * @param paramIndex The zero-relative parameter number
+     * @see CallMethodRule
+     */
+    public void addCallParamPath(String pattern,int paramIndex) {
+        addRule(pattern, new PathCallParamRule(paramIndex));
+    }
+    
+    /**
+     * Add a "call parameter" rule that sets a parameter from a 
+     * caller-provided object. This can be used to pass constants such as
+     * strings to methods; it can also be used to pass mutable objects,
+     * providing ways for objects to do things like "register" themselves
+     * with some shared object.
+     * <p>
+     * Note that when attempting to locate a matching method to invoke,
+     * the true type of the paramObj is used, so that despite the paramObj
+     * being passed in here as type Object, the target method can declare
+     * its parameters as being the true type of the object (or some ancestor
+     * type, according to the usual type-conversion rules).
+     *
+     * @param paramIndex The zero-relative parameter number
+     * @param paramObj Any arbitrary object to be passed to the target
+     * method.
+     * @see CallMethodRule
+     *
+     * @since 1.6
+     */    
+    public void addObjectParam(String pattern, int paramIndex, 
+                               Object paramObj) {
+    
+        addRule(pattern,
+                new ObjectParamRule(paramIndex, paramObj));
+      
+    }
+    
+    /**
+     * Add a "factory create" rule for the specified parameters.
+     * Exceptions thrown during the object creation process will be propagated.
+     *
+     * @param pattern Element matching pattern
+     * @param className Java class name of the object creation factory class
+     * @see FactoryCreateRule
+     */
+    public void addFactoryCreate(String pattern, String className) {
+
+        addFactoryCreate(pattern, className, false);
+
+    }
+
+
+    /**
+     * Add a "factory create" rule for the specified parameters.
+     * Exceptions thrown during the object creation process will be propagated.
+     *
+     * @param pattern Element matching pattern
+     * @param clazz Java class of the object creation factory class
+     * @see FactoryCreateRule
+     */
+    public void addFactoryCreate(String pattern, Class<?> clazz) {
+
+        addFactoryCreate(pattern, clazz, false);
+
+    }
+
+
+    /**
+     * Add a "factory create" rule for the specified parameters.
+     * Exceptions thrown during the object creation process will be propagated.
+     *
+     * @param pattern Element matching pattern
+     * @param className Java class name of the object creation factory class
+     * @param attributeName Attribute name which, if present, overrides the
+     *  value specified by <code>className</code>
+     * @see FactoryCreateRule
+     */
+    public void addFactoryCreate(String pattern, String className,
+                                 String attributeName) {
+
+        addFactoryCreate(pattern, className, attributeName, false);
+
+    }
+
+
+    /**
+     * Add a "factory create" rule for the specified parameters.
+     * Exceptions thrown during the object creation process will be propagated.
+     *
+     * @param pattern Element matching pattern
+     * @param clazz Java class of the object creation factory class
+     * @param attributeName Attribute name which, if present, overrides the
+     *  value specified by <code>className</code>
+     * @see FactoryCreateRule
+     */
+    public void addFactoryCreate(String pattern, Class<?> clazz,
+                                 String attributeName) {
+
+        addFactoryCreate(pattern, clazz, attributeName, false);
+
+    }
+
+
+    /**
+     * Add a "factory create" rule for the specified parameters.
+     * Exceptions thrown during the object creation process will be propagated.
+     *
+     * @param pattern Element matching pattern
+     * @param creationFactory Previously instantiated ObjectCreationFactory
+     *  to be utilized
+     * @see FactoryCreateRule
+     */
+    public void addFactoryCreate(String pattern,
+                                 ObjectCreationFactory creationFactory) {
+
+        addFactoryCreate(pattern, creationFactory, false);
+
+    }
+
+    /**
+     * Add a "factory create" rule for the specified parameters.
+     *
+     * @param pattern Element matching pattern
+     * @param className Java class name of the object creation factory class
+     * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during
+     * object creation will be ignored.
+     * @see FactoryCreateRule
+     */
+    public void addFactoryCreate(
+                                    String pattern, 
+                                    String className,
+                                    boolean ignoreCreateExceptions) {
+
+        addRule(
+                pattern,
+                new FactoryCreateRule(className, ignoreCreateExceptions));
+
+    }
+
+
+    /**
+     * Add a "factory create" rule for the specified parameters.
+     *
+     * @param pattern Element matching pattern
+     * @param clazz Java class of the object creation factory class
+     * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during
+     * object creation will be ignored.
+     * @see FactoryCreateRule
+     */
+    public void addFactoryCreate(
+                                    String pattern, 
+                                    Class<?> clazz,
+                                    boolean ignoreCreateExceptions) {
+
+        addRule(
+                pattern,
+                new FactoryCreateRule(clazz, ignoreCreateExceptions));
+
+    }
+
+
+    /**
+     * Add a "factory create" rule for the specified parameters.
+     *
+     * @param pattern Element matching pattern
+     * @param className Java class name of the object creation factory class
+     * @param attributeName Attribute name which, if present, overrides the
+     *  value specified by <code>className</code>
+     * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during
+     * object creation will be ignored.
+     * @see FactoryCreateRule
+     */
+    public void addFactoryCreate(
+                                String pattern, 
+                                String className,
+                                String attributeName,
+                                boolean ignoreCreateExceptions) {
+
+        addRule(
+                pattern,
+                new FactoryCreateRule(className, attributeName, ignoreCreateExceptions));
+
+    }
+
+
+    /**
+     * Add a "factory create" rule for the specified parameters.
+     *
+     * @param pattern Element matching pattern
+     * @param clazz Java class of the object creation factory class
+     * @param attributeName Attribute name which, if present, overrides the
+     *  value specified by <code>className</code>
+     * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during
+     * object creation will be ignored.
+     * @see FactoryCreateRule
+     */
+    public void addFactoryCreate(
+                                    String pattern, 
+                                    Class<?> clazz,
+                                    String attributeName,
+                                    boolean ignoreCreateExceptions) {
+
+        addRule(
+                pattern,
+                new FactoryCreateRule(clazz, attributeName, ignoreCreateExceptions));
+
+    }
+
+
+    /**
+     * Add a "factory create" rule for the specified parameters.
+     *
+     * @param pattern Element matching pattern
+     * @param creationFactory Previously instantiated ObjectCreationFactory
+     *  to be utilized
+     * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during
+     * object creation will be ignored.
+     * @see FactoryCreateRule
+     */
+    public void addFactoryCreate(String pattern,
+                                 ObjectCreationFactory creationFactory,
+                                 boolean ignoreCreateExceptions) {
+
+        creationFactory.setDigester(this);
+        addRule(pattern,
+                new FactoryCreateRule(creationFactory, ignoreCreateExceptions));
+
+    }
+
+    /**
+     * Add an "object create" rule for the specified parameters.
+     *
+     * @param pattern Element matching pattern
+     * @param className Java class name to be created
+     * @see ObjectCreateRule
+     */
+    public void addObjectCreate(String pattern, String className) {
+
+        addRule(pattern,
+                new ObjectCreateRule(className));
+
+    }
+
+
+    /**
+     * Add an "object create" rule for the specified parameters.
+     *
+     * @param pattern Element matching pattern
+     * @param clazz Java class to be created
+     * @see ObjectCreateRule
+     */
+    public void addObjectCreate(String pattern, Class<?> clazz) {
+
+        addRule(pattern,
+                new ObjectCreateRule(clazz));
+
+    }
+
+
+    /**
+     * Add an "object create" rule for the specified parameters.
+     *
+     * @param pattern Element matching pattern
+     * @param className Default Java class name to be created
+     * @param attributeName Attribute name that optionally overrides
+     *  the default Java class name to be created
+     * @see ObjectCreateRule
+     */
+    public void addObjectCreate(String pattern, String className,
+                                String attributeName) {
+
+        addRule(pattern,
+                new ObjectCreateRule(className, attributeName));
+
+    }
+
+
+    /**
+     * Add an "object create" rule for the specified parameters.
+     *
+     * @param pattern Element matching pattern
+     * @param attributeName Attribute name that optionally overrides
+     * @param clazz Default Java class to be created
+     *  the default Java class name to be created
+     * @see ObjectCreateRule
+     */
+    public void addObjectCreate(String pattern,
+                                String attributeName,
+                                Class<?> clazz) {
+
+        addRule(pattern,
+                new ObjectCreateRule(attributeName, clazz));
+
+    }
+
+    /**
+     * Add a "set next" rule for the specified parameters.
+     *
+     * @param pattern Element matching pattern
+     * @param methodName Method name to call on the parent element
+     * @see SetNextRule
+     */
+    public void addSetNext(String pattern, String methodName) {
+
+        addRule(pattern,
+                new SetNextRule(methodName));
+
+    }
+
+
+    /**
+     * Add a "set next" rule for the specified parameters.
+     *
+     * @param pattern Element matching pattern
+     * @param methodName Method name to call on the parent element
+     * @param paramType Java class name of the expected parameter type
+     *  (if you wish to use a primitive type, specify the corresponding
+     *  Java wrapper class instead, such as <code>java.lang.Boolean</code>
+     *  for a <code>boolean</code> parameter)
+     * @see SetNextRule
+     */
+    public void addSetNext(String pattern, String methodName,
+                           String paramType) {
+
+        addRule(pattern,
+                new SetNextRule(methodName, paramType));
+
+    }
+
+
+    /**
+     * Add {@link SetRootRule} with the specified parameters.
+     *
+     * @param pattern Element matching pattern
+     * @param methodName Method name to call on the root object
+     * @see SetRootRule
+     */
+    public void addSetRoot(String pattern, String methodName) {
+
+        addRule(pattern,
+                new SetRootRule(methodName));
+
+    }
+
+
+    /**
+     * Add {@link SetRootRule} with the specified parameters.
+     *
+     * @param pattern Element matching pattern
+     * @param methodName Method name to call on the root object
+     * @param paramType Java class name of the expected parameter type
+     * @see SetRootRule
+     */
+    public void addSetRoot(String pattern, String methodName,
+                           String paramType) {
+
+        addRule(pattern,
+                new SetRootRule(methodName, paramType));
+
+    }
+
+    /**
+     * Add a "set properties" rule for the specified parameters.
+     *
+     * @param pattern Element matching pattern
+     * @see SetPropertiesRule
+     */
+    public void addSetProperties(String pattern) {
+
+        addRule(pattern,
+                new SetPropertiesRule());
+
+    }
+
+    /**
+     * Add a "set properties" rule with a single overridden parameter.
+     * See {@link SetPropertiesRule#SetPropertiesRule(String attributeName, String propertyName)}
+     *
+     * @param pattern Element matching pattern
+     * @param attributeName map this attribute
+     * @param propertyName to this property
+     * @see SetPropertiesRule
+     */
+    public void addSetProperties(
+                                String pattern, 
+                                String attributeName,
+                                String propertyName) {
+
+        addRule(pattern,
+                new SetPropertiesRule(attributeName, propertyName));
+
+    }
+
+    /**
+     * Add a "set properties" rule with overridden parameters.
+     * See {@link SetPropertiesRule#SetPropertiesRule(String [] attributeNames, String [] propertyNames)}
+     *
+     * @param pattern Element matching pattern
+     * @param attributeNames names of attributes with custom mappings
+     * @param propertyNames property names these attributes map to
+     * @see SetPropertiesRule
+     */
+    public void addSetProperties(
+                                String pattern, 
+                                String [] attributeNames,
+                                String [] propertyNames) {
+
+        addRule(pattern,
+                new SetPropertiesRule(attributeNames, propertyNames));
+
+    }
+
+
+    /**
+     * Add a "set property" rule for the specified parameters.
+     *
+     * @param pattern Element matching pattern
+     * @param name Attribute name containing the property name to be set
+     * @param value Attribute name containing the property value to set
+     * @see SetPropertyRule
+     */
+    public void addSetProperty(String pattern, String name, String value) {
+
+        addRule(pattern,
+                new SetPropertyRule(name, value));
+
+    }
+
+
+    /**
+     * Add a "set top" rule for the specified parameters.
+     *
+     * @param pattern Element matching pattern
+     * @param methodName Method name to call on the parent element
+     * @see SetTopRule
+     */
+    public void addSetTop(String pattern, String methodName) {
+
+        addRule(pattern,
+                new SetTopRule(methodName));
+
+    }
+
+
+    /**
+     * Add a "set top" rule for the specified parameters.
+     *
+     * @param pattern Element matching pattern
+     * @param methodName Method name to call on the parent element
+     * @param paramType Java class name of the expected parameter type
+     *  (if you wish to use a primitive type, specify the corresponding
+     *  Java wrapper class instead, such as <code>java.lang.Boolean</code>
+     *  for a <code>boolean</code> parameter)
+     * @see SetTopRule
+     */
+    public void addSetTop(String pattern, String methodName,
+                          String paramType) {
+
+        addRule(pattern,
+                new SetTopRule(methodName, paramType));
+
+    }
+
+
+    // --------------------------------------------------- Object Stack Methods
+
+
+    /**
+     * Clear the current contents of the object stack.
+     * <p>
+     * Calling this method <i>might</i> allow another document of the same type
+     * to be correctly parsed. However this method was not intended for this 
+     * purpose. In general, a separate Digester object should be created for
+     * each document to be parsed.
+     */
+    public void clear() {
+
+        match = "";
+        bodyTexts.clear();
+        params.clear();
+        publicId = null;
+        stack.clear();
+        log = null;
+        saxLog = null;
+        configured = false;
+        
+    }
+
+    
+    public void reset() {
+        root = null;
+        setErrorHandler(null);
+        clear();
+    }
+
+
+    /**
+     * Return the top object on the stack without removing it.  If there are
+     * no objects on the stack, return <code>null</code>.
+     */
+    public Object peek() {
+
+        try {
+            return (stack.peek());
+        } catch (EmptyStackException e) {
+            log.warn("Empty stack (returning null)");
+            return (null);
+        }
+
+    }
+
+
+    /**
+     * Return the n'th object down the stack, where 0 is the top element
+     * and [getCount()-1] is the bottom element.  If the specified index
+     * is out of range, return <code>null</code>.
+     *
+     * @param n Index of the desired element, where 0 is the top of the stack,
+     *  1 is the next element down, and so on.
+     */
+    public Object peek(int n) {
+
+        try {
+            return (stack.peek(n));
+        } catch (EmptyStackException e) {
+            log.warn("Empty stack (returning null)");
+            return (null);
+        }
+
+    }
+
+
+    /**
+     * Pop the top object off of the stack, and return it.  If there are
+     * no objects on the stack, return <code>null</code>.
+     */
+    public Object pop() {
+
+        try {
+            return (stack.pop());
+        } catch (EmptyStackException e) {
+            log.warn("Empty stack (returning null)");
+            return (null);
+        }
+
+    }
+
+
+    /**
+     * Push a new object onto the top of the object stack.
+     *
+     * @param object The new object
+     */
+    public void push(Object object) {
+
+        if (stack.size() == 0) {
+            root = object;
+        }
+        stack.push(object);
+
+    }
+
+    /**
+     * Pushes the given object onto the stack with the given name.
+     * If no stack already exists with the given name then one will be created.
+     * 
+     * @param stackName the name of the stack onto which the object should be pushed
+     * @param value the Object to be pushed onto the named stack.
+     *
+     * @since 1.6
+     */
+    public void push(String stackName, Object value) {
+        ArrayStack<Object> namedStack = stacksByName.get(stackName);
+        if (namedStack == null) {
+            namedStack = new ArrayStack<Object>();
+            stacksByName.put(stackName, namedStack);
+        }
+        namedStack.push(value);
+    }
+
+    /**
+     * <p>Pops (gets and removes) the top object from the stack with the given name.</p>
+     *
+     * <p><strong>Note:</strong> a stack is considered empty
+     * if no objects have been pushed onto it yet.</p>
+     * 
+     * @param stackName the name of the stack from which the top value is to be popped
+     * @return the top <code>Object</code> on the stack or or null if the stack is either 
+     * empty or has not been created yet
+     * @throws EmptyStackException if the named stack is empty
+     *
+     * @since 1.6
+     */
+    public Object pop(String stackName) {
+        Object result = null;
+        ArrayStack<Object> namedStack = stacksByName.get(stackName);
+        if (namedStack == null) {
+            if (log.isDebugEnabled()) {
+                log.debug("Stack '" + stackName + "' is empty");
+            }
+            throw new EmptyStackException();
+            
+        } else {
+        
+            result = namedStack.pop();
+        }
+        return result;
+    }
+    
+    /**
+     * <p>Gets the top object from the stack with the given name.
+     * This method does not remove the object from the stack.
+     * </p>
+     * <p><strong>Note:</strong> a stack is considered empty
+     * if no objects have been pushed onto it yet.</p>
+     *
+     * @param stackName the name of the stack to be peeked
+     * @return the top <code>Object</code> on the stack or null if the stack is either 
+     * empty or has not been created yet
+     * @throws EmptyStackException if the named stack is empty 
+     *
+     * @since 1.6
+     */
+    public Object peek(String stackName) {
+        Object result = null;
+        ArrayStack<Object> namedStack = stacksByName.get(stackName);
+        if (namedStack == null ) {
+            if (log.isDebugEnabled()) {
+                log.debug("Stack '" + stackName + "' is empty");
+            }        
+            throw new EmptyStackException();
+        
+        } else {
+        
+            result = namedStack.peek();
+        }
+        return result;
+    }
+
+    /**
+     * <p>Is the stack with the given name empty?</p>
+     * <p><strong>Note:</strong> a stack is considered empty
+     * if no objects have been pushed onto it yet.</p>
+     * @param stackName the name of the stack whose emptiness 
+     * should be evaluated
+     * @return true if the given stack if empty 
+     *
+     * @since 1.6
+     */
+    public boolean isEmpty(String stackName) {
+        boolean result = true;
+        ArrayStack<Object> namedStack = stacksByName.get(stackName);
+        if (namedStack != null ) {
+            result = namedStack.isEmpty();
+        }
+        return result;
+    }
+    
+    /**
+     * When the Digester is being used as a SAXContentHandler, 
+     * this method allows you to access the root object that has been
+     * created after parsing.
+     * 
+     * @return the root object that has been created after parsing
+     *  or null if the digester has not parsed any XML yet.
+     */
+    public Object getRoot() {
+        return root;
+    }
+    
+
+    // ------------------------------------------------ Parameter Stack Methods
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * <p>
+     * Provide a hook for lazy configuration of this <code>Digester</code>
+     * instance.  The default implementation does nothing, but subclasses
+     * can override as needed.
+     * </p>
+     *
+     * <p>
+     * <strong>Note</strong> This method may be called more than once.
+     * Once only initialization code should be placed in {@link #initialize}
+     * or the code should take responsibility by checking and setting the 
+     * {@link #configured} flag.
+     * </p>
+     */
+    protected void configure() {
+
+        // Do not configure more than once
+        if (configured) {
+            return;
+        }
+
+        log = LogFactory.getLog("org.apache.commons.digester.Digester");
+        saxLog = LogFactory.getLog("org.apache.commons.digester.Digester.sax");
+
+        // Perform lazy configuration as needed
+        initialize(); // call hook method for subclasses that want to be initialized once only
+        // Nothing else required by default
+
+        // Set the configuration flag to avoid repeating
+        configured = true;
+
+    }
+    
+    /**
+     * <p>
+     * Provides a hook for lazy initialization of this <code>Digester</code>
+     * instance.  
+     * The default implementation does nothing, but subclasses
+     * can override as needed.
+     * Digester (by default) only calls this method once.
+     * </p>
+     *
+     * <p>
+     * <strong>Note</strong> This method will be called by {@link #configure} 
+     * only when the {@link #configured} flag is false. 
+     * Subclasses that override <code>configure</code> or who set <code>configured</code>
+     * may find that this method may be called more than once.
+     * </p>
+     *
+     * @since 1.6
+     */
+    protected void initialize() {
+
+        // Perform lazy initialization as needed
+        // Nothing required by default
+
+    }    
+
+    // -------------------------------------------------------- Package Methods
+
+
+    /**
+     * Return the set of DTD URL registrations, keyed by public identifier.
+     */
+    Map<String,String> getRegistrations() {
+
+        return (entityValidator);
+
+    }
+
+
+    /**
+     * <p>Return the top object on the parameters stack without removing it.  If there are
+     * no objects on the stack, return <code>null</code>.</p>
+     *
+     * <p>The parameters stack is used to store <code>CallMethodRule</code> parameters. 
+     * See {@link #params}.</p>
+     */
+    public Object peekParams() {
+
+        try {
+            return (params.peek());
+        } catch (EmptyStackException e) {
+            log.warn("Empty stack (returning null)");
+            return (null);
+        }
+
+    }
+
+
+    /**
+     * <p>Return the n'th object down the parameters stack, where 0 is the top element
+     * and [getCount()-1] is the bottom element.  If the specified index
+     * is out of range, return <code>null</code>.</p>
+     *
+     * <p>The parameters stack is used to store <code>CallMethodRule</code> parameters. 
+     * See {@link #params}.</p>
+     *
+     * @param n Index of the desired element, where 0 is the top of the stack,
+     *  1 is the next element down, and so on.
+     */
+    public Object peekParams(int n) {
+
+        try {
+            return (params.peek(n));
+        } catch (EmptyStackException e) {
+            log.warn("Empty stack (returning null)");
+            return (null);
+        }
+
+    }
+
+
+    /**
+     * <p>Pop the top object off of the parameters stack, and return it.  If there are
+     * no objects on the stack, return <code>null</code>.</p>
+     *
+     * <p>The parameters stack is used to store <code>CallMethodRule</code> parameters. 
+     * See {@link #params}.</p>
+     */
+    public Object popParams() {
+
+        try {
+            if (log.isTraceEnabled()) {
+                log.trace("Popping params");
+            }
+            return (params.pop());
+        } catch (EmptyStackException e) {
+            log.warn("Empty stack (returning null)");
+            return (null);
+        }
+
+    }
+
+
+    /**
+     * <p>Push a new object onto the top of the parameters stack.</p>
+     *
+     * <p>The parameters stack is used to store <code>CallMethodRule</code> parameters. 
+     * See {@link #params}.</p>
+     *
+     * @param object The new object
+     */
+    public void pushParams(Object object) {
+        if (log.isTraceEnabled()) {
+            log.trace("Pushing params");
+        }
+        params.push(object);
+
+    }
+
+    /**
+     * Create a SAX exception which also understands about the location in
+     * the digester file where the exception occurs
+     *
+     * @return the new exception
+     */
+    public SAXException createSAXException(String message, Exception e) {
+        if ((e != null) &&
+            (e instanceof InvocationTargetException)) {
+            Throwable t = ((InvocationTargetException) e).getTargetException();
+            if ((t != null) && (t instanceof Exception)) {
+                e = (Exception) t;
+            }
+        }
+        if (locator != null) {
+            String error = "Error at (" + locator.getLineNumber() + ", " +
+                    locator.getColumnNumber() + ") : " + message;
+            if (e != null) {
+                return new SAXParseException(error, locator, e);
+            } else {
+                return new SAXParseException(error, locator);
+            }
+        }
+        log.error("No Locator!");
+        if (e != null) {
+            return new SAXException(message, e);
+        } else {
+            return new SAXException(message);
+        }
+    }
+
+    /**
+     * Create a SAX exception which also understands about the location in
+     * the digester file where the exception occurs
+     *
+     * @return the new exception
+     */
+    public SAXException createSAXException(Exception e) {
+        if (e instanceof InvocationTargetException) {
+            Throwable t = ((InvocationTargetException) e).getTargetException();
+            if ((t != null) && (t instanceof Exception)) {
+                e = (Exception) t;
+            }
+        }
+        return createSAXException(e.getMessage(), e);
+    }
+
+    /**
+     * Create a SAX exception which also understands about the location in
+     * the digester file where the exception occurs
+     *
+     * @return the new exception
+     */
+    public SAXException createSAXException(String message) {
+        return createSAXException(message, null);
+    }
+    
+
+    // ------------------------------------------------------- Private Methods
+
+
+   /**
+     * Returns an attributes list which contains all the attributes
+     * passed in, with any text of form "${xxx}" in an attribute value
+     * replaced by the appropriate value from the system property.
+     */
+    private Attributes updateAttributes(Attributes list) {
+
+        if (list.getLength() == 0) {
+            return list;
+        }
+        
+        AttributesImpl newAttrs = new AttributesImpl(list);
+        int nAttributes = newAttrs.getLength();
+        for (int i = 0; i < nAttributes; ++i) {
+            String value = newAttrs.getValue(i);
+            try {
+                String newValue = 
+                    IntrospectionUtils.replaceProperties(value, null, source);
+                if (value != newValue) {
+                    newAttrs.setValue(i, newValue);
+                }
+            }
+            catch (Exception e) {
+                // ignore - let the attribute have its original value
+            }
+        }
+
+        return newAttrs;
+
+    }
+
+
+    /**
+     * Return a new StringBuilder containing the same contents as the
+     * input buffer, except that data of form ${varname} have been
+     * replaced by the value of that var as defined in the system property.
+     */
+    private StringBuilder updateBodyText(StringBuilder bodyText) {
+        String in = bodyText.toString();
+        String out;
+        try {
+            out = IntrospectionUtils.replaceProperties(in, null, source);
+        } catch(Exception e) {
+            return bodyText; // return unchanged data
+        }
+
+        if (out == in)  {
+            // No substitutions required. Don't waste memory creating
+            // a new buffer
+            return bodyText;
+        } else {
+            return new StringBuilder(out);
+        }
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/FactoryCreateRule.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/FactoryCreateRule.java
new file mode 100644
index 0000000..cb9c66d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/FactoryCreateRule.java
@@ -0,0 +1,402 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.digester;
+
+import org.xml.sax.Attributes;
+
+
+/**
+ * <p>Rule implementation that uses an {@link ObjectCreationFactory} to create
+ * a new object which it pushes onto the object stack.  When the element is
+ * complete, the object will be popped.</p>
+ *
+ * <p>This rule is intended in situations where the element's attributes are
+ * needed before the object can be created.  A common scenario is for the
+ * ObjectCreationFactory implementation to use the attributes  as parameters
+ * in a call to either a factory method or to a non-empty constructor.
+ */
+
+public class FactoryCreateRule extends Rule {
+
+    // ----------------------------------------------------------- Fields
+    
+    /** Should exceptions thrown by the factory be ignored? */
+    private boolean ignoreCreateExceptions;
+    /** Stock to manage */
+    private ArrayStack<Boolean> exceptionIgnoredStack;
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * <p>Construct a factory create rule that will use the specified
+     * class name to create an {@link ObjectCreationFactory} which will
+     * then be used to create an object and push it on the stack.</p>
+     *
+     * <p>Exceptions thrown during the object creation process will be propagated.</p>
+     *
+     * @param className Java class name of the object creation factory class
+     */
+    public FactoryCreateRule(String className) {
+
+        this(className, false);
+
+    }
+
+
+    /**
+     * <p>Construct a factory create rule that will use the specified
+     * class to create an {@link ObjectCreationFactory} which will
+     * then be used to create an object and push it on the stack.</p>
+     *
+     * <p>Exceptions thrown during the object creation process will be propagated.</p>
+     *
+     * @param clazz Java class name of the object creation factory class
+     */
+    public FactoryCreateRule(Class<?> clazz) {
+
+        this(clazz, false);
+
+    }
+
+
+    /**
+     * <p>Construct a factory create rule that will use the specified
+     * class name (possibly overridden by the specified attribute if present)
+     * to create an {@link ObjectCreationFactory}, which will then be used
+     * to instantiate an object instance and push it onto the stack.</p>
+     *
+     * <p>Exceptions thrown during the object creation process will be propagated.</p>
+     *
+     * @param className Default Java class name of the factory class
+     * @param attributeName Attribute name which, if present, contains an
+     *  override of the class name of the object creation factory to create.
+     */
+    public FactoryCreateRule(String className, String attributeName) {
+
+        this(className, attributeName, false);
+
+    }
+
+
+    /**
+     * <p>Construct a factory create rule that will use the specified
+     * class (possibly overridden by the specified attribute if present)
+     * to create an {@link ObjectCreationFactory}, which will then be used
+     * to instantiate an object instance and push it onto the stack.</p>
+     *
+     * <p>Exceptions thrown during the object creation process will be propagated.</p>
+     *
+     * @param clazz Default Java class name of the factory class
+     * @param attributeName Attribute name which, if present, contains an
+     *  override of the class name of the object creation factory to create.
+     */
+    public FactoryCreateRule(Class<?> clazz, String attributeName) {
+
+        this(clazz, attributeName, false);
+
+    }
+
+
+    /**
+     * <p>Construct a factory create rule using the given, already instantiated,
+     * {@link ObjectCreationFactory}.</p>
+     *
+     * <p>Exceptions thrown during the object creation process will be propagated.</p>
+     *
+     * @param creationFactory called on to create the object.
+     */
+    public FactoryCreateRule(ObjectCreationFactory creationFactory) {
+
+        this(creationFactory, false);
+
+    }
+    
+    /**
+     * Construct a factory create rule that will use the specified
+     * class name to create an {@link ObjectCreationFactory} which will
+     * then be used to create an object and push it on the stack.
+     *
+     * @param className Java class name of the object creation factory class
+     * @param ignoreCreateExceptions if true, exceptions thrown by the object
+     *  creation factory
+     * will be ignored.
+     */
+    public FactoryCreateRule(String className, boolean ignoreCreateExceptions) {
+
+        this(className, null, ignoreCreateExceptions);
+
+    }
+
+
+    /**
+     * Construct a factory create rule that will use the specified
+     * class to create an {@link ObjectCreationFactory} which will
+     * then be used to create an object and push it on the stack.
+     *
+     * @param clazz Java class name of the object creation factory class
+     * @param ignoreCreateExceptions if true, exceptions thrown by the
+     *  object creation factory
+     * will be ignored.
+     */
+    public FactoryCreateRule(Class<?> clazz, boolean ignoreCreateExceptions) {
+
+        this(clazz, null, ignoreCreateExceptions);
+
+    }
+
+
+    /**
+     * Construct a factory create rule that will use the specified
+     * class name (possibly overridden by the specified attribute if present)
+     * to create an {@link ObjectCreationFactory}, which will then be used
+     * to instantiate an object instance and push it onto the stack.
+     *
+     * @param className Default Java class name of the factory class
+     * @param attributeName Attribute name which, if present, contains an
+     *  override of the class name of the object creation factory to create.
+     * @param ignoreCreateExceptions if true, exceptions thrown by the object
+     *  creation factory will be ignored.
+     */
+    public FactoryCreateRule(
+                                String className, 
+                                String attributeName,
+                                boolean ignoreCreateExceptions) {
+
+        this.className = className;
+        this.attributeName = attributeName;
+        this.ignoreCreateExceptions = ignoreCreateExceptions;
+
+    }
+
+
+    /**
+     * Construct a factory create rule that will use the specified
+     * class (possibly overridden by the specified attribute if present)
+     * to create an {@link ObjectCreationFactory}, which will then be used
+     * to instantiate an object instance and push it onto the stack.
+     *
+     * @param clazz Default Java class name of the factory class
+     * @param attributeName Attribute name which, if present, contains an
+     *  override of the class name of the object creation factory to create.
+     * @param ignoreCreateExceptions if true, exceptions thrown by the object
+     *  creation factory will be ignored.
+     */
+    public FactoryCreateRule(
+                                Class<?> clazz, 
+                                String attributeName,
+                                boolean ignoreCreateExceptions) {
+
+        this(clazz.getName(), attributeName, ignoreCreateExceptions);
+
+    }
+
+
+    /**
+     * Construct a factory create rule using the given, already instantiated,
+     * {@link ObjectCreationFactory}.
+     *
+     * @param creationFactory called on to create the object.
+     * @param ignoreCreateExceptions if true, exceptions thrown by the object
+     *  creation factory will be ignored.
+     */
+    public FactoryCreateRule(
+                            ObjectCreationFactory creationFactory, 
+                            boolean ignoreCreateExceptions) {
+
+        this.creationFactory = creationFactory;
+        this.ignoreCreateExceptions = ignoreCreateExceptions;
+    }
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The attribute containing an override class name if it is present.
+     */
+    protected String attributeName = null;
+
+
+    /**
+     * The Java class name of the ObjectCreationFactory to be created.
+     * This class must have a no-arguments constructor.
+     */
+    protected String className = null;
+
+
+    /**
+     * The object creation factory we will use to instantiate objects
+     * as required based on the attributes specified in the matched XML
+     * element.
+     */
+    protected ObjectCreationFactory creationFactory = null;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Process the beginning of this element.
+     *
+     * @param attributes The attribute list of this element
+     */
+    @Override
+    public void begin(String namespace, String name, Attributes attributes) throws Exception {
+        
+        if (ignoreCreateExceptions) {
+        
+            if (exceptionIgnoredStack == null) {
+                exceptionIgnoredStack = new ArrayStack<Boolean>();
+            }
+            
+            try {
+                Object instance = getFactory(attributes).createObject(attributes);
+                
+                if (digester.log.isDebugEnabled()) {
+                    digester.log.debug("[FactoryCreateRule]{" + digester.match +
+                            "} New " + instance.getClass().getName());
+                }
+                digester.push(instance);
+                exceptionIgnoredStack.push(Boolean.FALSE);
+                
+            } catch (Exception e) {
+                // log message and error
+                if (digester.log.isInfoEnabled()) {
+                    digester.log.info("[FactoryCreateRule] Create exception ignored: " +
+                        ((e.getMessage() == null) ? e.getClass().getName() : e.getMessage()));
+                    if (digester.log.isDebugEnabled()) {
+                        digester.log.debug("[FactoryCreateRule] Ignored exception:", e);
+                    }
+                }
+                exceptionIgnoredStack.push(Boolean.TRUE);
+            }
+            
+        } else {
+            Object instance = getFactory(attributes).createObject(attributes);
+            
+            if (digester.log.isDebugEnabled()) {
+                digester.log.debug("[FactoryCreateRule]{" + digester.match +
+                        "} New " + instance.getClass().getName());
+            }
+            digester.push(instance);
+        }
+    }
+
+
+    /**
+     * Process the end of this element.
+     */
+    @Override
+    public void end(String namespace, String name) throws Exception {
+        
+        // check if object was created 
+        // this only happens if an exception was thrown and we're ignoring them
+        if (
+                ignoreCreateExceptions &&
+                exceptionIgnoredStack != null &&
+                !(exceptionIgnoredStack.empty())) {
+                
+            if ((exceptionIgnoredStack.pop()).booleanValue()) {
+                // creation exception was ignored
+                // nothing was put onto the stack
+                if (digester.log.isTraceEnabled()) {
+                    digester.log.trace("[FactoryCreateRule] No creation so no push so no pop");
+                }
+                return;
+            }
+        } 
+
+        Object top = digester.pop();
+        if (digester.log.isDebugEnabled()) {
+            digester.log.debug("[FactoryCreateRule]{" + digester.match +
+                    "} Pop " + top.getClass().getName());
+        }
+
+    }
+
+
+    /**
+     * Clean up after parsing is complete.
+     */
+    @Override
+    public void finish() throws Exception {
+
+        if (attributeName != null) {
+            creationFactory = null;
+        }
+
+    }
+
+
+    /**
+     * Render a printable version of this Rule.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("FactoryCreateRule[");
+        sb.append("className=");
+        sb.append(className);
+        sb.append(", attributeName=");
+        sb.append(attributeName);
+        if (creationFactory != null) {
+            sb.append(", creationFactory=");
+            sb.append(creationFactory);
+        }
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Return an instance of our associated object creation factory,
+     * creating one if necessary.
+     *
+     * @param attributes Attributes passed to our factory creation element
+     *
+     * @exception Exception if any error occurs
+     */
+    protected ObjectCreationFactory getFactory(Attributes attributes)
+            throws Exception {
+
+        if (creationFactory == null) {
+            String realClassName = className;
+            if (attributeName != null) {
+                String value = attributes.getValue(attributeName);
+                if (value != null) {
+                    realClassName = value;
+                }
+            }
+            if (digester.log.isDebugEnabled()) {
+                digester.log.debug("[FactoryCreateRule]{" + digester.match +
+                        "} New factory " + realClassName);
+            }
+            Class<?> clazz = digester.getClassLoader().loadClass(realClassName);
+            creationFactory = (ObjectCreationFactory)
+                    clazz.newInstance();
+            creationFactory.setDigester(digester);
+        }
+        return (creationFactory);
+
+    }    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/GenericParser.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/GenericParser.java
new file mode 100644
index 0000000..f75e671
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/GenericParser.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.tomcat.util.digester;
+
+import java.util.Properties;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotRecognizedException;
+
+/**
+ * Create a <code>SAXParser</code> configured to support XML Schema and DTD.
+ *
+ * @since 1.6
+ */
+
+public class GenericParser{
+
+    /**
+     * The Log to which all SAX event related logging calls will be made.
+     */
+    private static final Log log =
+        LogFactory.getLog("org.apache.commons.digester.Digester.sax");
+
+    /**
+     * The JAXP 1.2 property required to set up the schema location.
+     */
+    private static final String JAXP_SCHEMA_SOURCE =
+        "http://java.sun.com/xml/jaxp/properties/schemaSource";
+
+    /**
+     * The JAXP 1.2 property to set up the schemaLanguage used.
+     */
+    protected static String JAXP_SCHEMA_LANGUAGE =
+        "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
+
+    /**
+     * Create a <code>SAXParser</code> configured to support XML Schema and DTD
+     * @param properties parser specific properties/features
+     * @return an XML Schema/DTD enabled <code>SAXParser</code>
+     */
+    public static SAXParser newSAXParser(Properties properties)
+            throws ParserConfigurationException, 
+                   SAXException,
+                   SAXNotRecognizedException{ 
+
+        SAXParserFactory factory = 
+                        (SAXParserFactory)properties.get("SAXParserFactory");
+        SAXParser parser = factory.newSAXParser();
+        String schemaLocation = (String)properties.get("schemaLocation");
+        String schemaLanguage = (String)properties.get("schemaLanguage");
+
+        try{
+            if (schemaLocation != null) {
+                parser.setProperty(JAXP_SCHEMA_LANGUAGE, schemaLanguage);
+                parser.setProperty(JAXP_SCHEMA_SOURCE, schemaLocation);
+            }
+        } catch (SAXNotRecognizedException e){
+            log.info(parser.getClass().getName() + ": "  
+                                        + e.getMessage() + " not supported."); 
+        }
+        return parser;
+    }
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/NodeCreateRule.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/NodeCreateRule.java
new file mode 100644
index 0000000..c6dd072
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/NodeCreateRule.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.tomcat.util.digester;
+
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+
+/**
+ * A rule implementation that creates a DOM
+ * {@link org.w3c.dom.Node Node} containing the XML at the element that matched
+ * the rule. Two concrete types of nodes can be created by this rule:
+ * <ul>
+ *   <li>the default is to create an {@link org.w3c.dom.Element Element} node.
+ *   The created element will correspond to the element that matched the rule,
+ *   containing all XML content underneath that element.</li>
+ *   <li>alternatively, this rule can create nodes of type
+ *   {@link org.w3c.dom.DocumentFragment DocumentFragment}, which will contain
+ *   only the XML content under the element the rule was triggered on.</li>
+ * </ul>
+ * The created node will be normalized, meaning it will not contain text nodes 
+ * that only contain white space characters.
+ * 
+
+ * 
+ * <p>The created <code>Node</code> will be pushed on Digester's object stack
+ * when done. To use it in the context of another DOM
+ * {@link org.w3c.dom.Document Document}, it must be imported first, using the
+ * Document method
+ * {@link org.w3c.dom.Document#importNode(org.w3c.dom.Node, boolean) importNode()}.
+ * </p>
+ *
+ * <p><strong>Important Note:</strong> This is implemented by replacing the SAX
+ * {@link org.xml.sax.ContentHandler ContentHandler} in the parser used by 
+ * Digester, and resetting it when the matched element is closed. As a side 
+ * effect, rules that would match XML nodes under the element that matches 
+ * a <code>NodeCreateRule</code> will never be triggered by Digester, which 
+ * usually is the behavior one would expect.</p>
+ * 
+ * <p><strong>Note</strong> that the current implementation does not set the namespace prefixes
+ * in the exported nodes. The (usually more important) namespace URIs are set,
+ * of course.</p>
+ *
+ * @since Digester 1.4
+ */
+
+public class NodeCreateRule extends Rule {
+
+
+    // ---------------------------------------------------------- Inner Classes
+
+
+    /**
+     * The SAX content handler that does all the actual work of assembling the 
+     * DOM node tree from the SAX events.
+     */
+    private class NodeBuilder
+        extends DefaultHandler {
+
+
+        // ------------------------------------------------------- Constructors
+
+
+        /**
+         * Constructor.
+         * 
+         * <p>Stores the content handler currently used by Digester so it can 
+         * be reset when done, and initializes the DOM objects needed to 
+         * build the node.</p>
+         * 
+         * @param doc the document to use to create nodes
+         * @param root the root node
+         * @throws ParserConfigurationException if the DocumentBuilderFactory 
+         *   could not be instantiated
+         * @throws SAXException if the XMLReader could not be instantiated by 
+         *   Digester (should not happen)
+         */
+        public NodeBuilder(Document doc, Node root)
+            throws ParserConfigurationException, SAXException {
+
+            this.doc = doc;
+            this.root = root;
+            this.top = root;
+            
+            oldContentHandler = digester.getXMLReader().getContentHandler();
+
+        }
+
+
+        // ------------------------------------------------- Instance Variables
+
+
+        /**
+         * The content handler used by Digester before it was set to this 
+         * content handler.
+         */
+        protected ContentHandler oldContentHandler = null;
+
+
+        /**
+         * Depth of the current node, relative to the element where the content
+         * handler was put into action.
+         */
+        protected int depth = 0;
+
+
+        /**
+         * A DOM Document used to create the various Node instances.
+         */
+        protected Document doc = null;
+
+
+        /**
+         * The DOM node that will be pushed on Digester's stack.
+         */
+        protected Node root = null;
+
+
+        /**
+         * The current top DOM mode.
+         */
+        protected Node top = null;
+
+
+        // --------------------------------------------- ContentHandler Methods
+
+
+        /**
+         * Appends a {@link org.w3c.dom.Text Text} node to the current node.
+         * 
+         * @param ch the characters from the XML document
+         * @param start the start position in the array
+         * @param length the number of characters to read from the array
+         * @throws SAXException if the DOM implementation throws an exception
+         */
+        @Override
+        public void characters(char[] ch, int start, int length)
+            throws SAXException {
+
+            try {
+                String str = new String(ch, start, length);
+                if (str.trim().length() > 0) { 
+                    top.appendChild(doc.createTextNode(str));
+                }
+            } catch (DOMException e) {
+                throw new SAXException(e.getMessage(), e);
+            }
+
+        }
+
+
+        /**
+         * Checks whether control needs to be returned to Digester.
+         * 
+         * @param namespaceURI the namespace URI
+         * @param localName the local name
+         * @param qName the qualified (prefixed) name
+         * @throws SAXException if the DOM implementation throws an exception
+         */
+        @Override
+        public void endElement(String namespaceURI, String localName,
+                               String qName)
+            throws SAXException {
+            
+            try {
+                if (depth == 0) {
+                    getDigester().getXMLReader().setContentHandler(
+                        oldContentHandler);
+                    getDigester().push(root);
+                    getDigester().endElement(namespaceURI, localName, qName);
+                }
+    
+                top = top.getParentNode();
+                depth--;
+            } catch (DOMException e) {
+                throw new SAXException(e.getMessage(), e);
+            }
+
+        }
+
+
+        /**
+         * Adds a new
+         * {@link org.w3c.dom.ProcessingInstruction ProcessingInstruction} to 
+         * the current node.
+         * 
+         * @param target the processing instruction target
+         * @param data the processing instruction data, or null if none was 
+         *   supplied
+         * @throws SAXException if the DOM implementation throws an exception
+         */
+        @Override
+        public void processingInstruction(String target, String data)
+            throws SAXException {
+            
+            try {
+                top.appendChild(doc.createProcessingInstruction(target, data));
+            } catch (DOMException e) {
+                throw new SAXException(e.getMessage(), e);
+            }
+
+        }
+
+
+        /**
+         * Adds a new child {@link org.w3c.dom.Element Element} to the current
+         * node.
+         * 
+         * @param namespaceURI the namespace URI
+         * @param localName the local name
+         * @param qName the qualified (prefixed) name
+         * @param atts the list of attributes
+         * @throws SAXException if the DOM implementation throws an exception
+         */
+        @Override
+        public void startElement(String namespaceURI, String localName,
+                                 String qName, Attributes atts)
+            throws SAXException {
+
+            try {
+                Node previousTop = top;
+                if ((localName == null) || (localName.length() == 0)) { 
+                    top = doc.createElement(qName);
+                } else {
+                    top = doc.createElementNS(namespaceURI, localName);
+                }
+                for (int i = 0; i < atts.getLength(); i++) {
+                    Attr attr = null;
+                    if ((atts.getLocalName(i) == null) ||
+                        (atts.getLocalName(i).length() == 0)) {
+                        attr = doc.createAttribute(atts.getQName(i));
+                        attr.setNodeValue(atts.getValue(i));
+                        ((Element)top).setAttributeNode(attr);
+                    } else {
+                        attr = doc.createAttributeNS(atts.getURI(i),
+                                                     atts.getLocalName(i));
+                        attr.setNodeValue(atts.getValue(i));
+                        ((Element)top).setAttributeNodeNS(attr);
+                    }
+                }
+                previousTop.appendChild(top);
+                depth++;
+            } catch (DOMException e) {
+                throw new SAXException(e.getMessage(), e);
+            }
+
+        }
+
+    }
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Default constructor. Creates an instance of this rule that will create a
+     * DOM {@link org.w3c.dom.Element Element}.
+     */
+    public NodeCreateRule() throws ParserConfigurationException {
+
+        this(Node.ELEMENT_NODE);
+
+    }
+
+
+    /**
+     * Constructor. Creates an instance of this rule that will create a DOM
+     * {@link org.w3c.dom.Element Element}, but lets you specify the JAXP 
+     * <code>DocumentBuilder</code> that should be used when constructing the
+     * node tree.
+     * 
+     * @param documentBuilder the JAXP <code>DocumentBuilder</code> to use
+     */
+    public NodeCreateRule(DocumentBuilder documentBuilder) {
+
+        this(Node.ELEMENT_NODE, documentBuilder);
+
+    }
+
+
+    /**
+     * Constructor. Creates an instance of this rule that will create either a 
+     * DOM {@link org.w3c.dom.Element Element} or a DOM 
+     * {@link org.w3c.dom.DocumentFragment DocumentFragment}, depending on the
+     * value of the <code>nodeType</code> parameter.
+     * 
+     * @param nodeType the type of node to create, which can be either
+     *   {@link org.w3c.dom.Node#ELEMENT_NODE Node.ELEMENT_NODE} or 
+     *   {@link org.w3c.dom.Node#DOCUMENT_FRAGMENT_NODE Node.DOCUMENT_FRAGMENT_NODE}
+     * @throws IllegalArgumentException if the node type is not supported
+     */
+    public NodeCreateRule(int nodeType) throws ParserConfigurationException {
+
+        this(nodeType,
+             DocumentBuilderFactory.newInstance().newDocumentBuilder());
+
+    }
+
+
+    /**
+     * Constructor. Creates an instance of this rule that will create either a 
+     * DOM {@link org.w3c.dom.Element Element} or a DOM 
+     * {@link org.w3c.dom.DocumentFragment DocumentFragment}, depending on the
+     * value of the <code>nodeType</code> parameter. This constructor lets you
+     * specify the JAXP <code>DocumentBuilder</code> that should be used when
+     * constructing the node tree.
+     * 
+     * @param nodeType the type of node to create, which can be either
+     *   {@link org.w3c.dom.Node#ELEMENT_NODE Node.ELEMENT_NODE} or 
+     *   {@link org.w3c.dom.Node#DOCUMENT_FRAGMENT_NODE Node.DOCUMENT_FRAGMENT_NODE}
+     * @param documentBuilder the JAXP <code>DocumentBuilder</code> to use
+     * @throws IllegalArgumentException if the node type is not supported
+     */
+    public NodeCreateRule(int nodeType, DocumentBuilder documentBuilder) {
+
+        if (!((nodeType == Node.DOCUMENT_FRAGMENT_NODE) ||
+              (nodeType == Node.ELEMENT_NODE))) {
+            throw new IllegalArgumentException(
+                "Can only create nodes of type DocumentFragment and Element");
+        }
+        this.nodeType = nodeType;
+        this.documentBuilder = documentBuilder;
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The JAXP <code>DocumentBuilder</code> to use.
+     */
+    private DocumentBuilder documentBuilder = null;
+
+
+    /**
+     * The type of the node that should be created. Must be one of the
+     * constants defined in {@link org.w3c.dom.Node Node}, but currently only
+     * {@link org.w3c.dom.Node#ELEMENT_NODE Node.ELEMENT_NODE} and 
+     * {@link org.w3c.dom.Node#DOCUMENT_FRAGMENT_NODE Node.DOCUMENT_FRAGMENT_NODE}
+     * are allowed values.
+     */
+    private int nodeType = Node.ELEMENT_NODE;
+
+
+    // ----------------------------------------------------------- Rule Methods
+
+
+    /**
+     * Implemented to replace the content handler currently in use by a 
+     * NodeBuilder.
+     * 
+     * @param namespaceURI the namespace URI of the matching element, or an 
+     *   empty string if the parser is not namespace aware or the element has
+     *   no namespace
+     * @param name the local name if the parser is namespace aware, or just 
+     *   the element name otherwise
+     * @param attributes The attribute list of this element
+     * @throws Exception indicates a JAXP configuration problem
+     */
+    @Override
+    public void begin(String namespaceURI, String name, Attributes attributes)
+        throws Exception {
+
+        XMLReader xmlReader = getDigester().getXMLReader();
+        Document doc = documentBuilder.newDocument();
+        NodeBuilder builder = null;
+        if (nodeType == Node.ELEMENT_NODE) {
+            Element element = null;
+            if (getDigester().getNamespaceAware()) {
+                element =
+                    doc.createElementNS(namespaceURI, name);
+                for (int i = 0; i < attributes.getLength(); i++) {
+                    element.setAttributeNS(attributes.getURI(i),
+                                           attributes.getLocalName(i),
+                                           attributes.getValue(i));
+                }
+            } else {
+                element = doc.createElement(name);
+                for (int i = 0; i < attributes.getLength(); i++) {
+                    element.setAttribute(attributes.getQName(i),
+                                         attributes.getValue(i));
+                }
+            }
+            builder = new NodeBuilder(doc, element);
+        } else {
+            builder = new NodeBuilder(doc, doc.createDocumentFragment());
+        }
+        xmlReader.setContentHandler(builder);
+
+    }
+
+
+    /**
+     * Pop the Node off the top of the stack.
+     * 
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the parser is not namespace aware or the element has
+     *   no namespace
+     * @param name the local name if the parser is namespace aware, or just 
+     *   the element name otherwise
+     */
+    @Override
+    public void end(String namespace, String name) throws Exception {
+
+        digester.pop();
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/ObjectCreateRule.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/ObjectCreateRule.java
new file mode 100644
index 0000000..a745b76
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/ObjectCreateRule.java
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+
+package org.apache.tomcat.util.digester;
+
+
+import org.xml.sax.Attributes;
+
+
+/**
+ * Rule implementation that creates a new object and pushes it
+ * onto the object stack.  When the element is complete, the
+ * object will be popped
+ */
+
+public class ObjectCreateRule extends Rule {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct an object create rule with the specified class name.
+     *
+     * @param className Java class name of the object to be created
+     */
+    public ObjectCreateRule(String className) {
+
+        this(className, (String) null);
+
+    }
+
+
+    /**
+     * Construct an object create rule with the specified class.
+     *
+     * @param clazz Java class name of the object to be created
+     */
+    public ObjectCreateRule(Class<?> clazz) {
+
+        this(clazz.getName(), (String) null);
+
+    }
+
+
+    /**
+     * Construct an object create rule with the specified class name and an
+     * optional attribute name containing an override.
+     *
+     * @param className Java class name of the object to be created
+     * @param attributeName Attribute name which, if present, contains an
+     *  override of the class name to create
+     */
+    public ObjectCreateRule(String className,
+                            String attributeName) {
+
+        this.className = className;
+        this.attributeName = attributeName;
+
+    }
+
+
+    /**
+     * Construct an object create rule with the specified class and an
+     * optional attribute name containing an override.
+     *
+     * @param attributeName Attribute name which, if present, contains an
+     * @param clazz Java class name of the object to be created
+     *  override of the class name to create
+     */
+    public ObjectCreateRule(String attributeName,
+                            Class<?> clazz) {
+
+        this(clazz.getName(), attributeName);
+
+    }
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The attribute containing an override class name if it is present.
+     */
+    protected String attributeName = null;
+
+
+    /**
+     * The Java class name of the object to be created.
+     */
+    protected String className = null;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Process the beginning of this element.
+     *
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the parser is not namespace aware or the element has
+     *   no namespace
+     * @param name the local name if the parser is namespace aware, or just 
+     *   the element name otherwise
+     * @param attributes The attribute list for this element
+     */
+    @Override
+    public void begin(String namespace, String name, Attributes attributes)
+            throws Exception {
+
+        // Identify the name of the class to instantiate
+        String realClassName = className;
+        if (attributeName != null) {
+            String value = attributes.getValue(attributeName);
+            if (value != null) {
+                realClassName = value;
+            }
+        }
+        if (digester.log.isDebugEnabled()) {
+            digester.log.debug("[ObjectCreateRule]{" + digester.match +
+                    "}New " + realClassName);
+        }
+
+        if (realClassName == null) {
+            throw new NullPointerException("No class name specified for " +
+                    namespace + " " + name);
+        }
+
+        // Instantiate the new object and push it on the context stack
+        Class<?> clazz = digester.getClassLoader().loadClass(realClassName);
+        Object instance = clazz.newInstance();
+        digester.push(instance);
+    }
+
+
+    /**
+     * Process the end of this element.
+     * 
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the parser is not namespace aware or the element has
+     *   no namespace
+     * @param name the local name if the parser is namespace aware, or just 
+     *   the element name otherwise
+     */
+    @Override
+    public void end(String namespace, String name) throws Exception {
+
+        Object top = digester.pop();
+        if (digester.log.isDebugEnabled()) {
+            digester.log.debug("[ObjectCreateRule]{" + digester.match +
+                    "} Pop " + top.getClass().getName());
+        }
+
+    }
+
+
+    /**
+     * Render a printable version of this Rule.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("ObjectCreateRule[");
+        sb.append("className=");
+        sb.append(className);
+        sb.append(", attributeName=");
+        sb.append(attributeName);
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/ObjectCreationFactory.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/ObjectCreationFactory.java
new file mode 100644
index 0000000..72c84be
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/ObjectCreationFactory.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.tomcat.util.digester;
+
+
+import org.xml.sax.Attributes;
+
+/**
+ * <p> Interface for use with {@link FactoryCreateRule}.
+ * The rule calls {@link #createObject} to create an object
+ * to be pushed onto the <code>Digester</code> stack
+ * whenever it is matched.</p>
+ *
+ * <p> {@link AbstractObjectCreationFactory} is an abstract
+ * implementation suitable for creating anonymous
+ * <code>ObjectCreationFactory</code> implementations.
+ */
+public interface ObjectCreationFactory {
+
+    /**
+     * <p>Factory method called by {@link FactoryCreateRule} to supply an
+     * object based on the element's attributes.
+     *
+     * @param attributes the element's attributes
+     *
+     * @throws Exception any exception thrown will be propagated upwards
+     */
+    public Object createObject(Attributes attributes) throws Exception;
+
+    /**
+     * <p>Returns the {@link Digester} that was set by the
+     * {@link FactoryCreateRule} upon initialization.
+     */
+    public Digester getDigester();
+
+    /**
+     * <p>Set the {@link Digester} to allow the implementation to do logging,
+     * classloading based on the digester's classloader, etc.
+     *
+     * @param digester parent Digester object
+     */
+    public void setDigester(Digester digester);
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/ObjectParamRule.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/ObjectParamRule.java
new file mode 100644
index 0000000..81e6924
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/ObjectParamRule.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+
+package org.apache.tomcat.util.digester;
+
+import org.xml.sax.Attributes;
+
+/**
+ * <p>Rule implementation that saves a parameter for use by a surrounding
+ * <code>CallMethodRule<code>.</p>
+ *
+ * <p>This parameter may be:
+ * <ul>
+ * <li>an arbitrary Object defined programatically, assigned when the element pattern associated with the Rule is matched
+ * See {@link #ObjectParamRule(int paramIndex, Object param)}
+ * <li>an arbitrary Object defined programatically, assigned if the element pattern AND specified attribute name are matched
+ * See {@link #ObjectParamRule(int paramIndex, String attributeName, Object param)}
+ * </ul>
+ * </p>
+ *
+ * @since 1.4
+ */
+
+public class ObjectParamRule extends Rule {
+    // ----------------------------------------------------------- Constructors
+    /**
+     * Construct a "call parameter" rule that will save the given Object as
+     * the parameter value.
+     *
+     * @param paramIndex The zero-relative parameter number
+     * @param param the parameter to pass along
+     */
+    public ObjectParamRule(int paramIndex, Object param) {
+        this(paramIndex, null, param);
+    }
+
+
+    /**
+     * Construct a "call parameter" rule that will save the given Object as
+     * the parameter value, provided that the specified attribute exists.
+     *
+     * @param paramIndex The zero-relative parameter number
+     * @param attributeName The name of the attribute to match
+     * @param param the parameter to pass along
+     */
+    public ObjectParamRule(int paramIndex, String attributeName, Object param) {
+        this.paramIndex = paramIndex;
+        this.attributeName = attributeName;
+        this.param = param;
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * The attribute which we are attempting to match
+     */
+    protected String attributeName = null;
+
+    /**
+     * The zero-relative index of the parameter we are saving.
+     */
+    protected int paramIndex = 0;
+
+    /**
+     * The parameter we wish to pass to the method call
+     */
+    protected Object param = null;
+
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Process the start of this element.
+     *
+     * @param attributes The attribute list for this element
+     */
+    @Override
+    public void begin(String namespace, String name,
+                      Attributes attributes) throws Exception {
+        Object anAttribute = null;
+        Object parameters[] = (Object[]) digester.peekParams();
+
+        if (attributeName != null) {
+            anAttribute = attributes.getValue(attributeName);
+            if(anAttribute != null) {
+                parameters[paramIndex] = param;
+            }
+            // note -- if attributeName != null and anAttribute == null, this rule
+            // will pass null as its parameter!
+        }else{
+            parameters[paramIndex] = param;
+        }
+    }
+
+    /**
+     * Render a printable version of this Rule.
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder("ObjectParamRule[");
+        sb.append("paramIndex=");
+        sb.append(paramIndex);
+        sb.append(", attributeName=");
+        sb.append(attributeName);
+        sb.append(", param=");
+        sb.append(param);
+        sb.append("]");
+        return (sb.toString());
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/ParserFeatureSetterFactory.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/ParserFeatureSetterFactory.java
new file mode 100644
index 0000000..0084cc2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/ParserFeatureSetterFactory.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.tomcat.util.digester;
+
+import java.util.Properties;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+
+/**
+ * Creates a <code>SAXParser</code> based on the underlying parser.
+ * Allows logical properties depending on logical parser versions
+ * to be set.
+ *
+ * @since 1.6
+ */
+public class ParserFeatureSetterFactory{
+
+    /**
+     * <code>true</code> is Xerces is used.
+     */
+    private static boolean isXercesUsed; 
+
+    static {
+        try{
+            // Use reflection to avoid a build dependency with Xerces.
+            Class.forName("org.apache.xerces.impl.Version");
+            isXercesUsed = true;
+        } catch (Exception ex){
+            isXercesUsed = false;
+        }
+    }
+
+    /**
+     * Create a new <code>SAXParser</code>
+     * @param properties (logical) properties to be set on parser
+     * @return a <code>SAXParser</code> configured based on the underlying
+     * parser implementation.
+     */
+    public static SAXParser newSAXParser(Properties properties)
+            throws ParserConfigurationException, 
+                   SAXException,
+                   SAXNotRecognizedException, 
+                   SAXNotSupportedException {
+
+        if (isXercesUsed){
+            return XercesParser.newSAXParser(properties);
+        } else {
+            return GenericParser.newSAXParser(properties);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/PathCallParamRule.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/PathCallParamRule.java
new file mode 100644
index 0000000..30fb5b3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/PathCallParamRule.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+
+package org.apache.tomcat.util.digester;
+
+
+import org.xml.sax.Attributes;
+
+/**
+ * <p>Rule implementation that saves a parameter containing the 
+ * <code>Digester</code> matching path for use by a surrounding 
+ * <code>CallMethodRule</code>. This Rule is most useful when making 
+ * extensive use of wildcards in rule patterns.</p>
+ *
+ * @since 1.6
+ */
+
+public class PathCallParamRule extends Rule {
+
+    // ----------------------------------------------------------- Constructors
+
+    /**
+     * Construct a "call parameter" rule that will save the body text of this
+     * element as the parameter value.
+     *
+     * @param paramIndex The zero-relative parameter number
+     */
+    public PathCallParamRule(int paramIndex) {
+
+        this.paramIndex = paramIndex;
+
+    }
+ 
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * The zero-relative index of the parameter we are saving.
+     */
+    protected int paramIndex = 0;
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Process the start of this element.
+     *
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the parser is not namespace aware or the element has
+     *   no namespace
+     * @param name the local name if the parser is namespace aware, or just 
+     *   the element name otherwise
+     * @param attributes The attribute list for this element
+
+     */
+    @Override
+    public void begin(String namespace, String name, Attributes attributes) throws Exception {
+
+        String param = getDigester().getMatch();
+        
+        if(param != null) {
+            Object parameters[] = (Object[]) digester.peekParams();
+            parameters[paramIndex] = param;
+        }
+        
+    }
+
+    /**
+     * Render a printable version of this Rule.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("PathCallParamRule[");
+        sb.append("paramIndex=");
+        sb.append(paramIndex);
+        sb.append("]");
+        return (sb.toString());
+
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/Rule.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/Rule.java
new file mode 100644
index 0000000..5daef56
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/Rule.java
@@ -0,0 +1,226 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+
+package org.apache.tomcat.util.digester;
+
+
+import org.xml.sax.Attributes;
+
+
+/**
+ * Concrete implementations of this class implement actions to be taken when
+ * a corresponding nested pattern of XML elements has been matched.
+ */
+
+public abstract class Rule {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * <p>Base constructor.
+     * Now the digester will be set when the rule is added.</p>
+     */
+    public Rule() {}
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The Digester with which this Rule is associated.
+     */
+    protected Digester digester = null;
+
+
+    /**
+     * The namespace URI for which this Rule is relevant, if any.
+     */
+    protected String namespaceURI = null;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the Digester with which this Rule is associated.
+     */
+    public Digester getDigester() {
+
+        return (this.digester);
+
+    }
+    
+    /**
+     * Set the <code>Digester</code> with which this <code>Rule</code> is associated.
+     */
+    public void setDigester(Digester digester) {
+        
+        this.digester = digester;
+        
+    }
+
+    /**
+     * Return the namespace URI for which this Rule is relevant, if any.
+     */
+    public String getNamespaceURI() {
+
+        return (this.namespaceURI);
+
+    }
+
+
+    /**
+     * Set the namespace URI for which this Rule is relevant, if any.
+     *
+     * @param namespaceURI Namespace URI for which this Rule is relevant,
+     *  or <code>null</code> to match independent of namespace.
+     */
+    public void setNamespaceURI(String namespaceURI) {
+
+        this.namespaceURI = namespaceURI;
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * This method is called when the beginning of a matching XML element
+     * is encountered.
+     *
+     * @param attributes The attribute list of this element
+     * @deprecated Use the {@link #begin(String,String,Attributes) begin}
+     *   method with <code>namespace</code> and <code>name</code>
+     *   parameters instead.
+     */
+    @Deprecated
+    public void begin(Attributes attributes) throws Exception {
+        // The default implementation does nothing
+    }
+
+
+    /**
+     * This method is called when the beginning of a matching XML element
+     * is encountered. The default implementation delegates to the deprecated
+     * method {@link #begin(Attributes) begin} without the 
+     * <code>namespace</code> and <code>name</code> parameters, to retain 
+     * backwards compatibility.
+     *
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the parser is not namespace aware or the element has
+     *   no namespace
+     * @param name the local name if the parser is namespace aware, or just 
+     *   the element name otherwise
+     * @param attributes The attribute list of this element
+     * @since Digester 1.4
+     */
+    public void begin(String namespace, String name, Attributes attributes)
+        throws Exception {
+
+        begin(attributes);
+
+    }
+
+
+    /**
+     * This method is called when the body of a matching XML element
+     * is encountered.  If the element has no body, this method is
+     * not called at all.
+     *
+     * @param text The text of the body of this element
+     * @deprecated Use the {@link #body(String,String,String) body} method
+     *   with <code>namespace</code> and <code>name</code> parameters
+     *   instead.
+     */
+    @Deprecated
+    public void body(String text) throws Exception {
+        // The default implementation does nothing
+    }
+
+
+    /**
+     * This method is called when the body of a matching XML element is 
+     * encountered.  If the element has no body, this method is not called at 
+     * all. The default implementation delegates to the deprecated method 
+     * {@link #body(String) body} without the <code>namespace</code> and
+     * <code>name</code> parameters, to retain backwards compatibility.
+     *
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the parser is not namespace aware or the element has
+     *   no namespace
+     * @param name the local name if the parser is namespace aware, or just 
+     *   the element name otherwise
+     * @param text The text of the body of this element
+     * @since Digester 1.4
+     */
+    public void body(String namespace, String name, String text)
+        throws Exception {
+
+        body(text);
+
+    }
+
+
+    /**
+     * This method is called when the end of a matching XML element
+     * is encountered.
+     * 
+     * @deprecated Use the {@link #end(String,String) end} method with 
+     *   <code>namespace</code> and <code>name</code> parameters instead.
+     */
+    @Deprecated
+    public void end() throws Exception {
+        // The default implementation does nothing
+    }
+
+
+    /**
+     * This method is called when the end of a matching XML element
+     * is encountered. The default implementation delegates to the deprecated
+     * method {@link #end end} without the 
+     * <code>namespace</code> and <code>name</code> parameters, to retain 
+     * backwards compatibility.
+     *
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the parser is not namespace aware or the element has
+     *   no namespace
+     * @param name the local name if the parser is namespace aware, or just 
+     *   the element name otherwise
+     * @since Digester 1.4
+     */
+    public void end(String namespace, String name)
+        throws Exception {
+
+        end();
+
+    }
+
+
+    /**
+     * This method is called after all parsing methods have been
+     * called, to allow Rules to remove temporary data.
+     */
+    public void finish() throws Exception {
+        // The default implementation does nothing
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/RuleSet.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/RuleSet.java
new file mode 100644
index 0000000..e987ea4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/RuleSet.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.digester;
+
+
+/**
+ * <p>Public interface defining a shorthand means of configuring a complete
+ * set of related <code>Rule</code> definitions, possibly associated with
+ * a particular namespace URI, in one operation.  To use an instance of a
+ * class that implements this interface:</p>
+ * <ul>
+ * <li>Create a concrete implementation of this interface.</li>
+ * <li>Optionally, you can configure a <code>RuleSet</code> to be relevant
+ *     only for a particular namespace URI by configuring the value to be
+ *     returned by <code>getNamespaceURI()</code>.</li>
+ * <li>As you are configuring your Digester instance, call
+ *     <code>digester.addRuleSet()</code> and pass the RuleSet instance.</li>
+ * <li>Digester will call the <code>addRuleInstances()</code> method of
+ *     your RuleSet to configure the necessary rules.</li>
+ * </ul>
+ */
+
+public interface RuleSet {
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the namespace URI that will be applied to all Rule instances
+     * created from this RuleSet.
+     */
+    public String getNamespaceURI();
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * 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.
+     *
+     * @param digester Digester instance to which the new Rule instances
+     *  should be added.
+     */
+    public void addRuleInstances(Digester digester);
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/RuleSetBase.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/RuleSetBase.java
new file mode 100644
index 0000000..c8adaab
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/RuleSetBase.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+
+package org.apache.tomcat.util.digester;
+
+
+/**
+ * <p>Convenience base class that implements the {@link RuleSet} interface.
+ * Concrete implementations should list all of their actual rule creation
+ * logic in the <code>addRuleSet()</code> implementation.</p>
+ */
+
+public abstract class RuleSetBase implements RuleSet {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The namespace URI that all Rule instances created by this RuleSet
+     * will be associated with.
+     */
+    protected String namespaceURI = null;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the namespace URI that will be applied to all Rule instances
+     * created from this RuleSet.
+     */
+    public String getNamespaceURI() {
+
+        return (this.namespaceURI);
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * 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.
+     *
+     * @param digester Digester instance to which the new Rule instances
+     *  should be added.
+     */
+    public abstract void addRuleInstances(Digester digester);
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/Rules.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/Rules.java
new file mode 100644
index 0000000..0f4f4e2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/Rules.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.tomcat.util.digester;
+
+
+import java.util.List;
+
+
+/**
+ * Public interface defining a collection of Rule instances (and corresponding
+ * matching patterns) plus an implementation of a matching policy that selects
+ * the rules that match a particular pattern of nested elements discovered
+ * during parsing.
+ */
+
+public interface Rules {
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the Digester instance with which this Rules instance is
+     * associated.
+     */
+    public Digester getDigester();
+
+
+    /**
+     * Set the Digester instance with which this Rules instance is associated.
+     *
+     * @param digester The newly associated Digester instance
+     */
+    public void setDigester(Digester digester);
+
+
+    /**
+     * Return the namespace URI that will be applied to all subsequently
+     * added <code>Rule</code> objects.
+     */
+    public String getNamespaceURI();
+
+
+    /**
+     * Set the namespace URI that will be applied to all subsequently
+     * added <code>Rule</code> objects.
+     *
+     * @param namespaceURI Namespace URI that must match on all
+     *  subsequently added rules, or <code>null</code> for matching
+     *  regardless of the current namespace URI
+     */
+    public void setNamespaceURI(String namespaceURI);
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Register a new Rule instance matching the specified pattern.
+     *
+     * @param pattern Nesting pattern to be matched for this Rule
+     * @param rule Rule instance to be registered
+     */
+    public void add(String pattern, Rule rule);
+
+
+    /**
+     * Clear all existing Rule instance registrations.
+     */
+    public void clear();
+
+
+    /**
+     * Return a List of all registered Rule instances that match the specified
+     * nesting pattern, or a zero-length List if there are no matches.  If more
+     * than one Rule instance matches, they <strong>must</strong> be returned
+     * in the order originally registered through the <code>add()</code>
+     * method.
+     *
+     * @param namespaceURI Namespace URI for which to select matching rules,
+     *  or <code>null</code> to match regardless of namespace URI
+     * @param pattern Nesting pattern to be matched
+     */
+    public List<Rule> match(String namespaceURI, String pattern);
+
+
+    /**
+     * Return a List of all registered Rule instances, or a zero-length List
+     * if there are no registered Rule instances.  If more than one Rule
+     * instance has been registered, they <strong>must</strong> be returned
+     * in the order originally registered through the <code>add()</code>
+     * method.
+     */
+    public List<Rule> rules();
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/RulesBase.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/RulesBase.java
new file mode 100644
index 0000000..e04c284
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/RulesBase.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.digester;
+
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+
+/**
+ * <p>Default implementation of the <code>Rules</code> interface that supports
+ * the standard rule matching behavior.  This class can also be used as a
+ * base class for specialized <code>Rules</code> implementations.</p>
+ *
+ * <p>The matching policies implemented by this class support two different
+ * types of pattern matching rules:</p>
+ * <ul>
+ * <li><em>Exact Match</em> - A pattern "a/b/c" exactly matches a
+ *     <code>&lt;c&gt;</code> element, nested inside a <code>&lt;b&gt;</code>
+ *     element, which is nested inside an <code>&lt;a&gt;</code> element.</li>
+ * <li><em>Tail Match</em> - A pattern "&#42;/a/b" matches a
+ *     <code>&lt;b&gt;</code> element, nested inside an <code>&lt;a&gt;</code>
+ *      element, no matter how deeply the pair is nested.</li>
+ * </ul>
+ */
+
+public class RulesBase implements Rules {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The set of registered Rule instances, keyed by the matching pattern.
+     * Each value is a List containing the Rules for that pattern, in the
+     * order that they were originally registered.
+     */
+    protected HashMap<String,List<Rule>> cache =
+        new HashMap<String,List<Rule>>();
+
+
+    /**
+     * The Digester instance with which this Rules instance is associated.
+     */
+    protected Digester digester = null;
+
+
+    /**
+     * The namespace URI for which subsequently added <code>Rule</code>
+     * objects are relevant, or <code>null</code> for matching independent
+     * of namespaces.
+     */
+    protected String namespaceURI = null;
+
+
+    /**
+     * The set of registered Rule instances, in the order that they were
+     * originally registered.
+     */
+    protected ArrayList<Rule> rules = new ArrayList<Rule>();
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the Digester instance with which this Rules instance is
+     * associated.
+     */
+    public Digester getDigester() {
+
+        return (this.digester);
+
+    }
+
+
+    /**
+     * Set the Digester instance with which this Rules instance is associated.
+     *
+     * @param digester The newly associated Digester instance
+     */
+    public void setDigester(Digester digester) {
+
+        this.digester = digester;
+        Iterator<Rule> items = rules.iterator();
+        while (items.hasNext()) {
+            Rule item = items.next();
+            item.setDigester(digester);
+        }
+
+    }
+
+
+    /**
+     * Return the namespace URI that will be applied to all subsequently
+     * added <code>Rule</code> objects.
+     */
+    public String getNamespaceURI() {
+
+        return (this.namespaceURI);
+
+    }
+
+
+    /**
+     * Set the namespace URI that will be applied to all subsequently
+     * added <code>Rule</code> objects.
+     *
+     * @param namespaceURI Namespace URI that must match on all
+     *  subsequently added rules, or <code>null</code> for matching
+     *  regardless of the current namespace URI
+     */
+    public void setNamespaceURI(String namespaceURI) {
+
+        this.namespaceURI = namespaceURI;
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Register a new Rule instance matching the specified pattern.
+     *
+     * @param pattern Nesting pattern to be matched for this Rule
+     * @param rule Rule instance to be registered
+     */
+    public void add(String pattern, Rule rule) {
+        // to help users who accidently add '/' to the end of their patterns
+        int patternLength = pattern.length();
+        if (patternLength>1 && pattern.endsWith("/")) {
+            pattern = pattern.substring(0, patternLength-1);
+        }
+        
+        
+        List<Rule> list = cache.get(pattern);
+        if (list == null) {
+            list = new ArrayList<Rule>();
+            cache.put(pattern, list);
+        }
+        list.add(rule);
+        rules.add(rule);
+        if (this.digester != null) {
+            rule.setDigester(this.digester);
+        }
+        if (this.namespaceURI != null) {
+            rule.setNamespaceURI(this.namespaceURI);
+        }
+
+    }
+
+
+    /**
+     * Clear all existing Rule instance registrations.
+     */
+    public void clear() {
+
+        cache.clear();
+        rules.clear();
+
+    }
+
+
+    /**
+     * Return a List of all registered Rule instances that match the specified
+     * nesting pattern, or a zero-length List if there are no matches.  If more
+     * than one Rule instance matches, they <strong>must</strong> be returned
+     * in the order originally registered through the <code>add()</code>
+     * method.
+     *
+     * @param namespaceURI Namespace URI for which to select matching rules,
+     *  or <code>null</code> to match regardless of namespace URI
+     * @param pattern Nesting pattern to be matched
+     */
+    public List<Rule> match(String namespaceURI, String pattern) {
+
+        // List rulesList = (List) this.cache.get(pattern);
+        List<Rule> rulesList = lookup(namespaceURI, pattern);
+        if ((rulesList == null) || (rulesList.size() < 1)) {
+            // Find the longest key, ie more discriminant
+            String longKey = "";
+            Iterator<String> keys = this.cache.keySet().iterator();
+            while (keys.hasNext()) {
+                String key = keys.next();
+                if (key.startsWith("*/")) {
+                    if (pattern.equals(key.substring(2)) ||
+                        pattern.endsWith(key.substring(1))) {
+                        if (key.length() > longKey.length()) {
+                            // rulesList = (List) this.cache.get(key);
+                            rulesList = lookup(namespaceURI, key);
+                            longKey = key;
+                        }
+                    }
+                }
+            }
+        }
+        if (rulesList == null) {
+            rulesList = new ArrayList<Rule>();
+        }
+        return (rulesList);
+
+    }
+
+
+    /**
+     * Return a List of all registered Rule instances, or a zero-length List
+     * if there are no registered Rule instances.  If more than one Rule
+     * instance has been registered, they <strong>must</strong> be returned
+     * in the order originally registered through the <code>add()</code>
+     * method.
+     */
+    public List<Rule> rules() {
+
+        return (this.rules);
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Return a List of Rule instances for the specified pattern that also
+     * match the specified namespace URI (if any).  If there are no such
+     * rules, return <code>null</code>.
+     *
+     * @param namespaceURI Namespace URI to match, or <code>null</code> to
+     *  select matching rules regardless of namespace URI
+     * @param pattern Pattern to be matched
+     */
+    protected List<Rule> lookup(String namespaceURI, String pattern) {
+
+        // Optimize when no namespace URI is specified
+        List<Rule> list = this.cache.get(pattern);
+        if (list == null) {
+            return (null);
+        }
+        if ((namespaceURI == null) || (namespaceURI.length() == 0)) {
+            return (list);
+        }
+
+        // Select only Rules that match on the specified namespace URI
+        ArrayList<Rule> results = new ArrayList<Rule>();
+        Iterator<Rule> items = list.iterator();
+        while (items.hasNext()) {
+            Rule item = items.next();
+            if ((namespaceURI.equals(item.getNamespaceURI())) ||
+                    (item.getNamespaceURI() == null)) {
+                results.add(item);
+            }
+        }
+        return (results);
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/SetNextRule.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/SetNextRule.java
new file mode 100644
index 0000000..a743415
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/SetNextRule.java
@@ -0,0 +1,224 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.digester;
+
+import org.apache.tomcat.util.IntrospectionUtils;
+
+
+/**
+ * <p>Rule implementation that calls a method on the (top-1) (parent)
+ * object, passing the top object (child) as an argument.  It is
+ * commonly used to establish parent-child relationships.</p>
+ *
+ * <p>This rule now supports more flexible method matching by default.
+ * It is possible that this may break (some) code 
+ * written against release 1.1.1 or earlier.
+ * See {@link #isExactMatch()} for more details.</p> 
+ */
+
+public class SetNextRule extends Rule {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a "set next" rule with the specified method name.  The
+     * method's argument type is assumed to be the class of the
+     * child object.
+     *
+     * @param digester The associated Digester
+     * @param methodName Method name of the parent method to call
+     *
+     * @deprecated The digester instance is now set in the {@link Digester#addRule} method. 
+     * Use {@link #SetNextRule(String methodName)} instead.
+     */
+    @Deprecated
+    public SetNextRule(Digester digester, String methodName) {
+
+        this(methodName);
+
+    }
+
+
+    /**
+     * Construct a "set next" rule with the specified method name.
+     *
+     * @param digester The associated Digester
+     * @param methodName Method name of the parent method to call
+     * @param paramType Java class of the parent method's argument
+     *  (if you wish to use a primitive type, specify the corresponding
+     *  Java wrapper class instead, such as <code>java.lang.Boolean</code>
+     *  for a <code>boolean</code> parameter)
+     *
+     * @deprecated The digester instance is now set in the {@link Digester#addRule} method. 
+     * Use {@link #SetNextRule(String methodName,String paramType)} instead.
+     */
+    @Deprecated
+    public SetNextRule(Digester digester, String methodName,
+                       String paramType) {
+
+        this(methodName, paramType);
+
+    }
+
+    /**
+     * Construct a "set next" rule with the specified method name.  The
+     * method's argument type is assumed to be the class of the
+     * child object.
+     *
+     * @param methodName Method name of the parent method to call
+     */
+    public SetNextRule(String methodName) {
+
+        this(methodName, null);
+
+    }
+
+
+    /**
+     * Construct a "set next" rule with the specified method name.
+     *
+     * @param methodName Method name of the parent method to call
+     * @param paramType Java class of the parent method's argument
+     *  (if you wish to use a primitive type, specify the corresponding
+     *  Java wrapper class instead, such as <code>java.lang.Boolean</code>
+     *  for a <code>boolean</code> parameter)
+     */
+    public SetNextRule(String methodName,
+                       String paramType) {
+
+        this.methodName = methodName;
+        this.paramType = paramType;
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The method name to call on the parent object.
+     */
+    protected String methodName = null;
+
+
+    /**
+     * The Java class name of the parameter type expected by the method.
+     */
+    protected String paramType = null;
+
+    /**
+     * Should we use exact matching. Default is no.
+     */
+    protected boolean useExactMatch = false;
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * <p>Is exact matching being used.</p>
+     *
+     * <p>This rule uses <code>org.apache.commons.beanutils.MethodUtils</code> 
+     * to introspect the relevant objects so that the right method can be called.
+     * Originally, <code>MethodUtils.invokeExactMethod</code> was used.
+     * This matches methods very strictly 
+     * and so may not find a matching method when one exists.
+     * This is still the behaviour when exact matching is enabled.</p>
+     *
+     * <p>When exact matching is disabled, <code>MethodUtils.invokeMethod</code> is used.
+     * This method finds more methods but is less precise when there are several methods 
+     * with correct signatures.
+     * So, if you want to choose an exact signature you might need to enable this property.</p>
+     *
+     * <p>The default setting is to disable exact matches.</p>
+     *
+     * @return true iff exact matching is enabled
+     * @since Digester Release 1.1.1
+     */
+    public boolean isExactMatch() {
+    
+        return useExactMatch;
+    }
+    
+    /**
+     * <p>Set whether exact matching is enabled.</p>
+     *
+     * <p>See {@link #isExactMatch()}.</p>
+     *
+     * @param useExactMatch should this rule use exact method matching
+     * @since Digester Release 1.1.1
+     */    
+    public void setExactMatch(boolean useExactMatch) {
+
+        this.useExactMatch = useExactMatch;
+    }
+
+    /**
+     * Process the end of this element.
+     * 
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the parser is not namespace aware or the element has
+     *   no namespace
+     * @param name the local name if the parser is namespace aware, or just 
+     *   the element name otherwise
+     */
+    @Override
+    public void end(String namespace, String name) throws Exception {
+
+        // Identify the objects to be used
+        Object child = digester.peek(0);
+        Object parent = digester.peek(1);
+        if (digester.log.isDebugEnabled()) {
+            if (parent == null) {
+                digester.log.debug("[SetNextRule]{" + digester.match +
+                        "} Call [NULL PARENT]." +
+                        methodName + "(" + child + ")");
+            } else {
+                digester.log.debug("[SetNextRule]{" + digester.match +
+                        "} Call " + parent.getClass().getName() + "." +
+                        methodName + "(" + child + ")");
+            }
+        }
+
+        // Call the specified method
+        IntrospectionUtils.callMethod1(parent, methodName,
+                child, paramType, digester.getClassLoader());
+                
+    }
+
+
+    /**
+     * Render a printable version of this Rule.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("SetNextRule[");
+        sb.append("methodName=");
+        sb.append(methodName);
+        sb.append(", paramType=");
+        sb.append(paramType);
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/SetPropertiesRule.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/SetPropertiesRule.java
new file mode 100644
index 0000000..0664f79
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/SetPropertiesRule.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.digester;
+
+
+import org.apache.tomcat.util.IntrospectionUtils;
+import org.xml.sax.Attributes;
+
+
+/**
+ * <p>Rule implementation that sets properties on the object at the top of the
+ * stack, based on attributes with corresponding names.</p>
+ *
+ * <p>This rule supports custom mapping of attribute names to property names.
+ * The default mapping for particular attributes can be overridden by using 
+ * {@link #SetPropertiesRule(String[] attributeNames, String[] propertyNames)}.
+ * This allows attributes to be mapped to properties with different names.
+ * Certain attributes can also be marked to be ignored.</p>
+ */
+
+public class SetPropertiesRule extends Rule {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Default constructor sets only the the associated Digester.
+     *
+     * @param digester The digester with which this rule is associated
+     *
+     * @deprecated The digester instance is now set in the {@link Digester#addRule} method. 
+     * Use {@link #SetPropertiesRule()} instead.
+     */
+    @Deprecated
+    public SetPropertiesRule(Digester digester) {
+
+        this();
+
+    }
+    
+
+    /**
+     * Base constructor.
+     */
+    public SetPropertiesRule() {
+
+        // nothing to set up 
+
+    }
+    
+    /** 
+     * <p>Convenience constructor overrides the mapping for just one property.</p>
+     *
+     * <p>For details about how this works, see
+     * {@link #SetPropertiesRule(String[] attributeNames, String[] propertyNames)}.</p>
+     *
+     * @param attributeName map this attribute 
+     * @param propertyName to a property with this name
+     */
+    public SetPropertiesRule(String attributeName, String propertyName) {
+        
+        attributeNames = new String[1];
+        attributeNames[0] = attributeName;
+        propertyNames = new String[1];
+        propertyNames[0] = propertyName;
+    }
+    
+    /** 
+     * <p>Constructor allows attribute->property mapping to be overridden.</p>
+     *
+     * <p>Two arrays are passed in. 
+     * One contains the attribute names and the other the property names.
+     * The attribute name / property name pairs are match by position
+     * In order words, the first string in the attribute name list matches
+     * to the first string in the property name list and so on.</p>
+     *
+     * <p>If a property name is null or the attribute name has no matching
+     * property name, then this indicates that the attribute should be ignored.</p>
+     * 
+     * <h5>Example One</h5>
+     * <p> The following constructs a rule that maps the <code>alt-city</code>
+     * attribute to the <code>city</code> property and the <code>alt-state</code>
+     * to the <code>state</code> property. 
+     * All other attributes are mapped as usual using exact name matching.
+     * <code><pre>
+     *      SetPropertiesRule(
+     *                new String[] {"alt-city", "alt-state"}, 
+     *                new String[] {"city", "state"});
+     * </pre></code>
+     *
+     * <h5>Example Two</h5>
+     * <p> The following constructs a rule that maps the <code>class</code>
+     * attribute to the <code>className</code> property.
+     * The attribute <code>ignore-me</code> is not mapped.
+     * All other attributes are mapped as usual using exact name matching.
+     * <code><pre>
+     *      SetPropertiesRule(
+     *                new String[] {"class", "ignore-me"}, 
+     *                new String[] {"className"});
+     * </pre></code>
+     *
+     * @param attributeNames names of attributes to map
+     * @param propertyNames names of properties mapped to
+     */
+    public SetPropertiesRule(String[] attributeNames, String[] propertyNames) {
+        // create local copies
+        this.attributeNames = new String[attributeNames.length];
+        for (int i=0, size=attributeNames.length; i<size; i++) {
+            this.attributeNames[i] = attributeNames[i];
+        }
+        
+        this.propertyNames = new String[propertyNames.length];
+        for (int i=0, size=propertyNames.length; i<size; i++) {
+            this.propertyNames[i] = propertyNames[i];
+        } 
+    }
+        
+    // ----------------------------------------------------- Instance Variables
+    
+    /** 
+     * Attribute names used to override natural attribute->property mapping
+     */
+    private String [] attributeNames;
+    /** 
+     * Property names used to override natural attribute->property mapping
+     */    
+    private String [] propertyNames;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Process the beginning of this element.
+     *
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the parser is not namespace aware or the element has
+     *   no namespace
+     * @param theName the local name if the parser is namespace aware, or just 
+     *   the element name otherwise
+     * @param attributes The attribute list for this element
+     */
+    @Override
+    public void begin(String namespace, String theName, Attributes attributes)
+            throws Exception {
+        
+        // Populate the corresponding properties of the top object
+        Object top = digester.peek();
+        if (digester.log.isDebugEnabled()) {
+            if (top != null) {
+                digester.log.debug("[SetPropertiesRule]{" + digester.match +
+                                   "} Set " + top.getClass().getName() +
+                                   " properties");
+            } else {
+                digester.log.debug("[SetPropertiesRule]{" + digester.match +
+                                   "} Set NULL properties");
+            }
+        }
+        
+        // set up variables for custom names mappings
+        int attNamesLength = 0;
+        if (attributeNames != null) {
+            attNamesLength = attributeNames.length;
+        }
+        int propNamesLength = 0;
+        if (propertyNames != null) {
+            propNamesLength = propertyNames.length;
+        }
+        
+        for (int i = 0; i < attributes.getLength(); i++) {
+            String name = attributes.getLocalName(i);
+            if ("".equals(name)) {
+                name = attributes.getQName(i);
+            }
+            String value = attributes.getValue(i);
+            
+            // we'll now check for custom mappings
+            for (int n = 0; n<attNamesLength; n++) {
+                if (name.equals(attributeNames[n])) {
+                    if (n < propNamesLength) {
+                        // set this to value from list
+                        name = propertyNames[n];
+                    
+                    } else {
+                        // set name to null
+                        // we'll check for this later
+                        name = null;
+                    }
+                    break;
+                }
+            } 
+            
+            if (digester.log.isDebugEnabled()) {
+                digester.log.debug("[SetPropertiesRule]{" + digester.match +
+                        "} Setting property '" + name + "' to '" +
+                        value + "'");
+            }
+            if (!digester.isFakeAttribute(top, name) 
+                    && !IntrospectionUtils.setProperty(top, name, value) 
+                    && digester.getRulesValidation()) {
+                digester.log.warn("[SetPropertiesRule]{" + digester.match +
+                        "} Setting property '" + name + "' to '" +
+                        value + "' did not find a matching property.");
+            }
+        }
+
+    }
+
+
+    /**
+     * <p>Add an additional attribute name to property name mapping.
+     * This is intended to be used from the xml rules.
+     */
+    public void addAlias(String attributeName, String propertyName) {
+        
+        // this is a bit tricky.
+        // we'll need to resize the array.
+        // probably should be synchronized but digester's not thread safe anyway
+        if (attributeNames == null) {
+            
+            attributeNames = new String[1];
+            attributeNames[0] = attributeName;
+            propertyNames = new String[1];
+            propertyNames[0] = propertyName;        
+            
+        } else {
+            int length = attributeNames.length;
+            String [] tempAttributes = new String[length + 1];
+            for (int i=0; i<length; i++) {
+                tempAttributes[i] = attributeNames[i];
+            }
+            tempAttributes[length] = attributeName;
+            
+            String [] tempProperties = new String[length + 1];
+            for (int i=0; i<length && i< propertyNames.length; i++) {
+                tempProperties[i] = propertyNames[i];
+            }
+            tempProperties[length] = propertyName;
+            
+            propertyNames = tempProperties;
+            attributeNames = tempAttributes;
+        }        
+    }
+  
+
+    /**
+     * Render a printable version of this Rule.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("SetPropertiesRule[");
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/SetPropertyRule.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/SetPropertyRule.java
new file mode 100644
index 0000000..1393205
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/SetPropertyRule.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */ 
+
+
+package org.apache.tomcat.util.digester;
+
+
+import org.apache.tomcat.util.IntrospectionUtils;
+import org.xml.sax.Attributes;
+
+
+/**
+ * Rule implementation that sets an individual property on the object at the
+ * top of the stack, based on attributes with specified names.
+ */
+
+public class SetPropertyRule extends Rule {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a "set property" rule with the specified name and value
+     * attributes.
+     *
+     * @param digester The digester with which this rule is associated
+     * @param name Name of the attribute that will contain the name of the
+     *  property to be set
+     * @param value Name of the attribute that will contain the value to which
+     *  the property should be set
+     *
+     * @deprecated The digester instance is now set in the {@link Digester#addRule} method. 
+     * Use {@link #SetPropertyRule(String name, String value)} instead.
+     */
+    @Deprecated
+    public SetPropertyRule(Digester digester, String name, String value) {
+
+        this(name, value);
+
+    }
+
+    /**
+     * Construct a "set property" rule with the specified name and value
+     * attributes.
+     *
+     * @param name Name of the attribute that will contain the name of the
+     *  property to be set
+     * @param value Name of the attribute that will contain the value to which
+     *  the property should be set
+     */
+    public SetPropertyRule(String name, String value) {
+
+        this.name = name;
+        this.value = value;
+
+    }
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The attribute that will contain the property name.
+     */
+    protected String name = null;
+
+
+    /**
+     * The attribute that will contain the property value.
+     */
+    protected String value = null;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Process the beginning of this element.
+     *
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the parser is not namespace aware or the element has
+     *   no namespace
+     * @param theName the local name if the parser is namespace aware, or just 
+     *   the element name otherwise
+     * @param attributes The attribute list for this element
+     * 
+     * @exception NoSuchMethodException if the bean does not
+     *  have a writable property of the specified name
+     */
+    @Override
+    public void begin(String namespace, String theName, Attributes attributes)
+            throws Exception {
+
+        // Identify the actual property name and value to be used
+        String actualName = null;
+        String actualValue = null;
+        for (int i = 0; i < attributes.getLength(); i++) {
+            String name = attributes.getLocalName(i);
+            if ("".equals(name)) {
+                name = attributes.getQName(i);
+            }
+            String value = attributes.getValue(i);
+            if (name.equals(this.name)) {
+                actualName = value;
+            } else if (name.equals(this.value)) {
+                actualValue = value;
+            }
+        }
+
+        // Get a reference to the top object
+        Object top = digester.peek();
+
+        // Log some debugging information
+        if (digester.log.isDebugEnabled()) {
+            digester.log.debug("[SetPropertyRule]{" + digester.match +
+                    "} Set " + top.getClass().getName() + " property " +
+                    actualName + " to " + actualValue);
+        }
+
+        // Set the property (with conversion as necessary)
+        if (!digester.isFakeAttribute(top, actualName) 
+                && !IntrospectionUtils.setProperty(top, actualName, actualValue) 
+                && digester.getRulesValidation()) {
+            digester.log.warn("[SetPropertyRule]{" + digester.match +
+                    "} Setting property '" + name + "' to '" +
+                    value + "' did not find a matching property.");
+        }
+
+    }
+
+
+    /**
+     * Render a printable version of this Rule.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("SetPropertyRule[");
+        sb.append("name=");
+        sb.append(name);
+        sb.append(", value=");
+        sb.append(value);
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/SetRootRule.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/SetRootRule.java
new file mode 100644
index 0000000..423de8a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/SetRootRule.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.tomcat.util.digester;
+
+import org.apache.tomcat.util.IntrospectionUtils;
+
+
+/**
+ * <p>Rule implementation that calls a method on the root object on the stack,
+ * passing the top object (child) as an argument.
+ * It is important to remember that this rule acts on <code>end</code>.</p>
+ *
+ * <p>This rule now supports more flexible method matching by default.
+ * It is possible that this may break (some) code 
+ * written against release 1.1.1 or earlier.
+ * See {@link #isExactMatch()} for more details.</p>
+ */
+
+public class SetRootRule extends Rule {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a "set root" rule with the specified method name.  The
+     * method's argument type is assumed to be the class of the
+     * child object.
+     *
+     * @param digester The associated Digester
+     * @param methodName Method name of the parent method to call
+     *
+     * @deprecated The digester instance is now set in the {@link Digester#addRule} method. 
+     * Use {@link #SetRootRule(String methodName)} instead.
+     */
+    @Deprecated
+    public SetRootRule(Digester digester, String methodName) {
+
+        this(methodName);
+
+    }
+
+
+    /**
+     * Construct a "set root" rule with the specified method name.
+     *
+     * @param digester The associated Digester
+     * @param methodName Method name of the parent method to call
+     * @param paramType Java class of the parent method's argument
+     *  (if you wish to use a primitive type, specify the corresponding
+     *  Java wrapper class instead, such as <code>java.lang.Boolean</code>
+     *  for a <code>boolean</code> parameter)
+     *
+     * @deprecated The digester instance is now set in the {@link Digester#addRule} method. 
+     * Use {@link #SetRootRule(String methodName,String paramType)} instead.
+     */
+    @Deprecated
+    public SetRootRule(Digester digester, String methodName,
+                       String paramType) {
+
+        this(methodName, paramType);
+
+    }
+
+    /**
+     * Construct a "set root" rule with the specified method name.  The
+     * method's argument type is assumed to be the class of the
+     * child object.
+     *
+     * @param methodName Method name of the parent method to call
+     */
+    public SetRootRule(String methodName) {
+
+        this(methodName, null);
+
+    }
+
+
+    /**
+     * Construct a "set root" rule with the specified method name.
+     *
+     * @param methodName Method name of the parent method to call
+     * @param paramType Java class of the parent method's argument
+     *  (if you wish to use a primitive type, specify the corresponding
+     *  Java wrapper class instead, such as <code>java.lang.Boolean</code>
+     *  for a <code>boolean</code> parameter)
+     */
+    public SetRootRule(String methodName,
+                       String paramType) {
+
+        this.methodName = methodName;
+        this.paramType = paramType;
+
+    }
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The method name to call on the parent object.
+     */
+    protected String methodName = null;
+
+
+    /**
+     * The Java class name of the parameter type expected by the method.
+     */
+    protected String paramType = null;
+    
+    /**
+     * Should we use exact matching. Default is no.
+     */
+    protected boolean useExactMatch = false;
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * <p>Is exact matching being used.</p>
+     *
+     * <p>This rule uses <code>org.apache.commons.beanutils.MethodUtils</code> 
+     * to introspect the relevant objects so that the right method can be called.
+     * Originally, <code>MethodUtils.invokeExactMethod</code> was used.
+     * This matches methods very strictly 
+     * and so may not find a matching method when one exists.
+     * This is still the behaviour when exact matching is enabled.</p>
+     *
+     * <p>When exact matching is disabled, <code>MethodUtils.invokeMethod</code> is used.
+     * This method finds more methods but is less precise when there are several methods 
+     * with correct signatures.
+     * So, if you want to choose an exact signature you might need to enable this property.</p>
+     *
+     * <p>The default setting is to disable exact matches.</p>
+     *
+     * @return true iff exact matching is enabled
+     * @since Digester Release 1.1.1
+     */
+    public boolean isExactMatch() {
+    
+        return useExactMatch;
+    }
+    
+    
+    /**
+     * <p>Set whether exact matching is enabled.</p>
+     *
+     * <p>See {@link #isExactMatch()}.</p>
+     *
+     * @param useExactMatch should this rule use exact method matching
+     * @since Digester Release 1.1.1
+     */
+    public void setExactMatch(boolean useExactMatch) {
+
+        this.useExactMatch = useExactMatch;
+    }
+
+    /**
+     * Process the end of this element.
+     * 
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the parser is not namespace aware or the element has
+     *   no namespace
+     * @param name the local name if the parser is namespace aware, or just 
+     *   the element name otherwise
+     */
+    @Override
+    public void end(String namespace, String name) throws Exception {
+
+        // Identify the objects to be used
+        Object child = digester.peek(0);
+        Object parent = digester.root;
+        if (digester.log.isDebugEnabled()) {
+            if (parent == null) {
+                digester.log.debug("[SetRootRule]{" + digester.match +
+                        "} Call [NULL ROOT]." +
+                        methodName + "(" + child + ")");
+            } else {
+                digester.log.debug("[SetRootRule]{" + digester.match +
+                        "} Call " + parent.getClass().getName() + "." +
+                        methodName + "(" + child + ")");
+            }
+        }
+
+        // Call the specified method
+        IntrospectionUtils.callMethod1(parent, methodName,
+                child, paramType, digester.getClassLoader());
+
+    }
+
+
+    /**
+     * Render a printable version of this Rule.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("SetRootRule[");
+        sb.append("methodName=");
+        sb.append(methodName);
+        sb.append(", paramType=");
+        sb.append(paramType);
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/SetTopRule.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/SetTopRule.java
new file mode 100644
index 0000000..ce53a7f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/SetTopRule.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.tomcat.util.digester;
+
+
+import org.apache.tomcat.util.IntrospectionUtils;
+
+
+/**
+ * <p>Rule implementation that calls a "set parent" method on the top (child)
+ * object, passing the (top-1) (parent) object as an argument.</p>
+ *
+ * <p>This rule now supports more flexible method matching by default.
+ * It is possible that this may break (some) code 
+ * written against release 1.1.1 or earlier.
+ * See {@link #isExactMatch()} for more details.</p>
+ */
+
+public class SetTopRule extends Rule {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a "set parent" rule with the specified method name.  The
+     * "set parent" method's argument type is assumed to be the class of the
+     * parent object.
+     *
+     * @param digester The associated Digester
+     * @param methodName Method name of the "set parent" method to call
+     *
+     * @deprecated The digester instance is now set in the {@link Digester#addRule} method. 
+     * Use {@link #SetTopRule(String methodName)} instead.
+     */
+    @Deprecated
+    public SetTopRule(Digester digester, String methodName) {
+
+        this(methodName);
+
+    }
+
+
+    /**
+     * Construct a "set parent" rule with the specified method name.
+     *
+     * @param digester The associated Digester
+     * @param methodName Method name of the "set parent" method to call
+     * @param paramType Java class of the "set parent" method's argument
+     *  (if you wish to use a primitive type, specify the corresponding
+     *  Java wrapper class instead, such as <code>java.lang.Boolean</code>
+     *  for a <code>boolean</code> parameter)
+     *
+     * @deprecated The digester instance is now set in the {@link Digester#addRule} method. 
+     * Use {@link #SetTopRule(String methodName, String paramType)} instead.
+     */
+    @Deprecated
+    public SetTopRule(Digester digester, String methodName,
+                      String paramType) {
+
+        this(methodName, paramType);
+
+    }
+
+    /**
+     * Construct a "set parent" rule with the specified method name.  The
+     * "set parent" method's argument type is assumed to be the class of the
+     * parent object.
+     *
+     * @param methodName Method name of the "set parent" method to call
+     */
+    public SetTopRule(String methodName) {
+
+        this(methodName, null);
+
+    }
+
+
+    /**
+     * Construct a "set parent" rule with the specified method name.
+     *
+     * @param methodName Method name of the "set parent" method to call
+     * @param paramType Java class of the "set parent" method's argument
+     *  (if you wish to use a primitive type, specify the corresponding
+     *  Java wrapper class instead, such as <code>java.lang.Boolean</code>
+     *  for a <code>boolean</code> parameter)
+     */
+    public SetTopRule(String methodName,
+                      String paramType) {
+
+        this.methodName = methodName;
+        this.paramType = paramType;
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The method name to call on the child object.
+     */
+    protected String methodName = null;
+
+
+    /**
+     * The Java class name of the parameter type expected by the method.
+     */
+    protected String paramType = null;
+    
+    /**
+     * Should we use exact matching. Default is no.
+     */
+    protected boolean useExactMatch = false;
+
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * <p>Is exact matching being used.</p>
+     *
+     * <p>This rule uses <code>org.apache.commons.beanutils.MethodUtils</code> 
+     * to introspect the relevant objects so that the right method can be called.
+     * Originally, <code>MethodUtils.invokeExactMethod</code> was used.
+     * This matches methods very strictly 
+     * and so may not find a matching method when one exists.
+     * This is still the behaviour when exact matching is enabled.</p>
+     *
+     * <p>When exact matching is disabled, <code>MethodUtils.invokeMethod</code> is used.
+     * This method finds more methods but is less precise when there are several methods 
+     * with correct signatures.
+     * So, if you want to choose an exact signature you might need to enable this property.</p>
+     *
+     * <p>The default setting is to disable exact matches.</p>
+     *
+     * @return true iff exact matching is enabled
+     * @since Digester Release 1.1.1
+     */
+    public boolean isExactMatch() {
+    
+        return useExactMatch;
+    }
+    
+    /**
+     * <p>Set whether exact matching is enabled.</p>
+     *
+     * <p>See {@link #isExactMatch()}.</p>
+     *
+     * @param useExactMatch should this rule use exact method matching
+     * @since Digester Release 1.1.1
+     */
+    public void setExactMatch(boolean useExactMatch) {
+
+        this.useExactMatch = useExactMatch;
+    }
+    
+    /**
+     * Process the end of this element.
+     * 
+     * @param namespace the namespace URI of the matching element, or an 
+     *   empty string if the parser is not namespace aware or the element has
+     *   no namespace
+     * @param name the local name if the parser is namespace aware, or just 
+     *   the element name otherwise
+     */
+    @Override
+    public void end(String namespace, String name) throws Exception {
+
+        // Identify the objects to be used
+        Object child = digester.peek(0);
+        Object parent = digester.peek(1);
+        
+        if (digester.log.isDebugEnabled()) {
+            if (child == null) {
+                digester.log.debug("[SetTopRule]{" + digester.match +
+                        "} Call [NULL CHILD]." +
+                        methodName + "(" + parent + ")");
+            } else {
+                digester.log.debug("[SetTopRule]{" + digester.match +
+                        "} Call " + child.getClass().getName() + "." +
+                        methodName + "(" + parent + ")");
+            }
+        }
+
+        // Call the specified method
+        IntrospectionUtils.callMethod1(child, methodName,
+                parent, paramType, digester.getClassLoader());
+
+    }
+
+
+    /**
+     * Render a printable version of this Rule.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("SetTopRule[");
+        sb.append("methodName=");
+        sb.append(methodName);
+        sb.append(", paramType=");
+        sb.append(paramType);
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/WithDefaultsRulesWrapper.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/WithDefaultsRulesWrapper.java
new file mode 100644
index 0000000..95cd123
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/WithDefaultsRulesWrapper.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.digester;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * <p><code>Rules</code> <em>Decorator</em> that returns default rules 
+ * when no matches are returned by the wrapped implementation.</p>
+ *
+ * <p>This allows default <code>Rule</code> instances to be added to any 
+ * existing <code>Rules</code> implementation. These default <code>Rule</code> 
+ * instances will be returned for any match for which the wrapped 
+ * implementation does not return any matches.</p>
+ * <p> For example,
+ * <pre>
+ *   Rule alpha;
+ *   ...
+ *   WithDefaultsRulesWrapper rules = new WithDefaultsRulesWrapper(new BaseRules());
+ *   rules.addDefault(alpha);
+ *   ...
+ *   digester.setRules(rules);
+ *   ...
+ * </pre>
+ * when a pattern does not match any other rule, then rule alpha will be called.
+ * </p>
+ * <p><code>WithDefaultsRulesWrapper</code> follows the <em>Decorator</em> pattern.</p>
+ *
+ * @since 1.6
+ */
+
+public class WithDefaultsRulesWrapper implements Rules {
+
+    // --------------------------------------------------------- Fields
+    
+    /** The Rules implementation that this class wraps. */
+    private Rules wrappedRules;
+    /** Rules to be fired when the wrapped implementations returns none. */
+    private List<Rule> defaultRules = new ArrayList<Rule>();
+    /** All rules (preserves order in which they were originally added) */
+    private List<Rule> allRules = new ArrayList<Rule>();
+    
+    // --------------------------------------------------------- Constructor
+    
+    /** 
+     * Base constructor.
+     *
+     * @param wrappedRules the wrapped <code>Rules</code> implementation, not null
+     * @throws IllegalArgumentException when <code>wrappedRules</code> is null
+     */
+    public WithDefaultsRulesWrapper(Rules wrappedRules) {
+        if (wrappedRules == null) {
+            throw new IllegalArgumentException("Wrapped rules must not be null");
+        }
+        this.wrappedRules = wrappedRules;
+    }
+
+    // --------------------------------------------------------- Properties
+    
+    /** Gets digester using these Rules */
+    public Digester getDigester() {
+        return wrappedRules.getDigester();
+    }
+    
+    /** Sets digester using these Rules */
+    public void setDigester(Digester digester) {
+        wrappedRules.setDigester(digester);
+        Iterator<Rule> it = defaultRules.iterator();
+        while (it.hasNext()) {
+            Rule rule = it.next();
+            rule.setDigester(digester);
+        }
+    }
+    
+    /** Gets namespace to apply to Rule's added */
+    public String getNamespaceURI() {
+        return wrappedRules.getNamespaceURI();
+    }
+    
+    /** Sets namespace to apply to Rule's added subsequently */
+    public void setNamespaceURI(String namespaceURI) {
+        wrappedRules.setNamespaceURI(namespaceURI);
+    }
+    
+    /** Gets Rule's which will be fired when the wrapped implementation returns no matches */
+    public List<Rule> getDefaults() {
+        return defaultRules;
+    }
+    
+    // --------------------------------------------------------- Public Methods
+    
+    /**
+     * Return list of rules matching given pattern.
+     * If wrapped implementation returns any matches return those.
+     * Otherwise, return default matches.
+     */
+    public List<Rule> match(String namespaceURI, String pattern) {
+        List<Rule> matches = wrappedRules.match(namespaceURI, pattern);
+        if (matches ==  null || matches.isEmpty()) {
+            // a little bit of defensive programming
+            return new ArrayList<Rule>(defaultRules);
+        }
+        // otherwise
+        return matches;
+    }
+    
+    /** Adds a rule to be fired when wrapped implementation returns no matches */
+    public void addDefault(Rule rule) {
+        // set up rule
+        if (wrappedRules.getDigester() != null) {
+            rule.setDigester(wrappedRules.getDigester());
+        }
+        
+        if (wrappedRules.getNamespaceURI() != null) {
+            rule.setNamespaceURI(wrappedRules.getNamespaceURI());
+        }
+        
+        defaultRules.add(rule);
+        allRules.add(rule);
+    }
+    
+    /** Gets all rules */
+    public List<Rule> rules() {
+        return allRules;
+    }
+    
+    /** Clears all Rule's */
+    public void clear() {
+        wrappedRules.clear();
+        allRules.clear();
+        defaultRules.clear();
+    }
+    
+    /** 
+     * Adds a Rule to be fired on given pattern.
+     * Pattern matching is delegated to wrapped implementation.
+     */
+    public void add(String pattern, Rule rule) {
+        wrappedRules.add(pattern, rule);
+        allRules.add(rule);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/XercesParser.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/XercesParser.java
new file mode 100644
index 0000000..5abdd74
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/XercesParser.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.digester;
+
+import java.lang.reflect.Method;
+import java.util.Properties;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+
+/**
+ * Create a <code>SAXParser</code> based on the underlying Xerces version.
+ * Currently, Xerces 2.3 and up doesn't implement schema validation the same way
+ * 2.1 was. In other to support schema validation in a portable way between 
+ * parser, some features/properties need to be set.
+ *
+ * @since 1.6
+ */
+
+public class XercesParser{
+
+    /**
+     * The Log to which all SAX event related logging calls will be made.
+     */
+    private static final Log log =
+        LogFactory.getLog("org.apache.commons.digester.Digester.sax");
+
+
+    /**
+     * The JAXP 1.2 property required to set up the schema location.
+     */
+    private static final String JAXP_SCHEMA_SOURCE =
+        "http://java.sun.com/xml/jaxp/properties/schemaSource";
+
+
+    /**
+     * The JAXP 1.2 property to set up the schemaLanguage used.
+     */
+    protected static String JAXP_SCHEMA_LANGUAGE =
+        "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
+
+
+    /**
+     * Xerces dynamic validation property
+     */
+    protected static String XERCES_DYNAMIC = 
+        "http://apache.org/xml/features/validation/dynamic";
+
+
+    /**
+     * Xerces schema validation property
+     */
+    protected static String XERCES_SCHEMA =
+        "http://apache.org/xml/features/validation/schema";
+
+
+    /**
+     * A <code>float</code> representing the underlying Xerces version
+     */
+    protected static float version;
+
+
+    /**
+     * The current Xerces version.
+     */
+    protected static String versionNumber = null;
+
+
+    /**
+     * Return the current Xerces version.
+     * @return the current Xerces version.
+     */
+    private static String getXercesVersion() {
+        // If for some reason we can't get the version, set it to 1.0.
+        String versionNumber = "1.0";
+        try{
+            // Use reflection to avoid a build dependency with Xerces.
+            Class<?> versionClass = 
+                            Class.forName("org.apache.xerces.impl.Version");
+            // Will return Xerces-J 2.x.0
+            Method method = 
+                versionClass.getMethod("getVersion", (Class[]) null); 
+            String version = (String)method.invoke(null, (Object[]) null);
+            versionNumber = version.substring( "Xerces-J".length() , 
+                                               version.lastIndexOf(".") ); 
+        } catch (Exception ex){
+            // Do nothing.
+        }
+        return versionNumber;
+    }
+
+
+    /**
+     * Create a <code>SAXParser</code> based on the underlying
+     * <code>Xerces</code> version.
+     * @param properties parser specific properties/features
+     * @return an XML Schema/DTD enabled <code>SAXParser</code>
+     */
+    public static SAXParser newSAXParser(Properties properties) 
+            throws ParserConfigurationException, 
+                   SAXException,
+                   SAXNotSupportedException {
+
+        SAXParserFactory factory =  
+                        (SAXParserFactory)properties.get("SAXParserFactory");
+
+        if (versionNumber == null){
+            versionNumber = getXercesVersion();
+            version = new Float( versionNumber ).floatValue();
+        }
+
+        // Note: 2.2 is completely broken (with XML Schema). 
+        if (version > 2.1) {
+
+            configureXerces(factory);
+            return factory.newSAXParser();
+        } else {
+            SAXParser parser = factory.newSAXParser();
+            configureOldXerces(parser,properties);
+            return parser;
+        }
+    }
+
+
+    /**
+     * Configure schema validation as recommended by the JAXP 1.2 spec.
+     * The <code>properties</code> object may contains information about
+     * the schema local and language. 
+     * @param properties parser optional info
+     */
+    private static void configureOldXerces(SAXParser parser, 
+                                           Properties properties) 
+            throws ParserConfigurationException, 
+                   SAXNotSupportedException {
+
+        String schemaLocation = (String)properties.get("schemaLocation");
+        String schemaLanguage = (String)properties.get("schemaLanguage");
+
+        try{
+            if (schemaLocation != null) {
+                parser.setProperty(JAXP_SCHEMA_LANGUAGE, schemaLanguage);
+                parser.setProperty(JAXP_SCHEMA_SOURCE, schemaLocation);
+            }
+        } catch (SAXNotRecognizedException e){
+            log.info(parser.getClass().getName() + ": " 
+                                        + e.getMessage() + " not supported."); 
+        }
+
+    }
+
+
+    /**
+     * Configure schema validation as recommended by the Xerces spec. 
+     * Both DTD and Schema validation will be enabled simultaneously.
+     * @param factory SAXParserFactory to be configured
+     */
+    private static void configureXerces(SAXParserFactory factory)
+            throws ParserConfigurationException, 
+                   SAXNotRecognizedException, 
+                   SAXNotSupportedException {
+
+        factory.setFeature(XERCES_DYNAMIC, true);
+        factory.setFeature(XERCES_SCHEMA, true);
+
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/package.html b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/package.html
new file mode 100644
index 0000000..922a7cb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/digester/package.html
@@ -0,0 +1,1244 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT 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>Package Documentation for org.apache.commons.digester Package</title>
+</head>
+<body bgcolor="white">
+The Digester package provides for rules-based processing of arbitrary
+XML documents.
+<br><br>
+<a name="doc.Description"></a>
+<div align="center">
+<a href="#doc.Intro">[Introduction]</a>
+<a href="#doc.Properties">[Configuration Properties]</a>
+<a href="#doc.Stack">[The Object Stack]</a>
+<a href="#doc.Patterns">[Element Matching Patterns]</a>
+<a href="#doc.Rules">[Processing Rules]</a>
+<a href="#doc.Logging">[Logging]</a>
+<a href="#doc.Usage">[Usage Example]</a>
+<a href="#doc.Namespace">[Namespace Aware Parsing]</a>
+<a href="#doc.Pluggable">[Pluggable Rules Processing]</a>
+<a href="#doc.RuleSets">[Encapsulated Rule Sets]</a>
+<a href="#doc.NamedStacks">[Using Named Stacks For Inter-Rule Communication]</a>
+<a href="#doc.RegisteringDTDs">[Registering DTDs]</a>
+<a href="#doc.troubleshooting">[Troubleshooting]</a>
+<a href="#doc.FAQ">[FAQ]</a>
+<a href="#doc.Limits">[Known Limitations]</a>
+</div>
+
+<a name="doc.Intro"></a>
+<h3>Introduction</h3>
+
+<p>In many application environments that deal with XML-formatted data, it is
+useful to be able to process an XML document in an "event driven" manner,
+where particular Java objects are created (or methods of existing objects
+are invoked) when particular patterns of nested XML elements have been
+recognized.  Developers familiar with the Simple API for XML Parsing (SAX)
+approach to processing XML documents will recognize that the Digester provides
+a higher level, more developer-friendly interface to SAX events, because most
+of the details of navigating the XML element hierarchy are hidden -- allowing
+the developer to focus on the processing to be performed.</p>
+
+<p>In order to use a Digester, the following basic steps are required:</p>
+<ul>
+<li>Create a new instance of the
+    <code>org.apache.commons.digester.Digester</code> class.  Previously
+    created Digester instances may be safely reused, as long as you have
+    completed any previously requested parse, and you do not try to utilize
+    a particular Digester instance from more than one thread at a time.</li>
+<li>Set any desired <a href="#doc.Properties">configuration properties</a>
+    that will customize the operation of the Digester when you next initiate
+    a parse operation.</li>
+<li>Optionally, push any desired initial object(s) onto the Digester's
+    <a href="#doc.Stack">object stack</a>.</li>
+<li>Register all of the <a href="#doc.Patterns">element matching patterns</a>
+    for which you wish to have <a href="#doc.Rules">processing rules</a>
+    fired when this pattern is recognized in an input document.  You may
+    register as many rules as you like for any particular pattern.  If there
+    is more than one rule for a given pattern, the rules will be executed in
+    the order that they were listed.</li>
+<li>Call the <code>digester.parse()</code> method, passing a reference to the
+    XML document to be parsed in one of a variety of forms.  See the
+    <a href="Digester.html#parse(java.io.File)">Digester.parse()</a>
+    documentation for details.  Note that you will need to be prepared to
+    catch any <code>IOException</code> or <code>SAXException</code> that is
+    thrown by the parser, or any runtime expression that is thrown by one of
+    the processing rules.</li>
+</ul>
+
+<p>For example code, see <a href="#doc.Usage"> the usage 
+examples</a>, and <a href="#doc.FAQ.Examples"> the FAQ </a>. </p>
+
+<a name="doc.Properties"></a>
+<h3>Digester Configuration Properties</h3>
+
+<p>A <code>org.apache.commons.digester.Digester</code> instance contains several
+configuration properties that can be used to customize its operation.  These
+properties <strong>must</strong> be configured before you call one of the
+<code>parse()</code> variants, in order for them to take effect on that
+parse.</p>
+
+<blockquote>
+  <table border="1">
+    <tr>
+      <th width="15%">Property</th>
+      <th width="85%">Description</th>
+    </tr>
+    <tr>
+      <td align="center">classLoader</td>
+      <td>You can optionally specify the class loader that will be used to
+          load classes when required by the <code>ObjectCreateRule</code>
+          and <code>FactoryCreateRule</code> rules.  If not specified,
+          application classes will be loaded from the thread's context
+          class loader (if the <code>useContextClassLoader</code> property
+          is set to <code>true</code>) or the same class loader that was
+          used to load the <code>Digester</code> class itself.</td>
+    </tr>
+    <tr>
+      <td align="center">errorHandler</td>
+      <td>You can optionally specify a SAX <code>ErrorHandler</code> that
+          is notified when parsing errors occur.  By default, any parsing
+          errors that are encountered are logged, but Digester will continue
+          processing as well.</td>
+    </tr>
+    <tr>
+      <td align="center">namespaceAware</td>
+      <td>A boolean that is set to <code>true</code> to perform parsing in a
+          manner that is aware of XML namespaces.  Among other things, this
+          setting affects how elements are matched to processing rules.  See
+          <a href="#doc.Namespace">Namespace Aware Parsing</a> for more
+          information.</td>
+    </tr>
+    <tr>
+      <td align="center">ruleNamespaceURI</td>
+      <td>The public URI of the namespace for which all subsequently added
+          rules are associated, or <code>null</code> for adding rules that
+          are not associated with any namespace.  See
+          <a href="#doc.Namespace">Namespace Aware Parsing</a> for more
+          information.</td>
+    </tr>
+    <tr>
+      <td align="center">rules</td>
+      <td>The <code>Rules</code> component that actually performs matching of
+          <code>Rule</code> instances against the current element nesting
+          pattern is pluggable.  By default, Digester includes a
+          <code>Rules</code> implementation that behaves as described in this
+          document.  See
+          <a href="#doc.Pluggable">Pluggable Rules Processing</a> for
+          more information.</td>
+    </tr>
+    <tr>
+      <td align="center">useContextClassLoader</code>
+      <td>A boolean that is set to <code>true</code> if you want application
+          classes required by <code>FactoryCreateRule</code> and
+          <code>ObjectCreateRule</code> to be loaded from the context class
+          loader of the current thread.  By default, classes will be loaded
+          from the class loader that loaded this <code>Digester</code> class.
+          <strong>NOTE</strong> - This property is ignored if you set a
+          value for the <code>classLoader</code> property; that class loader
+          will be used unconditionally.</td>
+    </tr>
+    <tr>
+      <td align="center">validating</td>
+      <td>A boolean that is set to <code>true</code> if you wish to validate
+          the XML document against a Document Type Definition (DTD) that is
+          specified in its <code>DOCTYPE</code> declaration.  The default
+          value of <code>false</code> requests a parse that only detects
+          "well formed" XML documents, rather than "valid" ones.</td>
+    </tr>
+  </table>
+</blockquote>
+
+<p>In addition to the scalar properties defined above, you can also register
+a local copy of a Document Type Definition (DTD) that is referenced in a
+<code>DOCTYPE</code> declaration.  Such a registration tells the XML parser
+that, whenever it encounters a <code>DOCTYPE</code> declaration with the
+specified public identifier, it should utilize the actual DTD content at the
+registered system identifier (a URL), rather than the one in the
+<code>DOCTYPE</code> declaration.</p>
+
+<p>For example, the Struts framework controller servlet uses the following
+registration in order to tell Struts to use a local copy of the DTD for the
+Struts configuration file.  This allows usage of Struts in environments that
+are not connected to the Internet, and speeds up processing even at Internet
+connected sites (because it avoids the need to go across the network).</p>
+
+<pre>
+    URL url = new URL("/org/apache/struts/resources/struts-config_1_0.dtd");
+    digester.register
+      ("-//Apache Software Foundation//DTD Struts Configuration 1.0//EN",
+       url.toString());
+</pre>
+
+<p>As a side note, the system identifier used in this example is the path
+that would be passed to <code>java.lang.ClassLoader.getResource()</code>
+or <code>java.lang.ClassLoader.getResourceAsStream()</code>.  The actual DTD
+resource is loaded through the same class loader that loads all of the Struts
+classes -- typically from the <code>struts.jar</code> file.</p>
+
+<a name="doc.Stack"></a>
+<h3>The Object Stack</h3>
+
+<p>One very common use of <code>org.apache.commons.digester.Digester</code>
+technology is to dynamically construct a tree of Java objects, whose internal
+organization, as well as the details of property settings on these objects,
+are configured based on the contents of the XML document.  In fact, the
+primary reason that the Digester package was created (it was originally part
+of Struts, and then moved to the Commons project because it was recognized
+as being generally useful) was to facilitate the
+way that the Struts controller servlet configures itself based on the contents
+of your application's <code>struts-config.xml</code> file.</p>
+
+<p>To facilitate this usage, the Digester exposes a stack that can be
+manipulated by processing rules that are fired when element matching patterns
+are satisfied.  The usual stack-related operations are made available,
+including the following:</p>
+<ul>
+<li><a href="Digester.html#clear()">clear()</a> - Clear the current contents
+    of the object stack.</li>
+<li><a href="Digester.html#peek()">peek()</a> - Return a reference to the top
+    object on the stack, without removing it.</li>
+<li><a href="Digester.html#pop()">pop()</a> - Remove the top object from the
+    stack and return it.</li>
+<li><a href="Digester.html#push(java.lang.Object)">push()</a> - Push a new
+    object onto the top of the stack.</li>
+</ul>
+
+<p>A typical design pattern, then, is to fire a rule that creates a new object
+and pushes it on the stack when the beginning of a particular XML element is
+encountered. The object will remain there while the nested content of this
+element is processed, and it will be popped off when the end of the element
+is encountered.  As we will see, the standard "object create" processing rule
+supports exactly this functionalility in a very convenient way.</p>
+
+<p>Several potential issues with this design pattern are addressed by other
+features of the Digester functionality:</p>
+<ul>
+<li><em>How do I relate the objects being created to each other?</em> - The
+    Digester supports standard processing rules that pass the top object on
+    the stack as an argument to a named method on the next-to-top object on
+    the stack (or vice versa).  This rule makes it easy to establish
+    parent-child relationships between these objects.  One-to-one and
+    one-to-many relationships are both easy to construct.</li>
+<li><em>How do I retain a reference to the first object that was created?</em>
+    As you review the description of what the "object create" processing rule
+    does, it would appear that the first object you create (i.e. the object
+    created by the outermost XML element you process) will disappear from the
+    stack by the time that XML parsing is completed, because the end of the
+    element would have been encountered.  However, Digester will maintain a
+    reference to the very first object ever pushed onto the object stack,
+    and will return it to you
+    as the return value from the <code>parse()</code> call.  Alternatively,
+    you can push a reference to some application object onto the stack before
+    calling <code>parse()</code>, and arrange that a parent-child relationship
+    be created (by appropriate processing rules) between this manually pushed
+    object and the ones that are dynamically created.  In this way,
+    the pushed object will retain a reference to the dynamically created objects
+    (and therefore all of their children), and will be returned to you after
+    the parse finishes as well.</li>
+</ul>
+
+<a name="doc.Patterns"></a>
+<h3>Element Matching Patterns</h3>
+
+<p>A primary feature of the <code>org.apache.commons.digester.Digester</code>
+parser is that the Digester automatically navigates the element hierarchy of
+the XML document you are parsing for you, without requiring any developer
+attention to this process.  Instead, you focus on deciding what functions you
+would like to have performed whenver a certain arrangement of nested elements
+is encountered in the XML document being parsed.  The mechanism for specifying
+such arrangements are called <em>element matching patterns</em>.
+
+<p>A very simple element matching pattern is a simple string like "a".  This
+pattern is matched whenever an <code>&lt;a&gt;</code> top-level element is
+encountered in the XML document, no matter how many times it occurs.  Note that
+nested <code>&lt;a&gt;</code> elements will <strong>not</strong> match this
+pattern -- we will describe means to support this kind of matching later.</li>
+
+<p>The next step up in matching pattern complexity is "a/b".  This pattern will
+be matched when a <code>&lt;b&gt;</code> element is found nested inside a
+top-level <code>&lt;a&gt;</code> element.  Again, this match can occur as many
+times as desired, depending on the content of the XML document being parsed.
+You can use multiple slashes to define a hierarchy of any desired depth that
+will be matched appropriately.</p>
+
+<p>For example, assume you have registered processing rules that match patterns
+"a", "a/b", and "a/b/c".  For an input XML document with the following
+contents, the indicated patterns will be matched when the corresponding element
+is parsed:</p>
+<pre>
+  &lt;a&gt;         -- Matches pattern "a"
+    &lt;b&gt;       -- Matches pattern "a/b"
+      &lt;c/&gt;    -- Matches pattern "a/b/c"
+      &lt;c/&gt;    -- Matches pattern "a/b/c"
+    &lt;/b&gt;
+    &lt;b&gt;       -- Matches pattern "a/b"
+      &lt;c/&gt;    -- Matches pattern "a/b/c"
+      &lt;c/&gt;    -- Matches pattern "a/b/c"
+      &lt;c/&gt;    -- Matches pattern "a/b/c"
+    &lt;/b&gt;
+  &lt;/a&gt;
+</pre>
+
+<p>It is also possible to match a particular XML element, no matter how it is
+nested (or not nested) in the XML document, by using the "*" wildcard character
+in your matching pattern strings.  For example, an element matching pattern
+of "*/a" will match an <code>&lt;a&gt;</code> element at any nesting position
+within the document.</p>
+
+<p>It is quite possible that, when a particular XML element is being parsed,
+the pattern for more than one registered processing rule will be matched
+ either because you registered more than one processing rule with the same
+matching pattern, or because one more more exact pattern matches and wildcard
+pattern matches are satisfied by the same element.</p>
+
+<p>When this occurs, the corresponding processing rules will all be fired in order. 
+<code>begin</code> (and <code>body</code>) method calls are executed in the 
+order that the <code>Rules</code> where initially registered with the 
+<code>Digester</code>, whilst <code>end</code> method calls are execute in 
+reverse order. In other words - the order is first in, last out.</p>
+
+<a name="doc.Rules"></a>
+<h3>Processing Rules</h3>
+
+<p>The <a href="#doc.Patterns">previous section</a> documented how you identify
+<strong>when</strong> you wish to have certain actions take place.  The purpose
+of processing rules is to define <strong>what</strong> should happen when the
+patterns are matched.</p>
+
+<p>Formally, a processing rule is a Java class that subclasses the
+<a href="Rule.html">org.apache.commons.digester.Rule</a> interface.  Each Rule
+implements one or more of the following event methods that are called at
+well-defined times when the matching patterns corresponding to this rule
+trigger it:</p>
+<ul>
+<li><a href="Rule.html#begin(org.xml.sax.AttributeList)">begin()</a> -
+    Called when the beginning of the matched XML element is encountered.  A
+    data structure containing all of the attributes corresponding to this
+    element are passed as well.</li>
+<li><a href="Rule.html#body(java.lang.String)">body()</a> -
+    Called when nested content (that is not itself XML elements) of the
+    matched element is encountered.  Any leading or trailing whitespace will
+    have been removed as part of the parsing process.</li>
+<li><a href="Rule.html#end()">end()</a> - Called when the ending of the matched
+    XML element is encountered.  If nested XML elements that matched other
+    processing rules was included in the body of this element, the appropriate
+    processing rules for the matched rules will have already been completed
+    before this method is called.</li>
+<li><a href="Rule.html#finish()">finish()</a> - Called when the parse has
+    been completed, to give each rule a chance to clean up any temporary data
+    they might have created and cached.</li>
+</ul>
+
+<p>As you are configuring your digester, you can call the
+<code>addRule()</code> method to register a specific element matching pattern,
+along with an instance of a <code>Rule</code> class that will have its event
+handling methods called at the appropriate times, as described above.  This
+mechanism allows you to create <code>Rule</code> implementation classes
+dynamically, to implement any desired application specific functionality.</p>
+
+<p>In addition, a set of processing rule implementation classes are provided,
+which deal with many common programming scenarios.  These classes include the
+following:</p>
+<ul>
+<li><a href="ObjectCreateRule.html">ObjectCreateRule</a> - When the
+    <code>begin()</code> method is called, this rule instantiates a new
+    instance of a specified Java class, and pushes it on the stack.  The
+    class name to be used is defaulted according to a parameter passed to
+    this rule's constructor, but can optionally be overridden by a classname
+    passed via the specified attribute to the XML element being processed.
+    When the <code>end()</code> method is called, the top object on the stack
+    (presumably, the one we added in the <code>begin()</code> method) will
+    be popped, and any reference to it (within the Digester) will be
+    discarded.</li>
+<li><a href="FactoryCreateRule.html">FactoryCreateRule</a> - A variation of
+    <code>ObjectCreateRule</code> that is useful when the Java class with
+    which you wish to create an object instance does not have a no-arguments
+    constructor, or where you wish to perform other setup processing before
+    the object is handed over to the Digester.</li>
+<li><a href="SetPropertiesRule.html">SetPropertiesRule</a> - When the
+    <code>begin()</code> method is called, the digester uses the standard
+    Java Reflection API to identify any JavaBeans property setter methods
+    (on the object at the top of the digester's stack)
+    who have property names that match the attributes specified on this XML
+    element, and then call them individually, passing the corresponding
+    attribute values. These natural mappings can be overridden. This allows
+    (for example) a <code>class</code> attribute to be mapped correctly.
+    It is recommended that this feature should not be overused - in most cases,
+    it's better to use the standard <code>BeanInfo</code> mechanism.
+    A very common idiom is to define an object create
+    rule, followed by a set properties rule, with the same element matching
+    pattern.  This causes the creation of a new Java object, followed by
+    "configuration" of that object's properties based on the attributes
+    of the same XML element that created this object.</li>
+<li><a href="SetPropertyRule.html">SetPropertyRule</a> - When the
+    <code>begin()</code> method is called, the digester calls a specified
+    property setter (where the property itself is named by an attribute)
+    with a specified value (where the value is named by another attribute),
+    on the object at the top of the digester's stack.
+    This is useful when your XML file conforms to a particular DTD, and
+    you wish to configure a particular property that does not have a
+    corresponding attribute in the DTD.</li>
+<li><a href="SetNextRule.html">SetNextRule</a> - When the
+    <code>end()</code> method is called, the digester analyzes the
+    next-to-top element on the stack, looking for a property setter method
+    for a specified property.  It then calls this method, passing the object
+    at the top of the stack as an argument.  This rule is commonly used to
+    establish one-to-many relationships between the two objects, with the
+    method name commonly being something like "addChild".</li>
+<li><a href="SetTopRule.html">SetTopRule</a> - When the
+    <code>end()</code> method is called, the digester analyzes the
+    top element on the stack, looking for a property setter method for a
+    specified property.  It then calls this method, passing the next-to-top
+    object on the stack as an argument.  This rule would be used as an
+    alternative to a SetNextRule, with a typical method name "setParent",
+    if the API supported by your object classes prefers this approach.</li>
+<li><a href="CallMethodRule.html">CallMethodRule</a> - This rule sets up a
+    method call to a named method of the top object on the digester's stack,
+    which will actually take place when the <code>end()</code> method is
+    called.  You configure this rule by specifying the name of the method
+    to be called, the number of arguments it takes, and (optionally) the
+    Java class name(s) defining the type(s) of the method's arguments.
+    The actual parameter values, if any, will typically be accumulated from
+    the body content of nested elements within the element that triggered
+    this rule, using the CallParamRule discussed next.</li>
+<li><a href="CallParamRule.html">CallParamRule</a> - This rule identifies
+    the source of a particular numbered (zero-relative) parameter for a
+    CallMethodRule within which we are nested.  You can specify that the
+    parameter value be taken from a particular named attribute, or from the
+    nested body content of this element.</li>
+<li><a href="NodeCreateRule.html">NodeCreateRule</a> - A specialized rule
+    that converts part of the tree into a <code>DOM Node</code> and then
+    pushes it onto the stack.</li>
+</ul>
+
+<p>You can create instances of the standard <code>Rule</code> classes and
+register them by calling <code>digester.addRule()</code>, as described above.
+However, because their usage is so common, shorthand registration methods are
+defined for each of the standard rules, directly on the <code>Digester</code>
+class.  For example, the following code sequence:</p>
+<pre>
+    Rule rule = new SetNextRule(digester, "addChild",
+                                "com.mycompany.mypackage.MyChildClass");
+    digester.addRule("a/b/c", rule);
+</pre>
+<p>can be replaced by:</p>
+<pre>
+    digester.addSetNext("a/b/c", "addChild",
+                        "com.mycompany.mypackage.MyChildClass");
+</pre>
+
+<a name="doc.Logging"></a>
+<h3>Logging</h3>
+
+<p>Logging is a vital tool for debugging Digester rulesets. Digester can log
+copious amounts of debugging information. So, you need to know how logging
+works before you start using Digester seriously.</p>
+
+<p>Two main logs are used by Digester:</p>
+<ul>
+<li>SAX-related messages are logged to
+    <strong><code>org.apache.commons.digester.Digester.sax</code></strong>.
+    This log gives information about the basic SAX events received by
+    Digester.</li>
+<li><strong><code>org.apache.commons.digester.Digester</code></strong> is used
+    for everything else. You'll probably want to have this log turned up during
+    debugging but turned down during production due to the high message
+    volume.</li>
+</ul>
+
+<a name="doc.Usage"></a>
+<h3>Usage Examples</h3>
+
+
+<h5>Creating a Simple Object Tree</h5>
+
+<p>Let's assume that you have two simple JavaBeans, <code>Foo</code> and
+<code>Bar</code>, with the following method signatures:</p>
+<pre>
+  package mypackage;
+  public class Foo {
+    public void addBar(Bar bar);
+    public Bar findBar(int id);
+    public Iterator getBars();
+    public String getName();
+    public void setName(String name);
+  }
+
+  public mypackage;
+  public class Bar {
+    public int getId();
+    public void setId(int id);
+    public String getTitle();
+    public void setTitle(String title);
+  }
+</pre>
+
+<p>and you wish to use Digester to parse the following XML document:</p>
+
+<pre>
+  &lt;foo name="The Parent"&gt;
+    &lt;bar id="123" title="The First Child"/&gt;
+    &lt;bar id="456" title="The Second Child"/&gt;
+  &lt;/foo&gt;
+</pre>
+
+<p>A simple approach will be to use the following Digester in the following way
+to set up the parsing rules, and then process an input file containing this
+document:</p>
+
+<pre>
+  Digester digester = new Digester();
+  digester.setValidating(false);
+  digester.addObjectCreate("foo", "mypackage.Foo");
+  digester.addSetProperties("foo");
+  digester.addObjectCreate("foo/bar", "mypackage.Bar");
+  digester.addSetProperties("foo/bar");
+  digester.addSetNext("foo/bar", "addBar", "mypackage.Bar");
+  Foo foo = (Foo) digester.parse();
+</pre>
+
+<p>In order, these rules do the following tasks:</p>
+<ol>
+<li>When the outermost <code>&lt;foo&gt;</code> element is encountered,
+    create a new instance of <code>mypackage.Foo</code> and push it
+    on to the object stack.  At the end of the <code>&lt;foo&gt;</code>
+    element, this object will be popped off of the stack.</li>
+<li>Cause properties of the top object on the stack (i.e. the <code>Foo</code>
+    object that was just created and pushed) to be set based on the values
+    of the attributes of this XML element.</li>
+<li>When a nested <code>&lt;bar&gt;</code> element is encountered,
+    create a new instance of <code>mypackage.Bar</code> and push it
+    on to the object stack.  At the end of the <code>&lt;bar&gt;</code>
+    element, this object will be popped off of the stack (i.e. after the
+    remaining rules matching <code>foo/bar</code> are processed).</li>
+<li>Cause properties of the top object on the stack (i.e. the <code>Bar</code>
+    object that was just created and pushed) to be set based on the values
+    of the attributes of this XML element.  Note that type conversions
+    are automatically performed (such as String to int for the <code>id</code>
+    property), for all converters registered with the <code>ConvertUtils</code>
+    class from <code>commons-beanutils</code> package.</li>
+<li>Cause the <code>addBar</code> method of the next-to-top element on the
+    object stack (which is why this is called the "set <em>next</em>" rule)
+    to be called, passing the element that is on the top of the stack, which
+    must be of type <code>mypackage.Bar</code>.  This is the rule that causes
+    the parent/child relationship to be created.</li>
+</ol>
+
+<p>Once the parse is completed, the first object that was ever pushed on to the
+stack (the <code>Foo</code> object in this case) is returned to you.  It will
+have had its properties set, and all of its child <code>Bar</code> objects
+created for you.</p>
+
+
+<h5>Processing A Struts Configuration File</h5>
+
+<p>As stated earlier, the primary reason that the
+<code>Digester</code> package was created is because the
+Struts controller servlet itself needed a robust, flexible, easy to extend
+mechanism for processing the contents of the <code>struts-config.xml</code>
+configuration that describes nearly every aspect of a Struts-based application.
+Because of this, the controller servlet contains a comprehensive, real world,
+example of how the Digester can be employed for this type of a use case.
+See the <code>initDigester()</code> method of class
+<code>org.apache.struts.action.ActionServlet</code> for the code that creates
+and configures the Digester to be used, and the <code>initMapping()</code>
+method for where the parsing actually takes place.</p>
+
+<p>(Struts binary and source distributions can be acquired at
+<a href="http://struts.apache.org/">http://struts.apache.org/</a>.)</p>
+
+<p>The following discussion highlights a few of the matching patterns and
+processing rules that are configured, to illustrate the use of some of the
+Digester features.  First, let's look at how the Digester instance is
+created and initialized:</p>
+<pre>
+    Digester digester = new Digester();
+    digester.push(this); // Push controller servlet onto the stack
+    digester.setValidating(true);
+</pre>
+
+<p>We see that a new Digester instance is created, and is configured to use
+a validating parser.  Validation will occur against the struts-config_1_0.dtd
+DTD that is included with Struts (as discussed earlier).  In order to provide
+a means of tracking the configured objects, the controller servlet instance
+itself will be added to the digester's stack.</p>
+
+<pre>
+    digester.addObjectCreate("struts-config/global-forwards/forward",
+                             forwardClass, "className");
+    digester.addSetProperties("struts-config/global-forwards/forward");
+    digester.addSetNext("struts-config/global-forwards/forward",
+                        "addForward",
+                        "org.apache.struts.action.ActionForward");
+    digester.addSetProperty
+      ("struts-config/global-forwards/forward/set-property",
+       "property", "value");
+</pre>
+
+<p>The rules created by these lines are used to process the global forward
+declarations.  When a <code>&lt;forward&gt;</code> element is encountered,
+the following actions take place:</p>
+<ul>
+<li>A new object instance is created -- the <code>ActionForward</code>
+    instance that will represent this definition.  The Java class name
+    defaults to that specified as an initialization parameter (which
+    we have stored in the String variable <code>forwardClass</code>), but can
+    be overridden by using the "className" attribute (if it is present in the
+    XML element we are currently parsing).  The new <code>ActionForward</code>
+    instance is pushed onto the stack.</li>
+<li>The properties of the <code>ActionForward</code> instance (at the top of
+    the stack) are configured based on the attributes of the
+    <code>&lt;forward&gt;</code> element.</li>
+<li>Nested occurrences of the <code>&lt;set-property&gt;</code> element
+    cause calls to additional property setter methods to occur.  This is
+    required only if you have provided a custom implementation of the
+    <code>ActionForward</code> class with additional properties that are
+    not included in the DTD.</li>
+<li>The <code>addForward()</code> method of the next-to-top object on
+    the stack (i.e. the controller servlet itself) will be called, passing
+    the object at the top of the stack (i.e. the <code>ActionForward</code>
+    instance) as an argument.  This causes the global forward to be
+    registered, and as a result of this it will be remembered even after
+    the stack is popped.</li>
+<li>At the end of the <code>&lt;forward&gt;</code> element, the top element
+    (i.e. the <code>ActionForward</code> instance) will be popped off the
+    stack.</li>
+</ul>
+
+<p>Later on, the digester is actually executed as follows:</p>
+<pre>
+    InputStream input =
+      getServletContext().getResourceAsStream(config);
+    ...
+    try {
+        digester.parse(input);
+        input.close();
+    } catch (SAXException e) {
+        ... deal with the problem ...
+    }
+</pre>
+
+<p>As a result of the call to <code>parse()</code>, all of the configuration
+information that was defined in the <code>struts-config.xml</code> file is
+now represented as collections of objects cached within the Struts controller
+servlet, as well as being exposed as servlet context attributes.</p>
+
+
+<h5>Parsing Body Text In XML Files</h5>
+
+<p>The Digester module also allows you to process the nested body text in an
+XML file, not just the elements and attributes that are encountered.  The
+following example is based on an assumed need to parse the web application
+deployment descriptor (<code>/WEB-INF/web.xml</code>) for the current web
+application, and record the configuration information for a particular
+servlet.  To record this information, assume the existence of a bean class
+with the following method signatures (among others):</p>
+<pre>
+  package com.mycompany;
+  public class ServletBean {
+    public void setServletName(String servletName);
+    public void setServletClass(String servletClass);
+    public void addInitParam(String name, String value);
+  }
+</pre>
+
+<p>We are going to process the <code>web.xml</code> file that declares the
+controller servlet in a typical Struts-based application (abridged for
+brevity in this example):</p>
+<pre>
+  &lt;web-app&gt;
+    ...
+    &lt;servlet&gt;
+      &lt;servlet-name&gt;action&lt;/servlet-name&gt;
+      &lt;servlet-class&gt;org.apache.struts.action.ActionServlet&lt;servlet-class&gt;
+      &lt;init-param&gt;
+        &lt;param-name&gt;application&lt;/param-name&gt;
+        &lt;param-value&gt;org.apache.struts.example.ApplicationResources&lt;param-value&gt;
+      &lt;/init-param&gt;
+      &lt;init-param&gt;
+        &lt;param-name&gt;config&lt;/param-name&gt;
+        &lt;param-value&gt;/WEB-INF/struts-config.xml&lt;param-value&gt;
+      &lt;/init-param&gt;
+    &lt;/servlet&gt;
+    ...
+  &lt;/web-app&gt;
+</pre>
+
+<p>Next, lets define some Digester processing rules for this input file:</p>
+<pre>
+  digester.addObjectCreate("web-app/servlet",
+                           "com.mycompany.ServletBean");
+  digester.addCallMethod("web-app/servlet/servlet-name", "setServletName", 0);
+  digester.addCallMethod("web-app/servlet/servlet-class",
+                         "setServletClass", 0);
+  digester.addCallMethod("web-app/servlet/init-param",
+                         "addInitParam", 2);
+  digester.addCallParam("web-app/servlet/init-param/param-name", 0);
+  digester.addCallParam("web-app/servlet/init-param/param-value", 1);
+</pre>
+
+<p>Now, as elements are parsed, the following processing occurs:</p>
+<ul>
+<li><em>&lt;servlet&gt;</em> - A new <code>com.mycompany.ServletBean</code>
+    object is created, and pushed on to the object stack.</li>
+<li><em>&lt;servlet-name&gt;</em> - The <code>setServletName()</code> method
+    of the top object on the stack (our <code>ServletBean</code>) is called,
+    passing the body content of this element as a single parameter.</li>
+<li><em>&lt;servlet-class&gt;</em> - The <code>setServletClass()</code> method
+    of the top object on the stack (our <code>ServletBean</code>) is called,
+    passing the body content of this element as a single parameter.</li>
+<li><em>&lt;init-param&gt;</em> - A call to the <code>addInitParam</code>
+    method of the top object on the stack (our <code>ServletBean</code>) is
+    set up, but it is <strong>not</strong> called yet.  The call will be
+    expecting two <code>String</code> parameters, which must be set up by
+    subsequent call parameter rules.</li>
+<li><em>&lt;param-name&gt;</em> - The body content of this element is assigned
+    as the first (zero-relative) argument to the call we are setting up.</li>
+<li><em>&lt;param-value&gt;</em> - The body content of this element is assigned
+    as the second (zero-relative) argument to the call we are setting up.</li>
+<li><em>&lt;/init-param&gt;</em> - The call to <code>addInitParam()</code>
+    that we have set up is now executed, which will cause a new name-value
+    combination to be recorded in our bean.</li>
+<li><em>&lt;init-param&gt;</em> - The same set of processing rules are fired
+    again, causing a second call to <code>addInitParam()</code> with the
+    second parameter's name and value.</li>
+<li><em>&lt;/servlet&gt;</em> - The element on the top of the object stack
+    (which should be the <code>ServletBean</code> we pushed earlier) is
+    popped off the object stack.</li>
+</ul>
+
+
+<a name="doc.Namespace"></a>
+<h3>Namespace Aware Parsing</h3>
+
+<p>For digesting XML documents that do not use XML namespaces, the default
+behavior of <code>Digester</code>, as described above, is generally sufficient.
+However, if the document you are processing uses namespaces, it is often
+convenient to have sets of <code>Rule</code> instances that are <em>only</em>
+matched on elements that use the prefix of a particular namespace.  This
+approach, for example, makes it possible to deal with element names that are 
+the same in different namespaces, but where you want to perform different 
+processing for each namespace. </p>
+
+<p>Digester does not provide full support for namespaces, but does provide
+sufficient to accomplish most tasks. Enabling digester's namespace support
+is done by following these steps:</p>
+
+<ol>
+<li>Tell <code>Digester</code> that you will be doing namespace
+    aware parsing, by adding this statement in your initalization
+    of the Digester's properties:
+    <pre>
+    digester.setNamespaceAware(true);
+    </pre></li>
+<li>Declare the public namespace URI of the namespace with which
+    following rules will be associated.  Note that you do <em>not</em>
+    make any assumptions about the prefix - the XML document author
+    is free to pick whatever prefix they want:
+    <pre>
+    digester.setRuleNamespaceURI("http://www.mycompany.com/MyNamespace");
+    </pre></li>
+<li>Add the rules that correspond to this namespace, in the usual way,
+    by calling methods like <code>addObjectCreate()</code> or
+    <code>addSetProperties()</code>.  In the matching patterns you specify,
+    use only the <em>local name</em> portion of the elements (i.e. the
+    part after the prefix and associated colon (":") character:
+    <pre>
+    digester.addObjectCreate("foo/bar", "com.mycompany.MyFoo");
+    digester.addSetProperties("foo/bar");
+    </pre></li>
+<li>Repeat the previous two steps for each additional public namespace URI
+    that should be recognized on this <code>Digester</code> run.</li>
+</ol>
+
+<p>Now, consider that you might wish to digest the following document, using
+the rules that were set up in the steps above:</p>
+<pre>
+&lt;m:foo
+   xmlns:m="http://www.mycompany.com/MyNamespace"
+   xmlns:y="http://www.yourcompany.com/YourNamespace"&gt;
+
+  &lt;m:bar name="My Name" value="My Value"/&gt;
+
+  &lt;y:bar id="123" product="Product Description"/&gt;L
+
+&lt;/x:foo&gt;
+</pre>
+
+<p>Note that your object create and set properties rules will be fired for the
+<em>first</em> occurrence of the <code>bar</code> element, but not the
+<em>second</em> one.  This is because we declared that our rules only matched
+for the particular namespace we are interested in.  Any elements in the
+document that are associated with other namespaces (or no namespaces at all)
+will not be processed.  In this way, you can easily create rules that digest
+only the portions of a compound document that they understand, without placing
+any restrictions on what other content is present in the document.</p>
+
+<p>You might also want to look at <a href="#doc.RuleSets">Encapsulated
+Rule Sets</a> if you wish to reuse a particular set of rules, associated
+with a particular namespace, in more than one application context.</p>
+
+<h4>Using Namespace Prefixes In Pattern Matching</h4>
+
+<p>Using rules with namespaces is very useful when you have orthogonal rulesets. 
+One ruleset applies to a namespace and is independent of other rulesets applying
+to other namespaces. However, if your rule logic requires mixed namespaces, then 
+matching namespace prefix patterns might be a better strategy.</p>
+
+<p>When you set the <code>NamespaceAware</code> property to false, digester uses
+the qualified element name (which includes the namespace prefix) rather than the
+local name as the patten component for the element. This means that your pattern
+matches can include namespace prefixes as well as element names. So, rather than
+create namespace-aware rules, create pattern matches including the namespace
+prefixes.</p>
+
+<p>For example, (with <code>NamespaceAware</code> false), the pattern <code>
+'foo:bar'</code> will match a top level element named <code>'bar'</code> in the 
+namespace with (local) prefix <code>'foo'</code>.</p>
+
+<h4>Limitations of Digester Namespace support</h4>
+<p>Digester does not provide general "xpath-compliant" matching;
+only the namespace attached to the <i>last</i> element in the match path
+is involved in the matching process. Namespaces attached to parent
+elements are ignored for matching purposes.</p>
+
+
+<a name="doc.Pluggable"></a>
+<h3>Pluggable Rules Processing</h3>
+
+<p>By default, <code>Digester</code> selects the rules that match a particular
+pattern of nested elements as described under
+<a href="#doc.Patterns">Element Matching Patterns</a>.  If you prefer to use
+different selection policies, however, you can create your own implementation
+of the <a href="Rules.html">org.apache.commons.digester.Rules</a> interface,
+or subclass the corresponding convenience base class
+<a href="RulesBase.html">org.apache.commons.digester.RulesBase</a>.
+Your implementation of the <code>match()</code> method will be called when the
+processing for a particular element is started or ended, and you must return
+a <code>List</code> of the rules that are relevant for the current nesting
+pattern.  The order of the rules you return <strong>is</strong> significant,
+and should match the order in which rules were initally added.</p>
+
+<p>Your policy for rule selection should generally be sensitive to whether
+<a href="#doc.Namespace">Namespace Aware Parsing</a> is taking place.  In
+general, if <code>namespaceAware</code> is true, you should select only rules
+that:</p>
+<ul>
+<li>Are registered for the public namespace URI that corresponds to the
+    prefix being used on this element.</li>
+<li>Match on the "local name" portion of the element (so that the document
+    creator can use any prefix that they like).</li>
+</ul>
+
+<h4>ExtendedBaseRules</h4>
+<p><a href="ExtendedBaseRules.html">ExtendedBaseRules</a>,
+adds some additional expression syntax for pattern matching
+to the default mechanism, but it also executes more slowly.  See the
+JavaDocs for more details on the new pattern matching syntax, and suggestions
+on when this implementation should be used.  To use it, simply do the
+following as part of your Digester initialization:</p>
+
+<pre>
+  Digester digester = ...
+  ...
+  digester.setRules(new ExtendedBaseRules());
+  ...
+</pre>
+
+<h4>RegexRules</h4>
+<p><a href="RegexRules.html">RegexRules</a> is an advanced <code>Rules</code> 
+implementation which does not build on the default pattern matching rules.
+It uses a pluggable <a href="RegexMatcher.html">RegexMatcher</a> implementation to test 
+if a path matches the pattern for a Rule. All matching rules are returned 
+(note that this behaviour differs from longest matching rule of the default
+ pattern matching rules). See the Java Docs for more details.
+</p>
+<p>
+Example usage:
+</p>
+
+<pre>
+  Digester digester = ...
+  ...
+  digester.setRules(new RegexRules(new SimpleRegexMatcher()));
+  ...
+</pre>
+<h5>RegexMatchers</h5>
+<p>
+<code>Digester</code> ships only with one <code>RegexMatcher</code>
+implementation: <a href='SimpleRegexMatcher.html'>SimpleRegexMatcher</a>.
+This implementation is unsophisticated and lacks many good features
+lacking in more power Regex libraries. There are some good reasons
+why this approach was adopted. The first is that <code>SimpleRegexMatcher</code>
+is simple, it is easy to write and runs quickly. The second has to do with 
+the way that <code>RegexRules</code> is intended to be used.
+</p>
+<p>
+There are many good regex libraries available. (For example 
+<a href='http://jakarta.apache.org/oro/index.html'>Jakarta ORO</a>,
+<a href='http://jakarta.apache.org/regexp/index.html'>Jakarta Regex</a>,
+<a href='http://www.cacas.org/java/gnu/regexp/'>GNU Regex</a> and
+<a href='http://java.sun.com/j2se/1.4.2/docs/api/java/util/regex/package-summary.html'>
+Java 1.4 Regex</a>)
+Not only do different people have different personal tastes when it comes to
+regular expression matching but these products all offer different functionality
+and different strengths.
+</p>
+<p>
+The pluggable <code>RegexMatcher</code> is a thin bridge
+designed to adapt other Regex systems. This allows any Regex library the user
+desires to be plugged in and used just by creating one class.
+<code>Digester</code> does not (currently) ship with bridges to the major
+regex (to allow the dependencies required by <code>Digester</code>
+to be kept to a minimum).
+</p>
+
+<h4>WithDefaultsRulesWrapper</h4>
+<p>
+<a href="WithDefaultsRulesWrapper.html"> WithDefaultsRulesWrapper</a> allows 
+default <code>Rule</code> instances to be added to any existing 
+<code>Rules</code> implementation. These default <code>Rule</code> instances 
+will be returned for any match for which the wrapped implementation does not 
+return any matches. 
+</p>
+<p>
+For example,
+<pre>
+    Rule alpha;
+    ...
+    WithDefaultsRulesWrapper rules = new WithDefaultsRulesWrapper(new BaseRules());
+    rules.addDefault(alpha);
+    ...
+    digester.setRules(rules);
+    ...
+</pre>
+when a pattern does not match any other rule, then rule alpha will be called.
+</p>
+<p>
+<code>WithDefaultsRulesWrapper</code> follows the <em>Decorator</em> pattern.
+</p>
+
+<a name="doc.RuleSets"></a>
+<h3>Encapsulated Rule Sets</h3>
+
+<p>All of the examples above have described a scenario where the rules to be
+processed are registered with a <code>Digester</code> instance immediately
+after it is created.  However, this approach makes it difficult to reuse the
+same set of rules in more than one application environment.  Ideally, one
+could package a set of rules into a single class, which could be easily
+loaded and registered with a <code>Digester</code> instance in one easy step.
+</p>
+
+<p>The <a href="RuleSet.html">RuleSet</a> interface (and the convenience base
+class <a href="RuleSetBase.html">RuleSetBase</a>) make it possible to do this.
+In addition, the rule instances registered with a particular
+<code>RuleSet</code> can optionally be associated with a particular namespace,
+as described under <a href="#doc.Namespace">Namespace Aware Processing</a>.</p>
+
+<p>An example of creating a <code>RuleSet</code> might be something like this:
+</p>
+<pre>
+public class MyRuleSet extends RuleSetBase {
+
+  public MyRuleSet() {
+    this("");
+  }
+
+  public MyRuleSet(String prefix) {
+    super();
+    this.prefix = prefix;
+    this.namespaceURI = "http://www.mycompany.com/MyNamespace";
+  }
+
+  protected String prefix = null;
+
+  public void addRuleInstances(Digester digester) {
+    digester.addObjectCreate(prefix + "foo/bar",
+      "com.mycompany.MyFoo");
+    digester.addSetProperties(prefix + "foo/bar");
+  }
+
+}
+</pre>
+
+<p>You might use this <code>RuleSet</code> as follow to initialize a
+<code>Digester</code> instance:</p>
+<pre>
+  Digester digester = new Digester();
+  ... configure Digester properties ...
+  digester.addRuleSet(new MyRuleSet("baz/"));
+</pre>
+
+<p>A couple of interesting notes about this approach:</p>
+<ul>
+<li>The application that is using these rules does not need to know anything
+    about the fact that the <code>RuleSet</code> being used is associated
+    with a particular namespace URI.  That knowledge is emedded inside the
+    <code>RuleSet</code> class itself.</li>
+<li>If desired, you could make a set of rules work for more than one
+    namespace URI by providing constructors on the <code>RuleSet</code> to
+    allow this to be specified dynamically.</li>
+<li>The <code>MyRuleSet</code> example above illustrates another technique
+    that increases reusability -- you can specify (as an argument to the
+    constructor) the leading portion of the matching pattern to be used.
+    In this way, you can construct a <code>Digester</code> that recognizes
+    the same set of nested elements at different nesting levels within an
+    XML document.</li>
+</ul>
+<a name="doc.NamedStacks"></a>
+<h3>Using Named Stacks For Inter-Rule Communication</h3>
+<p>
+<code>Digester</code> is based on <code>Rule</code> instances working together 
+to process xml. For anything other than the most trival processing, 
+communication between <code>Rule</code> instances is necessary. Since <code>Rule</code>
+instances are processed in sequence, this usually means storing an Object 
+somewhere where later instances can retrieve it.
+</p>
+<p>
+<code>Digester</code> is based on SAX. The most natural data structure to use with 
+SAX based xml processing is the stack. This allows more powerful processes to be
+specified more simply since the pushing and popping of objects can mimic the 
+nested structure of the xml. 
+</p>
+<p>
+<code>Digester</code> uses two basic stacks: one for the main beans and the other 
+for parameters for method calls. These are inadequate for complex processing 
+where many different <code>Rule</code> instances need to communicate through 
+different channels.
+</p>
+<p>
+In this case, it is recommended that named stacks are used. In addition to the
+two basic stacks, <code>Digester</code> allows rules to use an unlimited number
+of other stacks referred two by an identifying string (the name). (That's where
+the term <em>named stack</em> comes from.) These stacks are 
+accessed through calls to:
+</p>
+<ul>
+    <li><a href='Digester.html#push(java.lang.String, java.lang.Object)'>
+        void push(String stackName, Object value)</a></li>
+    <li><a href='Digester.html#pop(java.lang.String)'>
+        Object pop(String stackName)</a></li>
+    <li><a href='Digester.html#peek(java.lang.String)'>
+        Object peek(String stackName)</a></li>
+</ul>
+<p>
+<strong>Note:</strong> all stack names beginning with <code>org.apache.commons.digester</code>
+are reserved for future use by the <code>Digester</code> component. It is also recommended
+that users choose stack names perfixed by the name of their own domain to avoid conflicts
+with other <code>Rule</code> implementations.
+</p>
+<a name="doc.RegisteringDTDs"></a>
+<h3>Registering DTDs</h3>
+
+<h4>Brief (But Still Too Long) Introduction To System and Public Identifiers</h4>
+<p>A definition for an external entity comes in one of two forms:
+</p>
+<ol>
+    <li><code>SYSTEM <em>system-identifier</em></code></li>
+    <li><code>PUBLIC <em>public-identifier</em> <em>system-identifier</em></code></li>
+</ol>
+<p>
+The <code><em>system-identifier</em></code> is an URI from which the resource can be obtained
+(either directly or indirectly). Many valid URIs may identify the same resource.
+The <code><em>public-identifier</em></code> is an additional free identifier which may be used
+(by the parser) to locate the resource. 
+</p>
+<p>
+In practice, the weakness with a <code><em>system-identifier</em></code> is that most parsers
+will attempt to interprete this URI as an URL, try to download the resource directly
+from the URL and stop the parsing if this download fails. So, this means that 
+almost always the URI will have to be an URL from which the declaration
+can be downloaded.
+</p>
+<p>
+URLs may be local or remote but if the URL is chosen to be local, it is likely only
+to function correctly on a small number of machines (which are configured precisely
+to allow the xml to be parsed). This is usually unsatisfactory and so a universally
+accessable URL is preferred. This usually means an internet URL.
+</p>
+<p>
+To recap, in practice the <code><em>system-identifier</em></code> will (most likely) be an 
+internet URL. Unfortunately downloading from an internet URL is not only slow
+but unreliable (since successfully downloading a document from the internet 
+relies on the client being connect to the internet and the server being
+able to satisfy the request).
+</p>
+<p>
+The <code><em>public-identifier</em></code> is a freely defined name but (in practice) it is 
+strongly recommended that a unique, readable and open format is used (for reasons
+that should become clear later). A Formal Public Identifier (FPI) is a very
+common choice. This public identifier is often used to provide a unique and location
+independent key which can be used to subsistute local resources for remote ones 
+(hint: this is why ;).
+</p>
+<p>
+By using the second (<code>PUBLIC</code>) form combined with some form of local
+catalog (which matches <code><em>public-identifiers</em></code> to local resources) and where
+the <code><em>public-identifier</em></code> is a unique name and the <code><em>system-identifier</em></code> 
+is an internet URL, the practical disadvantages of specifying just a 
+<code><em>system-identifier</em></code> can be avoided. Those external entities which have been 
+store locally (on the machine parsing the document) can be identified and used.
+Only when no local copy exists is it necessary to download the document
+from the internet URL. This naming scheme is recommended when using <code>Digester</code>.
+</p>
+
+<h4>External Entity Resolution Using Digester</h4>
+<p>
+SAX factors out the resolution of external entities into an <code>EntityResolver</code>.
+<code>Digester</code> supports the use of custom <code>EntityResolver</code> 
+but ships with a simple internal implementation. This implementation allows local URLs
+to be easily associated with <code><em>public-identifiers</em></code>. 
+</p>
+<p>For example:</p>
+<code><pre>
+    digester.register("-//Example Dot Com //DTD Sample Example//EN", "assets/sample.dtd");
+</pre></code>
+<p>
+will make digester return the relative file path <code>assets/sample.dtd</code> 
+whenever an external entity with public id 
+<code>-//Example Dot Com //DTD Sample Example//EN</code> is needed.
+</p>
+<p><strong>Note:</strong> This is a simple (but useful) implementation. 
+Greater sophistication requires a custom <code>EntityResolver</code>.</p>
+    
+<a name="doc.troubleshooting"></a>
+<h3>Troubleshooting</h3>
+<h4>Debugging Exceptions</h4>
+<p>
+<code>Digester</code> is based on <a href='http://www.saxproject.org'>SAX</a>.
+Digestion throws two kinds of <code>Exception</code>:
+</p>
+<ul>
+    <li><code>java.io.IOException</code></li>
+    <li><code>org.xml.sax.SAXException</code></li>
+</ul>
+<p>
+The first is rarely thrown and indicates the kind of fundemental IO exception
+that developers know all about. The second is thrown by SAX parsers when the processing
+of the XML cannot be completed. So, to diagnose the cause a certain familiarity with 
+the way that SAX error handling works is very useful. 
+</p>
+<h5>Diagnosing SAX Exceptions</h5>
+<p>
+This is a short, potted guide to SAX error handling strategies. It's not intended as a
+proper guide to error handling in SAX.
+</p>
+<p>
+When a SAX parser encounters a problem with the xml (well, ok - sometime after it 
+encounters a problem) it will throw a 
+<a href='http://www.saxproject.org/apidoc/org/xml/sax/SAXParseException.html'>
+SAXParseException</a>. This is a subclass of <code>SAXException</code> and contains
+a bit of extra information about what exactly when wrong - and more importantly,
+where it went wrong. If you catch an exception of this sort, you can be sure that
+the problem is with the XML and not <code>Digester</code> or your rules.
+It is usually a good idea to catch this exception and log the extra information
+to help with diagnosing the reason for the failure.
+</p>
+<p>
+General <a href='http://www.saxproject.org/apidoc/org/xml/sax/SAXException.html'>
+SAXException</a> instances may wrap a causal exception. When exceptions are
+throw by <code>Digester</code> each of these will be wrapped into a 
+<code>SAXException</code> and rethrown. So, catch these and examine the wrapped
+exception to diagnose what went wrong.
+</p>
+<a name="doc.FAQ"></a>
+<h3>Frequently Asked Questions</h3>
+<p><ul>
+<li><strong>Why do I get warnings when using a JAXP 1.1 parser?</strong>
+<p>If you're using a JAXP 1.1 parser, you might see the following warning (in your log):
+<code><pre>
+[WARN] Digester - -Error: JAXP SAXParser property not recognized: http://java.sun.com/xml/jaxp/properties/schemaLanguage
+</pre></code>
+This property is needed for JAXP 1.2 (XML Schema support) as required
+for the Servlet Spec. 2.4 but is not recognized by JAXP 1.1 parsers.
+This warning is harmless.</p>
+<p>
+</li>
+<li><strong>Why Doesn't Schema Validation Work With Parser XXX Out Of The Box?</strong>
+<p>
+Schema location and language settings are often need for validation using schemas.
+Unfortunately, there isn't a single standard approach to how these properties are
+configured on a parser.
+Digester tries to guess the parser being used and configure it appropriately
+but it's not infallible.
+You might need to grab an instance, configure it and pass it to Digester.
+</p>
+<p>
+If you want to support more than one parser in a portable manner, 
+then you'll probably want to take a look at the 
+<code>org.apache.commons.digester.parsers</code> package
+and add a new class to support the particular parser that's causing problems.
+</p>
+</li>
+<li><strong>Help! 
+I'm Validating Against Schema But Digester Ignores Errors!</strong>
+<p>
+Digester is based on <a href='http://www.saxproject.org'>SAX</a>. The convention for
+SAX parsers is that all errors are reported (to any registered 
+<code>ErrorHandler</code>) but processing continues. Digester (by default) 
+registers its own <code>ErrorHandler</code> implementation. This logs details 
+but does not stop the processing (following the usual convention for SAX 
+based processors). 
+</p>
+<p>
+This means that the errors reported by the validation of the schema will appear in the
+Digester logs but the processing will continue. To change this behaviour, call
+<code>digester.setErrorHandler</code> with a more suitable implementation.
+</p>
+
+<li><strong>Where Can I Find Example Code?</strong>
+<a name="doc.FAQ.Examples">
+<p>Digester ships with a sample application: a mapping for the <em>Rich Site 
+Summary</em> format used by many newsfeeds. Download the source distribution 
+to see how it works.</p>
+<p>Digester also ships with a set of examples demonstrating most of the 
+features described in this document. See the "src/examples" subdirectory 
+of the source distribution.</p>
+</li>
+<li><strong>When Are You Going To Support <em>Rich Site Summary</em> Version x.y.z?</strong>
+<p>
+The <em>Rich Site Summary</em> application is intended to be a sample application. 
+It works but we have no plans to add support for other versions of the format.
+</p>
+<p>
+We would consider donations of standard digester applications but it's unlikely that
+these would ever be shipped with the base digester distribution.
+If you want to discuss this, please post to <a href='http://commons.apache.org/mail-lists.html'>
+commons dev mailing list</a>
+</p>
+</li>
+</ul>
+<a name="doc.Limits"></a>
+<h3>Known Limitations</h3>
+<h4>Accessing Public Methods In A Default Access Superclass</h4>
+<p>There is an issue when invoking public methods contained in a default access superclass.
+Reflection locates these methods fine and correctly assigns them as public.
+However, an <code>IllegalAccessException</code> is thrown if the method is invoked.</p>
+
+<p><code>MethodUtils</code> contains a workaround for this situation. 
+It will attempt to call <code>setAccessible</code> on this method.
+If this call succeeds, then the method can be invoked as normal.
+This call will only succeed when the application has sufficient security privilages. 
+If this call fails then a warning will be logged and the method may fail.</p>
+
+<p><code>Digester</code> uses <code>MethodUtils</code> and so there may be an issue accessing methods
+of this kind from a high security environment. If you think that you might be experiencing this 
+problem, please ask on the mailing list.</p>
+</body>
+</html>
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/ContentType.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/ContentType.java
new file mode 100644
index 0000000..187d503
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/ContentType.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.util.http;
+
+
+/**
+ * Useful methods for Content-Type processing
+ * 
+ * @author James Duncan Davidson [duncan@eng.sun.com]
+ * @author James Todd [gonzo@eng.sun.com]
+ * @author Jason Hunter [jch@eng.sun.com]
+ * @author Harish Prabandham
+ * @author costin@eng.sun.com
+ */
+public class ContentType {
+
+    /**
+     * Parse the character encoding from the specified content type header.
+     * If the content type is null, or there is no explicit character encoding,
+     * <code>null</code> is returned.
+     *
+     * @param contentType a content type header
+     */
+    public static String getCharsetFromContentType(String contentType) {
+
+        if (contentType == null)
+            return (null);
+        int start = contentType.indexOf("charset=");
+        if (start < 0)
+            return (null);
+        String encoding = contentType.substring(start + 8);
+        int end = encoding.indexOf(';');
+        if (end >= 0)
+            encoding = encoding.substring(0, end);
+        encoding = encoding.trim();
+        if ((encoding.length() > 2) && (encoding.startsWith("\""))
+            && (encoding.endsWith("\"")))
+            encoding = encoding.substring(1, encoding.length() - 1);
+        return (encoding.trim());
+
+    }
+
+
+    /**
+     * Returns true if the given content type contains a charset component,
+     * false otherwise.
+     *
+     * @param type Content type
+     * @return true if the given content type contains a charset component,
+     * false otherwise
+     */
+    public static boolean hasCharset(String type) {
+
+        boolean hasCharset = false;
+
+        int len = type.length();
+        int index = type.indexOf(';');
+        while (index != -1) {
+            index++;
+            while (index < len && Character.isSpace(type.charAt(index))) {
+                index++;
+            }
+            if (index+8 < len
+                    && type.charAt(index) == 'c'
+                    && type.charAt(index+1) == 'h'
+                    && type.charAt(index+2) == 'a'
+                    && type.charAt(index+3) == 'r'
+                    && type.charAt(index+4) == 's'
+                    && type.charAt(index+5) == 'e'
+                    && type.charAt(index+6) == 't'
+                    && type.charAt(index+7) == '=') {
+                hasCharset = true;
+                break;
+            }
+            index = type.indexOf(';', index);
+        }
+
+        return hasCharset;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/CookieSupport.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/CookieSupport.java
new file mode 100644
index 0000000..6456de3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/CookieSupport.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;
+
+
+/**
+ * Static constants for this package.
+ */
+public final class CookieSupport {
+
+    // --------------------------------------------------------------- Constants
+    /**
+     * If set to true, we parse cookies strictly according to the servlet,
+     * cookie and HTTP specs by default.
+     */
+    public static final boolean STRICT_SERVLET_COMPLIANCE;
+
+    /**
+     * If true, cookie values are allowed to contain an equals character without
+     * being quoted.
+     */
+    public static final boolean ALLOW_EQUALS_IN_VALUE;
+
+    /**
+     * If true, separators that are not explicitly dis-allowed by the v0 cookie
+     * spec but are disallowed by the HTTP spec will be allowed in v0 cookie
+     * names and values. These characters are: \"()/:<=>?@[\\]{} Note that the
+     * inclusion of / depends on the value of {@link #FWD_SLASH_IS_SEPARATOR}.
+     */
+    public static final boolean ALLOW_HTTP_SEPARATORS_IN_V0;
+    
+    /**
+     * If set to false, we don't use the IE6/7 Max-Age/Expires work around.
+     * Default is usually true. If STRICT_SERVLET_COMPLIANCE==true then default
+     * is false. Explicitly setting always takes priority.
+     */
+    public static final boolean ALWAYS_ADD_EXPIRES;
+
+    /**
+     * 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.
+     */
+    public static final boolean FWD_SLASH_IS_SEPARATOR;
+
+    /**
+     * If true, name only cookies will be permitted.
+     */
+    public static final boolean ALLOW_NAME_ONLY;
+
+    /**
+     * The list of separators that apply to version 0 cookies. To quote the
+     * spec, these are comma, semi-colon and white-space. The HTTP spec
+     * definition of linear white space is [CRLF] 1*( SP | HT )
+     */
+    private static final char[] V0_SEPARATORS = {',', ';', ' ', '\t'};
+    private static final boolean[] V0_SEPARATOR_FLAGS = new boolean[128];
+    
+    /**
+     * The list of separators that apply to version 1 cookies. This may or may
+     * not include '/' depending on the setting of
+     * {@link #FWD_SLASH_IS_SEPARATOR}.
+     */
+    private static final char[] HTTP_SEPARATORS;
+    private static final boolean[] HTTP_SEPARATOR_FLAGS = new boolean[128];
+    
+    static {
+        STRICT_SERVLET_COMPLIANCE = Boolean.valueOf(System.getProperty(
+                "org.apache.catalina.STRICT_SERVLET_COMPLIANCE",
+                "false")).booleanValue();
+        
+        ALLOW_EQUALS_IN_VALUE = Boolean.valueOf(System.getProperty(
+                "org.apache.tomcat.util.http.ServerCookie.ALLOW_EQUALS_IN_VALUE",
+                "false")).booleanValue();
+        
+        ALLOW_HTTP_SEPARATORS_IN_V0 = Boolean.valueOf(System.getProperty(
+                "org.apache.tomcat.util.http.ServerCookie.ALLOW_HTTP_SEPARATORS_IN_V0",
+                "false")).booleanValue();
+        
+        String alwaysAddExpires = System.getProperty(
+        "org.apache.tomcat.util.http.ServerCookie.ALWAYS_ADD_EXPIRES");
+        if (alwaysAddExpires == null) {
+            ALWAYS_ADD_EXPIRES = !STRICT_SERVLET_COMPLIANCE;
+        } else {
+            ALWAYS_ADD_EXPIRES =
+                Boolean.valueOf(alwaysAddExpires).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();
+        }
+        
+        ALLOW_NAME_ONLY = Boolean.valueOf(System.getProperty(
+                "org.apache.tomcat.util.http.ServerCookie.ALLOW_NAME_ONLY",
+                "false")).booleanValue();
+        
+
+        /*
+        Excluding the '/' char by default violates the RFC, but 
+        it looks like a lot of people put '/'
+        in unquoted values: '/': ; //47 
+        '\t':9 ' ':32 '\"':34 '(':40 ')':41 ',':44 ':':58 ';':59 '<':60 
+        '=':61 '>':62 '?':63 '@':64 '[':91 '\\':92 ']':93 '{':123 '}':125
+        */
+        if (CookieSupport.FWD_SLASH_IS_SEPARATOR) {
+            HTTP_SEPARATORS = new char[] { '\t', ' ', '\"', '(', ')', ',', '/', 
+                    ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '{', '}' };
+        } else {
+            HTTP_SEPARATORS = new char[] { '\t', ' ', '\"', '(', ')', ',', 
+                    ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '{', '}' };
+        }
+        for (int i = 0; i < 128; i++) {
+            V0_SEPARATOR_FLAGS[i] = false;
+            HTTP_SEPARATOR_FLAGS[i] = false;
+        }
+        for (int i = 0; i < V0_SEPARATORS.length; i++) {
+            V0_SEPARATOR_FLAGS[V0_SEPARATORS[i]] = true;
+        }
+        for (int i = 0; i < HTTP_SEPARATORS.length; i++) {
+            HTTP_SEPARATOR_FLAGS[HTTP_SEPARATORS[i]] = true;
+        }
+
+    }
+    
+    // ----------------------------------------------------------------- Methods
+
+    /**
+     * Returns true if the byte is a separator as defined by V0 of the cookie
+     * spec.
+     */
+    public static final boolean isV0Separator(final char c) {
+        if (c < 0x20 || c >= 0x7f) {
+            if (c != 0x09) {
+                throw new IllegalArgumentException(
+                        "Control character in cookie value or attribute.");
+            }
+        }
+
+        return V0_SEPARATOR_FLAGS[c];
+    }
+    
+    public static boolean isV0Token(String value) {
+        if( value==null) return false;
+        
+        int i = 0;
+        int len = value.length();
+
+        if (alreadyQuoted(value)) {
+            i++;
+            len--;
+        }
+        
+        for (; i < len; i++) {
+            char c = value.charAt(i);
+
+            if (isV0Separator(c))
+                return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if the byte is a separator as defined by V1 of the cookie
+     * spec, RFC2109.
+     * @throws IllegalArgumentException if a control character was supplied as
+     *         input
+     */
+    public static final boolean isHttpSeparator(final char c) {
+        if (c < 0x20 || c >= 0x7f) {
+            if (c != 0x09) {
+                throw new IllegalArgumentException(
+                        "Control character in cookie value or attribute.");
+            }
+        }
+
+        return HTTP_SEPARATOR_FLAGS[c];
+    }
+
+    public static boolean isHttpToken(String value) {
+        if( value==null) return false;
+        
+        int i = 0;
+        int len = value.length();
+
+        if (alreadyQuoted(value)) {
+            i++;
+            len--;
+        }
+        
+        for (; i < len; i++) {
+            char c = value.charAt(i);
+
+            if (isHttpSeparator(c))
+                return true;
+        }
+        return false;
+    }
+
+    public static boolean alreadyQuoted (String value) {
+        if (value==null || value.length() < 2) return false;
+        return (value.charAt(0)=='\"' && value.charAt(value.length()-1)=='\"');
+    }
+
+
+    // ------------------------------------------------------------- Constructor
+    private CookieSupport() {
+        // Utility class. Don't allow instances to be created.
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/Cookies.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/Cookies.java
new file mode 100644
index 0000000..857d0fd
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/Cookies.java
@@ -0,0 +1,495 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
+
+/**
+ * A collection of cookies - reusable and tuned for server side performance.
+ * Based on RFC2965 ( and 2109 )
+ *
+ * This class is not synchronized.
+ *
+ * @author Costin Manolache
+ * @author kevin seguin
+ */
+public final class Cookies { // extends MultiMap {
+
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog(Cookies.class );
+    
+    // expected average number of cookies per request
+    public static final int INITIAL_SIZE=4; 
+    ServerCookie scookies[]=new ServerCookie[INITIAL_SIZE];
+    int cookieCount=0;
+    boolean unprocessed=true;
+
+    MimeHeaders headers;
+
+    /**
+     *  Construct a new cookie collection, that will extract
+     *  the information from headers.
+     *
+     * @param headers Cookies are lazy-evaluated and will extract the
+     *     information from the provided headers.
+     */
+    public Cookies(MimeHeaders headers) {
+        this.headers=headers;
+    }
+
+    /**
+     * Recycle.
+     */
+    public void recycle() {
+            for( int i=0; i< cookieCount; i++ ) {
+            if( scookies[i]!=null )
+                scookies[i].recycle();
+        }
+        cookieCount=0;
+        unprocessed=true;
+    }
+
+    /**
+     * EXPENSIVE!!!  only for debugging.
+     */
+    @Override
+    public String toString() {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        pw.println("=== Cookies ===");
+        int count = getCookieCount();
+        for (int i = 0; i < count; ++i) {
+            pw.println(getCookie(i).toString());
+        }
+        return sw.toString();
+    }
+
+    // -------------------- Indexed access --------------------
+    
+    public ServerCookie getCookie( int idx ) {
+        if( unprocessed ) {
+            getCookieCount(); // will also update the cookies
+        }
+        return scookies[idx];
+    }
+
+    public int getCookieCount() {
+        if( unprocessed ) {
+            unprocessed=false;
+            processCookies(headers);
+        }
+        return cookieCount;
+    }
+
+    // -------------------- Adding cookies --------------------
+
+    /** Register a new, initialized cookie. Cookies are recycled, and
+     *  most of the time an existing ServerCookie object is returned.
+     *  The caller can set the name/value and attributes for the cookie
+     */
+    private ServerCookie addCookie() {
+        if( cookieCount >= scookies.length  ) {
+            ServerCookie scookiesTmp[]=new ServerCookie[2*cookieCount];
+            System.arraycopy( scookies, 0, scookiesTmp, 0, cookieCount);
+            scookies=scookiesTmp;
+        }
+        
+        ServerCookie c = scookies[cookieCount];
+        if( c==null ) {
+            c= new ServerCookie();
+            scookies[cookieCount]=c;
+        }
+        cookieCount++;
+        return c;
+    }
+
+
+    // code from CookieTools 
+
+    /** Add all Cookie found in the headers of a request.
+     */
+    public  void processCookies( MimeHeaders headers ) {
+        if( headers==null )
+            return;// nothing to process
+        // process each "cookie" header
+        int pos=0;
+        while( pos>=0 ) {
+            // Cookie2: version ? not needed
+            pos=headers.findHeader( "Cookie", pos );
+            // no more cookie headers headers
+            if( pos<0 ) break;
+
+            MessageBytes cookieValue=headers.getValue( pos );
+            if( cookieValue==null || cookieValue.isNull() ) {
+                pos++;
+                continue;
+            }
+
+            // Uncomment to test the new parsing code
+            if( cookieValue.getType() != MessageBytes.T_BYTES ) {
+                Exception e = new Exception();
+                log.warn("Cookies: Parsing cookie as String. Expected bytes.",
+                        e);
+                cookieValue.toBytes();
+            }
+            if(log.isDebugEnabled())
+                log.debug("Cookies: Parsing b[]: " + cookieValue.toString());
+            ByteChunk bc=cookieValue.getByteChunk();
+            processCookieHeader( bc.getBytes(),
+                                 bc.getOffset(),
+                                 bc.getLength());
+            pos++;// search from the next position
+        }
+    }
+
+    // XXX will be refactored soon!
+    private static boolean equals( String s, byte b[], int start, int end) {
+        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;
+    }
+    
+
+    /**
+     * Returns true if the byte is a whitespace character as
+     * defined in RFC2619
+     * JVK
+     */
+    private static final boolean isWhiteSpace(final byte c) {
+        // This switch statement is slightly slower
+        // for my vm than the if statement.
+        // Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_07-164)
+        /* 
+        switch (c) {
+        case ' ':;
+        case '\t':;
+        case '\n':;
+        case '\r':;
+        case '\f':;
+            return true;
+        default:;
+            return false;
+        }
+        */
+       if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f')
+           return true;
+       else
+           return false;
+    }
+
+    /**
+     * Unescapes any double quotes in the given cookie value.
+     *
+     * @param bc The cookie value to modify
+     */
+    private static void unescapeDoubleQuotes(ByteChunk bc) {
+
+        if (bc == null || bc.getLength() == 0 || bc.indexOf('"', 0) == -1) {
+            return;
+        }
+
+        int src = bc.getStart();
+        int end = bc.getEnd();
+        int dest = src;
+        byte[] buffer = bc.getBuffer();
+        
+        while (src < end) {
+            if (buffer[src] == '\\' && src < end && buffer[src+1]  == '"') {
+                src++;
+            }
+            buffer[dest] = buffer[src];
+            dest ++;
+            src ++;
+        }
+        bc.setEnd(dest);
+    }
+
+    /**
+     * Parses a cookie header after the initial "Cookie:"
+     * [WS][$]token[WS]=[WS](token|QV)[;|,]
+     * RFC 2965
+     * JVK
+     */
+    protected final void processCookieHeader(byte bytes[], int off, int len){
+        if( len<=0 || bytes==null ) return;
+        int end=off+len;
+        int pos=off;
+        int nameStart=0;
+        int nameEnd=0;
+        int valueStart=0;
+        int valueEnd=0;
+        int version = 0;
+        ServerCookie sc=null;
+        boolean isSpecial;
+        boolean isQuoted;
+
+        while (pos < end) {
+            isSpecial = false;
+            isQuoted = false;
+
+            // Skip whitespace and non-token characters (separators)
+            while (pos < end && 
+                   (CookieSupport.isHttpSeparator((char) bytes[pos]) &&
+                           !CookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0 ||
+                    CookieSupport.isV0Separator((char) bytes[pos]) ||
+                    isWhiteSpace(bytes[pos]))) 
+                {pos++; } 
+
+            if (pos >= end)
+                return;
+
+            // Detect Special cookies
+            if (bytes[pos] == '$') {
+                isSpecial = true;
+                pos++;
+            }
+
+            // Get the cookie/attribute name. This must be a token            
+            valueEnd = valueStart = nameStart = pos; 
+            pos = nameEnd = getTokenEndPosition(bytes,pos,end,version,true);
+
+            // Skip whitespace
+            while (pos < end && isWhiteSpace(bytes[pos])) {pos++; } 
+         
+
+            // Check for an '=' -- This could also be a name-only
+            // cookie at the end of the cookie header, so if we
+            // are past the end of the header, but we have a name
+            // skip to the name-only part.
+            if (pos < (end - 1) && bytes[pos] == '=') {                
+
+                // Skip whitespace
+                do {
+                    pos++;
+                } while (pos < end && isWhiteSpace(bytes[pos])); 
+
+                if (pos >= end)
+                    return;
+
+                // Determine what type of value this is, quoted value,
+                // token, name-only with an '=', or other (bad)
+                switch (bytes[pos]) {
+                case '"': // Quoted Value
+                    isQuoted = true;
+                    valueStart=pos + 1; // strip "
+                    // getQuotedValue returns the position before 
+                    // at the last quote. This must be dealt with
+                    // when the bytes are copied into the cookie
+                    valueEnd=getQuotedValueEndPosition(bytes, 
+                                                       valueStart, end);
+                    // We need pos to advance
+                    pos = valueEnd; 
+                    // Handles cases where the quoted value is 
+                    // unterminated and at the end of the header, 
+                    // e.g. [myname="value]
+                    if (pos >= end)
+                        return;
+                    break;
+                case ';':
+                case ',':
+                    // Name-only cookie with an '=' after the name token
+                    // This may not be RFC compliant
+                    valueStart = valueEnd = -1;
+                    // The position is OK (On a delimiter)
+                    break;
+                default:
+                    if (version == 0 &&
+                                !CookieSupport.isV0Separator((char)bytes[pos]) &&
+                                CookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0 ||
+                            !CookieSupport.isHttpSeparator((char)bytes[pos]) ||
+                            bytes[pos] == '=' && CookieSupport.ALLOW_EQUALS_IN_VALUE) {
+                        // Token
+                        valueStart=pos;
+                        // getToken returns the position at the delimiter
+                        // or other non-token character
+                        valueEnd=getTokenEndPosition(bytes, valueStart, end,
+                                version, false);
+                        // We need pos to advance
+                        pos = valueEnd;
+                    } else  {
+                        // INVALID COOKIE, advance to next delimiter
+                        // The starting character of the cookie value was
+                        // not valid.
+                        log.info("Cookies: Invalid cookie. " +
+                                "Value not a token or quoted value");
+                        while (pos < end && bytes[pos] != ';' && 
+                               bytes[pos] != ',') 
+                            {pos++; }
+                        pos++;
+                        // Make sure no special avpairs can be attributed to 
+                        // the previous cookie by setting the current cookie
+                        // to null
+                        sc = null;
+                        continue;                        
+                    }
+                }
+            } else {
+                // Name only cookie
+                valueStart = valueEnd = -1;
+                pos = nameEnd;
+
+            }
+          
+            // We should have an avpair or name-only cookie at this
+            // point. Perform some basic checks to make sure we are
+            // in a good state.
+  
+            // Skip whitespace
+            while (pos < end && isWhiteSpace(bytes[pos])) {pos++; }
+
+
+            // Make sure that after the cookie we have a separator. This
+            // is only important if this is not the last cookie pair
+            while (pos < end && bytes[pos] != ';' && bytes[pos] != ',') { 
+                pos++;
+            }
+            
+            pos++;
+
+            // All checks passed. Add the cookie, start with the 
+            // special avpairs first
+            if (isSpecial) {
+                isSpecial = false;
+                // $Version must be the first avpair in the cookie header
+                // (sc must be null)
+                if (equals( "Version", bytes, nameStart, nameEnd) && 
+                    sc == null) {
+                    // Set version
+                    if( bytes[valueStart] =='1' && valueEnd == (valueStart+1)) {
+                        version=1;
+                    } else {
+                        // unknown version (Versioning is not very strict)
+                    }
+                    continue;
+                } 
+                
+                // We need an active cookie for Path/Port/etc.
+                if (sc == null) {
+                    continue;
+                }
+
+                // Domain is more common, so it goes first
+                if (equals( "Domain", bytes, nameStart, nameEnd)) {
+                    sc.getDomain().setBytes( bytes,
+                                           valueStart,
+                                           valueEnd-valueStart);
+                    continue;
+                } 
+
+                if (equals( "Path", bytes, nameStart, nameEnd)) {
+                    sc.getPath().setBytes( bytes,
+                                           valueStart,
+                                           valueEnd-valueStart);
+                    continue;
+                } 
+
+                // v2 cookie attributes - skip them
+                if (equals( "Port", bytes, nameStart, nameEnd)) {
+                    continue;
+                } 
+                if (equals( "CommentURL", bytes, nameStart, nameEnd)) {
+                    continue;
+                } 
+
+                // Unknown cookie, complain
+                log.info("Cookies: Unknown Special Cookie");
+
+            } else { // Normal Cookie
+                if (valueStart == -1 && !CookieSupport.ALLOW_NAME_ONLY) {
+                    // Skip name only cookies if not supported
+                    continue;
+                }
+
+                sc = addCookie();
+                sc.setVersion( version );
+                sc.getName().setBytes( bytes, nameStart,
+                                       nameEnd-nameStart);
+                
+                if (valueStart != -1) { // Normal AVPair
+                    sc.getValue().setBytes( bytes, valueStart,
+                            valueEnd-valueStart);
+                    if (isQuoted) {
+                        // We know this is a byte value so this is safe
+                        unescapeDoubleQuotes(sc.getValue().getByteChunk());
+                    }
+                } else {
+                    // Name Only
+                    sc.getValue().setString(""); 
+                }
+                continue;
+            }
+        }
+    }
+
+    /**
+     * Given the starting position of a token, this gets the end of the
+     * token, with no separator characters in between.
+     * JVK
+     */
+    private static final int getTokenEndPosition(byte bytes[], int off, int end,
+            int version, boolean isName){
+        int pos = off;
+        while (pos < end &&
+                (!CookieSupport.isHttpSeparator((char)bytes[pos]) ||
+                 version == 0 &&
+                        CookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0 &&
+                        bytes[pos] != '=' &&
+                        !CookieSupport.isV0Separator((char)bytes[pos]) ||
+                 !isName && bytes[pos] == '=' &&
+                         CookieSupport.ALLOW_EQUALS_IN_VALUE)) {
+            pos++;
+        }
+        
+        if (pos > end)
+            return end;
+        return pos;
+    }
+
+    /** 
+     * Given a starting position after an initial quote character, this gets
+     * the position of the end quote. This escapes anything after a '\' char
+     * JVK RFC 2616
+     */
+    private static final int getQuotedValueEndPosition(byte bytes[], int off, int end){
+        int pos = off;
+        while (pos < end) {
+            if (bytes[pos] == '"') {
+                return pos;                
+            } else if (bytes[pos] == '\\' && pos < (end - 1)) {
+                pos+=2;
+            } else {
+                pos++;
+            }
+        }
+        // Error, we have reached the end of the header w/o a end quote
+        return end;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/FastHttpDateFormat.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/FastHttpDateFormat.java
new file mode 100644
index 0000000..fa16d32
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/FastHttpDateFormat.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;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Utility class to generate HTTP dates.
+ * 
+ * @author Remy Maucherat
+ */
+public final class FastHttpDateFormat {
+
+
+    // -------------------------------------------------------------- Variables
+
+
+    private static final int CACHE_SIZE = 
+        Integer.parseInt(System.getProperty("org.apache.tomcat.util.http.FastHttpDateFormat.CACHE_SIZE", "1000"));
+
+    
+    /**
+     * HTTP date format.
+     */
+    private static final SimpleDateFormat format = 
+        new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
+
+
+    /**
+     * The set of SimpleDateFormat formats to use in getDateHeader().
+     */
+    private 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)
+    };
+
+
+    private 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);
+
+    }
+
+
+    /**
+     * Instant on which the currentDate object was generated.
+     */
+    private static volatile long currentDateGenerated = 0L;
+
+
+    /**
+     * Current formatted date.
+     */
+    private static String currentDate = null;
+
+
+    /**
+     * Formatter cache.
+     */
+    private static final ConcurrentHashMap<Long, String> formatCache = 
+        new ConcurrentHashMap<Long, String>(CACHE_SIZE);
+
+
+    /**
+     * Parser cache.
+     */
+    private static final ConcurrentHashMap<String, Long> parseCache = 
+        new ConcurrentHashMap<String, Long>(CACHE_SIZE);
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Get the current date in HTTP format.
+     */
+    public static final String getCurrentDate() {
+
+        long now = System.currentTimeMillis();
+        if ((now - currentDateGenerated) > 1000) {
+            synchronized (format) {
+                if ((now - currentDateGenerated) > 1000) {
+                    currentDate = format.format(new Date(now));
+                    currentDateGenerated = now;
+                }
+            }
+        }
+        return currentDate;
+
+    }
+
+
+    /**
+     * Get the HTTP format of the specified date.
+     */
+    public static final String formatDate
+        (long value, DateFormat threadLocalformat) {
+
+        Long longValue = new Long(value);
+        String cachedDate = formatCache.get(longValue);
+        if (cachedDate != null)
+            return cachedDate;
+
+        String newDate = null;
+        Date dateValue = new Date(value);
+        if (threadLocalformat != null) {
+            newDate = threadLocalformat.format(dateValue);
+            updateFormatCache(longValue, newDate);
+        } else {
+            synchronized (formatCache) {
+                synchronized (format) {
+                    newDate = format.format(dateValue);
+                }
+                updateFormatCache(longValue, newDate);
+            }
+        }
+        return newDate;
+
+    }
+
+
+    /**
+     * Try to parse the given date as a HTTP date.
+     */
+    public static final long parseDate(String value, 
+                                       DateFormat[] threadLocalformats) {
+
+        Long cachedDate = parseCache.get(value);
+        if (cachedDate != null)
+            return cachedDate.longValue();
+
+        Long date = null;
+        if (threadLocalformats != null) {
+            date = internalParseDate(value, threadLocalformats);
+            updateParseCache(value, date);
+        } else {
+            synchronized (parseCache) {
+                date = internalParseDate(value, formats);
+                updateParseCache(value, date);
+            }
+        }
+        if (date == null) {
+            return (-1L);
+        }
+
+        return date.longValue();
+    }
+
+
+    /**
+     * Parse date with given formatters.
+     */
+    private static final Long internalParseDate
+        (String value, DateFormat[] formats) {
+        Date date = null;
+        for (int i = 0; (date == null) && (i < formats.length); i++) {
+            try {
+                date = formats[i].parse(value);
+            } catch (ParseException e) {
+                // Ignore
+            }
+        }
+        if (date == null) {
+            return null;
+        }
+        return new Long(date.getTime());
+    }
+
+
+    /**
+     * Update cache.
+     */
+    private static void updateFormatCache(Long key, String value) {
+        if (value == null) {
+            return;
+        }
+        if (formatCache.size() > CACHE_SIZE) {
+            formatCache.clear();
+        }
+        formatCache.put(key, value);
+    }
+
+
+    /**
+     * Update cache.
+     */
+    private static void updateParseCache(String key, Long value) {
+        if (value == null) {
+            return;
+        }
+        if (parseCache.size() > CACHE_SIZE) {
+            parseCache.clear();
+        }
+        parseCache.put(key, value);
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/HttpMessages.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/HttpMessages.java
new file mode 100644
index 0000000..8abb5c1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/HttpMessages.java
@@ -0,0 +1,138 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.http;
+
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Handle (internationalized) HTTP messages.
+ * 
+ * @author James Duncan Davidson [duncan@eng.sun.com]
+ * @author James Todd [gonzo@eng.sun.com]
+ * @author Jason Hunter [jch@eng.sun.com]
+ * @author Harish Prabandham
+ * @author costin@eng.sun.com
+ */
+public class HttpMessages {
+    // XXX move message resources in this package
+    protected static final StringManager sm =
+        StringManager.getManager("org.apache.tomcat.util.http.res");
+        
+    static String st_200=null;
+    static String st_302=null;
+    static String st_400=null;
+    static String st_404=null;
+    
+    /** Get the status string associated with a status code.
+     *  No I18N - return the messages defined in the HTTP spec.
+     *  ( the user isn't supposed to see them, this is the last
+     *  thing to translate)
+     *
+     *  Common messages are cached.
+     *
+     */
+    public static String getMessage( int status ) {
+        // method from Response.
+        
+        // Does HTTP requires/allow international messages or
+        // are pre-defined? The user doesn't see them most of the time
+        switch( status ) {
+        case 200:
+            if( st_200==null ) st_200=sm.getString( "sc.200");
+            return st_200;
+        case 302:
+            if( st_302==null ) st_302=sm.getString( "sc.302");
+            return st_302;
+        case 400:
+            if( st_400==null ) st_400=sm.getString( "sc.400");
+            return st_400;
+        case 404:
+            if( st_404==null ) st_404=sm.getString( "sc.404");
+            return st_404;
+        }
+        return sm.getString("sc."+ status);
+    }
+
+    /**
+     * 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());
+    }
+
+    /**
+     * Is the provided message safe to use in an HTTP header. Safe messages must
+     * meet the requirements of RFC2616 - i.e. must consist only of TEXT.
+     * 
+     * @param msg   The message to test
+     * @return      <code>true</code> if the message is safe to use in an HTTP
+     *              header else <code>false</code>
+     */
+    public static boolean isSafeInHttpHeader(String msg) {
+        // Nulls are fine. It is up to the calling code to address any NPE
+        // concerns
+        if (msg == null) {
+            return true;
+        }
+
+        // Reason-Phrase is defined as *<TEXT, excluding CR, LF>
+        // TEXT is defined as any OCTET except CTLs, but including LWS
+        // OCTET is defined as an 8-bit sequence of data
+        // CTL is defined as octets 0-31 and 127
+        // LWS, if we exclude CR LF pairs, is defined as SP or HT (32, 9)
+        final int len = msg.length();
+        for (int i = 0; i < len; i++) {
+            char c = msg.charAt(i);
+            if (32 <= c && c <= 126 || 128 <= c && c <= 255 || c == 9) {
+                continue;
+            }
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/MimeHeaders.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/MimeHeaders.java
new file mode 100644
index 0000000..0ac2c8c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/MimeHeaders.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.tomcat.util.http;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Enumeration;
+
+import org.apache.tomcat.util.buf.MessageBytes;
+
+/* XXX XXX XXX Need a major rewrite  !!!!
+ */
+
+/**
+ * This class is used to contain standard internet message headers,
+ * used for SMTP (RFC822) and HTTP (RFC2068) messages as well as for
+ * MIME (RFC 2045) applications such as transferring typed data and
+ * grouping related items in multipart message bodies.
+ *
+ * <P> Message headers, as specified in RFC822, include a field name
+ * and a field body.  Order has no semantic significance, and several
+ * fields with the same name may exist.  However, most fields do not
+ * (and should not) exist more than once in a header.
+ *
+ * <P> Many kinds of field body must conform to a specified syntax,
+ * including the standard parenthesized comment syntax.  This class
+ * supports only two simple syntaxes, for dates and integers.
+ *
+ * <P> When processing headers, care must be taken to handle the case of
+ * multiple same-name fields correctly.  The values of such fields are
+ * only available as strings.  They may be accessed by index (treating
+ * the header as an array of fields), or by name (returning an array
+ * of string values).
+ */
+
+/* Headers are first parsed and stored in the order they are
+   received. This is based on the fact that most servlets will not
+   directly access all headers, and most headers are single-valued.
+   ( the alternative - a hash or similar data structure - will add
+   an overhead that is not needed in most cases )
+   
+   Apache seems to be using a similar method for storing and manipulating
+   headers.
+       
+   Future enhancements:
+   - hash the headers the first time a header is requested ( i.e. if the
+   servlet needs direct access to headers).
+   - scan "common" values ( length, cookies, etc ) during the parse
+   ( addHeader hook )
+   
+*/
+
+
+/**
+ *  Memory-efficient repository for Mime Headers. When the object is recycled, it
+ *  will keep the allocated headers[] and all the MimeHeaderField - no GC is generated.
+ *
+ *  For input headers it is possible to use the MessageByte for Fields - so no GC
+ *  will be generated.
+ *
+ *  The only garbage is generated when using the String for header names/values -
+ *  this can't be avoided when the servlet calls header methods, but is easy
+ *  to avoid inside tomcat. The goal is to use _only_ MessageByte-based Fields,
+ *  and reduce to 0 the memory overhead of tomcat.
+ *
+ *  TODO:
+ *  XXX one-buffer parsing - for http ( other protocols don't need that )
+ *  XXX remove unused methods
+ *  XXX External enumerations, with 0 GC.
+ *  XXX use HeaderName ID
+ *  
+ * 
+ * @author dac@eng.sun.com
+ * @author James Todd [gonzo@eng.sun.com]
+ * @author Costin Manolache
+ * @author kevin seguin
+ */
+public class MimeHeaders {
+    /** Initial size - should be == average number of headers per request
+     *  XXX  make it configurable ( fine-tuning of web-apps )
+     */
+    public static final int DEFAULT_HEADER_SIZE=8;
+    
+    /**
+     * The header fields.
+     */
+    private MimeHeaderField[] headers = new
+        MimeHeaderField[DEFAULT_HEADER_SIZE];
+
+    /**
+     * The current number of header fields.
+     */
+    private int count;
+
+    /**
+     * Creates a new MimeHeaders object using a default buffer size.
+     */
+    public MimeHeaders() {
+        // NO-OP
+    }
+
+    /**
+     * Clears all header fields.
+     */
+    // [seguin] added for consistency -- most other objects have recycle().
+    public void recycle() {
+        clear();
+    }
+
+    /**
+     * Clears all header fields.
+     */
+    public void clear() {
+        for (int i = 0; i < count; i++) {
+            headers[i].recycle();
+        }
+        count = 0;
+    }
+
+    /**
+     * EXPENSIVE!!!  only for debugging.
+     */
+    @Override
+    public String toString() {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        pw.println("=== MimeHeaders ===");
+        Enumeration<String> e = names();
+        while (e.hasMoreElements()) {
+            String n = e.nextElement();
+            pw.println(n + " = " + getHeader(n));
+        }
+        return sw.toString();
+    }
+
+    // -------------------- Idx access to headers ----------
+    
+    /**
+     * Returns the current number of header fields.
+     */
+    public int size() {
+        return count;
+    }
+
+    /**
+     * Returns the Nth header name, or null if there is no such header.
+     * This may be used to iterate through all header fields.
+     */
+    public MessageBytes getName(int n) {
+        return n >= 0 && n < count ? headers[n].getName() : null;
+    }
+
+    /**
+     * Returns the Nth header value, or null if there is no such header.
+     * This may be used to iterate through all header fields.
+     */
+    public MessageBytes getValue(int n) {
+        return n >= 0 && n < count ? headers[n].getValue() : null;
+    }
+
+    /** Find the index of a header with the given name.
+     */
+    public int findHeader( String name, int starting ) {
+        // We can use a hash - but it's not clear how much
+        // benefit you can get - there is an  overhead 
+        // and the number of headers is small (4-5 ?)
+        // Another problem is that we'll pay the overhead
+        // of constructing the hashtable
+
+        // A custom search tree may be better
+        for (int i = starting; i < count; i++) {
+            if (headers[i].getName().equalsIgnoreCase(name)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+    
+    // -------------------- --------------------
+
+    /**
+     * Returns an enumeration of strings representing the header field names.
+     * Field names may appear multiple times in this enumeration, indicating
+     * that multiple fields with that name exist in this header.
+     */
+    public Enumeration<String> names() {
+        return new NamesEnumerator(this);
+    }
+
+    public Enumeration<String> values(String name) {
+        return new ValuesEnumerator(this, name);
+    }
+
+    // -------------------- Adding headers --------------------
+    
+
+    /**
+     * Adds a partially constructed field to the header.  This
+     * field has not had its name or value initialized.
+     */
+    private MimeHeaderField createHeader() {
+        MimeHeaderField mh;
+        int len = headers.length;
+        if (count >= len) {
+            // expand header list array
+            MimeHeaderField tmp[] = new MimeHeaderField[count * 2];
+            System.arraycopy(headers, 0, tmp, 0, len);
+            headers = tmp;
+        }
+        if ((mh = headers[count]) == null) {
+            headers[count] = mh = new MimeHeaderField();
+        }
+        count++;
+        return mh;
+    }
+
+    /** Create a new named header , return the MessageBytes
+        container for the new value
+    */
+    public MessageBytes addValue( String name ) {
+         MimeHeaderField mh = createHeader();
+        mh.getName().setString(name);
+        return mh.getValue();
+    }
+
+    /** Create a new named header using un-translated byte[].
+        The conversion to chars can be delayed until
+        encoding is known.
+     */
+    public MessageBytes addValue(byte b[], int startN, int len)
+    {
+        MimeHeaderField mhf=createHeader();
+        mhf.getName().setBytes(b, startN, len);
+        return mhf.getValue();
+    }
+
+    /** Create a new named header using translated char[].
+     */
+    public MessageBytes addValue(char c[], int startN, int len)
+    {
+        MimeHeaderField mhf=createHeader();
+        mhf.getName().setChars(c, startN, len);
+        return mhf.getValue();
+    }
+
+    /** Allow "set" operations - 
+        return a MessageBytes container for the
+        header value ( existing header or new
+        if this .
+    */
+    public MessageBytes setValue( String name ) {
+        for ( int i = 0; i < count; i++ ) {
+            if(headers[i].getName().equalsIgnoreCase(name)) {
+                for ( int j=i+1; j < count; j++ ) {
+                    if(headers[j].getName().equalsIgnoreCase(name)) {
+                        removeHeader(j--);
+                    }
+                }
+                return headers[i].getValue();
+            }
+        }
+        MimeHeaderField mh = createHeader();
+        mh.getName().setString(name);
+        return mh.getValue();
+    }
+
+    //-------------------- Getting headers --------------------
+    /**
+     * Finds and returns a header field with the given name.  If no such
+     * field exists, null is returned.  If more than one such field is
+     * in the header, an arbitrary one is returned.
+     */
+    public MessageBytes getValue(String name) {
+        for (int i = 0; i < count; i++) {
+            if (headers[i].getName().equalsIgnoreCase(name)) {
+                return headers[i].getValue();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Finds and returns a unique header field with the given name. If no such
+     * field exists, null is returned. If the specified header field is not
+     * unique then an {@link IllegalArgumentException} is thrown. 
+     */
+    public MessageBytes getUniqueValue(String name) {
+        MessageBytes result = null;
+        for (int i = 0; i < count; i++) {
+            if (headers[i].getName().equalsIgnoreCase(name)) {
+                if (result == null) {
+                    result = headers[i].getValue();
+                } else {
+                    throw new IllegalArgumentException();
+                }
+            }
+        }
+        return result;
+    }
+
+    // bad shortcut - it'll convert to string ( too early probably,
+    // encoding is guessed very late )
+    public String getHeader(String name) {
+        MessageBytes mh = getValue(name);
+        return mh != null ? mh.toString() : null;
+    }
+
+    // -------------------- Removing --------------------
+    /**
+     * Removes a header field with the specified name.  Does nothing
+     * if such a field could not be found.
+     * @param name the name of the header field to be removed
+     */
+    public void removeHeader(String name) {
+        // XXX
+        // warning: rather sticky code; heavily tuned
+
+        for (int i = 0; i < count; i++) {
+            if (headers[i].getName().equalsIgnoreCase(name)) {
+                removeHeader(i--);
+            }
+        }
+    }
+
+    /**
+     * reset and swap with last header
+     * @param idx the index of the header to remove.
+     */
+    private void removeHeader(int idx) {
+        MimeHeaderField mh = headers[idx];
+        
+        mh.recycle();
+        headers[idx] = headers[count - 1];
+        headers[count - 1] = mh;
+        count--;
+    }
+
+}
+
+/** Enumerate the distinct header names.
+    Each nextElement() is O(n) ( a comparison is
+    done with all previous elements ).
+
+    This is less frequent than add() -
+    we want to keep add O(1).
+*/
+class NamesEnumerator implements Enumeration<String> {
+    int pos;
+    int size;
+    String next;
+    MimeHeaders headers;
+
+    public NamesEnumerator(MimeHeaders headers) {
+        this.headers=headers;
+        pos=0;
+        size = headers.size();
+        findNext();
+    }
+
+    private void findNext() {
+        next=null;
+        for(; pos< size; pos++ ) {
+            next=headers.getName( pos ).toString();
+            for( int j=0; j<pos ; j++ ) {
+                if( headers.getName( j ).equalsIgnoreCase( next )) {
+                    // duplicate.
+                    next=null;
+                    break;
+                }
+            }
+            if( next!=null ) {
+                // it's not a duplicate
+                break;
+            }
+        }
+        // next time findNext is called it will try the
+        // next element
+        pos++;
+    }
+    
+    @Override
+    public boolean hasMoreElements() {
+        return next!=null;
+    }
+
+    @Override
+    public String nextElement() {
+        String current=next;
+        findNext();
+        return current;
+    }
+}
+
+/** Enumerate the values for a (possibly ) multiple
+    value element.
+*/
+class ValuesEnumerator implements Enumeration<String> {
+    int pos;
+    int size;
+    MessageBytes next;
+    MimeHeaders headers;
+    String name;
+
+    ValuesEnumerator(MimeHeaders headers, String name) {
+        this.name=name;
+        this.headers=headers;
+        pos=0;
+        size = headers.size();
+        findNext();
+    }
+
+    private void findNext() {
+        next=null;
+        for(; pos< size; pos++ ) {
+            MessageBytes n1=headers.getName( pos );
+            if( n1.equalsIgnoreCase( name )) {
+                next=headers.getValue( pos );
+                break;
+            }
+        }
+        pos++;
+    }
+    
+    @Override
+    public boolean hasMoreElements() {
+        return next!=null;
+    }
+
+    @Override
+    public String nextElement() {
+        MessageBytes current=next;
+        findNext();
+        return current.toString();
+    }
+}
+
+class MimeHeaderField {
+    // multiple headers with same name - a linked list will
+    // speed up name enumerations and search ( both cpu and
+    // GC)
+    MimeHeaderField next;
+    MimeHeaderField prev; 
+    
+    protected final MessageBytes nameB = MessageBytes.newInstance();
+    protected final MessageBytes valueB = MessageBytes.newInstance();
+
+    /**
+     * Creates a new, uninitialized header field.
+     */
+    public MimeHeaderField() {
+        // NO-OP
+    }
+
+    public void recycle() {
+        nameB.recycle();
+        valueB.recycle();
+        next=null;
+    }
+
+    public MessageBytes getName() {
+        return nameB;
+    }
+
+    public MessageBytes getValue() {
+        return valueB;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/Parameters.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/Parameters.java
new file mode 100644
index 0000000..5541c81
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/Parameters.java
@@ -0,0 +1,377 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+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.buf.UDecoder;
+
+/**
+ * 
+ * @author Costin Manolache
+ */
+public final class Parameters {
+
+    
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog(Parameters.class );
+    
+    // Transition: we'll use the same Hashtable( String->String[] )
+    // for the beginning. When we are sure all accesses happen through
+    // this class - we can switch to MultiMap
+    private Hashtable<String,String[]> paramHashStringArray =
+        new Hashtable<String,String[]>();
+    private boolean didQueryParameters=false;
+    
+    MessageBytes queryMB;
+
+    UDecoder urlDec;
+    MessageBytes decodedQuery=MessageBytes.newInstance();
+
+    String encoding=null;
+    String queryStringEncoding=null;
+    
+    public Parameters() {
+        // NO-OP
+    }
+
+    public void setQuery( MessageBytes queryMB ) {
+        this.queryMB=queryMB;
+    }
+
+    public String getEncoding() {
+        return encoding;
+    }
+
+    public void setEncoding( String s ) {
+        encoding=s;
+        if(log.isDebugEnabled()) {
+            log.debug( "Set encoding to " + s );
+        }
+    }
+
+    public void setQueryStringEncoding( String s ) {
+        queryStringEncoding=s;
+        if(log.isDebugEnabled()) {
+            log.debug( "Set query string encoding to " + s );
+        }
+    }
+
+    public void recycle() {
+        paramHashStringArray.clear();
+        didQueryParameters=false;
+        encoding=null;
+        decodedQuery.recycle();
+    }
+    
+    // -------------------- Data access --------------------
+    // Access to the current name/values, no side effect ( processing ).
+    // You must explicitly call handleQueryParameters and the post methods.
+    
+    // This is the original data representation ( hash of String->String[])
+
+    public void addParameterValues( String key, String[] newValues) {
+        if ( key==null ) return;
+        String values[];
+        if (paramHashStringArray.containsKey(key)) {
+            String oldValues[] = paramHashStringArray.get(key);
+            values = new String[oldValues.length + newValues.length];
+            for (int i = 0; i < oldValues.length; i++) {
+                values[i] = oldValues[i];
+            }
+            for (int i = 0; i < newValues.length; i++) {
+                values[i+ oldValues.length] = newValues[i];
+            }
+        } else {
+            values = newValues;
+        }
+
+        paramHashStringArray.put(key, values);
+    }
+
+    public String[] getParameterValues(String name) {
+        handleQueryParameters();
+        // no "facade"
+        String values[] = paramHashStringArray.get(name);
+        return values;
+    }
+ 
+    public Enumeration<String> getParameterNames() {
+        handleQueryParameters();
+        return paramHashStringArray.keys();
+    }
+
+    // Shortcut.
+    public String getParameter(String name ) {
+        String[] values = getParameterValues(name);
+        if (values != null) {
+            if( values.length==0 ) return "";
+            return values[0];
+        } else {
+            return null;
+        }
+    }
+    // -------------------- Processing --------------------
+    /** Process the query string into parameters
+     */
+    public void handleQueryParameters() {
+        if( didQueryParameters ) return;
+
+        didQueryParameters=true;
+
+        if( queryMB==null || queryMB.isNull() )
+            return;
+        
+        if(log.isDebugEnabled()) {
+            log.debug("Decoding query " + decodedQuery + " " +
+                    queryStringEncoding);
+        }
+
+        try {
+            decodedQuery.duplicate( queryMB );
+        } catch (IOException e) {
+            // Can't happen, as decodedQuery can't overflow
+            e.printStackTrace();
+        }
+        processParameters( decodedQuery, queryStringEncoding );
+    }
+
+    // incredibly inefficient data representation for parameters,
+    // until we test the new one
+    private void addParam( String key, String value ) {
+        if( key==null ) return;
+        String values[];
+        if (paramHashStringArray.containsKey(key)) {
+            String oldValues[] = paramHashStringArray.get(key);
+            values = new String[oldValues.length + 1];
+            for (int i = 0; i < oldValues.length; i++) {
+                values[i] = oldValues[i];
+            }
+            values[oldValues.length] = value;
+        } else {
+            values = new String[1];
+            values[0] = value;
+        }
+        
+        
+        paramHashStringArray.put(key, values);
+    }
+
+    public void setURLDecoder( UDecoder u ) {
+        urlDec=u;
+    }
+
+    // -------------------- Parameter parsing --------------------
+    // we are called from a single thread - we can do it the hard way
+    // if needed
+    ByteChunk tmpName=new ByteChunk();
+    ByteChunk tmpValue=new ByteChunk();
+    private ByteChunk origName=new ByteChunk();
+    private ByteChunk origValue=new ByteChunk();
+    CharChunk tmpNameC=new CharChunk(1024);
+    public static final String DEFAULT_ENCODING = "ISO-8859-1";
+    
+    public void processParameters( byte bytes[], int start, int len ) {
+        processParameters(bytes, start, len, encoding);
+    }
+
+    public void processParameters( byte bytes[], int start, int len, 
+                                   String enc ) {
+        int end=start+len;
+        int pos=start;
+        
+        if(log.isDebugEnabled()) {
+            try {
+                log.debug("Bytes: " +
+                        new String(bytes, start, len, DEFAULT_ENCODING));
+            } catch (UnsupportedEncodingException e) {
+                // Should never happen...
+                log.error("Unable to convert bytes", e);
+            }
+        }
+
+        do {
+            boolean noEq=false;
+            int valStart=-1;
+            int valEnd=-1;
+            
+            int nameStart=pos;
+            int nameEnd=ByteChunk.indexOf(bytes, nameStart, end, '=' );
+            // Workaround for a&b&c encoding
+            int nameEnd2=ByteChunk.indexOf(bytes, nameStart, end, '&' );
+            if( (nameEnd2!=-1 ) &&
+                ( nameEnd==-1 || nameEnd > nameEnd2) ) {
+                nameEnd=nameEnd2;
+                noEq=true;
+                valStart=nameEnd;
+                valEnd=nameEnd;
+                if(log.isDebugEnabled()) {
+                    try {
+                        log.debug("no equal " + nameStart + " " + nameEnd + " " +
+                                new String(bytes, nameStart, nameEnd-nameStart,
+                                        DEFAULT_ENCODING) );
+                    } catch (UnsupportedEncodingException e) {
+                        // Should never happen...
+                        log.error("Unable to convert bytes", e);
+                    }
+                }
+            }
+            if( nameEnd== -1 ) 
+                nameEnd=end;
+
+            if( ! noEq ) {
+                valStart= (nameEnd < end) ? nameEnd+1 : end;
+                valEnd=ByteChunk.indexOf(bytes, valStart, end, '&');
+                if( valEnd== -1 ) valEnd = (valStart < end) ? end : valStart;
+            }
+            
+            pos=valEnd+1;
+            
+            if( nameEnd<=nameStart ) {
+                if (log.isInfoEnabled()) {
+                    StringBuilder msg = new StringBuilder("Parameters: Invalid chunk ");
+                    // No name eg ...&=xx&... will trigger this
+                    if (valEnd >= nameStart) {
+                        msg.append('\'');
+                        try {
+                            msg.append(new String(bytes, nameStart,
+                                    valEnd - nameStart, DEFAULT_ENCODING));
+                        } catch (UnsupportedEncodingException e) {
+                            // Should never happen...
+                            log.error("Unable to convert bytes", e);
+                        }
+                        msg.append("' ");
+                    }
+                    msg.append("ignored.");
+                    log.info(msg);
+                }
+                continue;
+                // invalid chunk - it's better to ignore
+            }
+            tmpName.setBytes( bytes, nameStart, nameEnd-nameStart );
+            tmpValue.setBytes( bytes, valStart, valEnd-valStart );
+            
+            // Take copies as if anything goes wrong originals will be
+            // corrupted. This means original values can be logged.
+            // For performance - only done for debug
+            if (log.isDebugEnabled()) {
+                try {
+                    origName.append(bytes, nameStart, nameEnd-nameStart);
+                    origValue.append(bytes, valStart, valEnd-valStart);
+                } catch (IOException ioe) {
+                    // Should never happen...
+                    log.error("Error copying parameters", ioe);
+                }
+            }
+            
+            try {
+                addParam( urlDecode(tmpName, enc), urlDecode(tmpValue, enc) );
+            } catch (IOException e) {
+                StringBuilder msg =
+                    new StringBuilder("Parameters: Character decoding failed.");
+                msg.append(" Parameter '");
+                if (log.isDebugEnabled()) {
+                    msg.append(origName.toString());
+                    msg.append("' with value '");
+                    msg.append(origValue.toString());
+                    msg.append("' has been ignored.");
+                    log.debug(msg, e);
+                } else if (log.isInfoEnabled()) {
+                    msg.append(tmpName.toString());
+                    msg.append("' with value '");
+                    msg.append(tmpValue.toString());
+                    msg.append("' has been ignored. Note that the name and ");
+                    msg.append("value quoted here may be corrupted due to ");
+                    msg.append("the failed decoding. Use debug level logging ");
+                    msg.append("to see the original, non-corrupted values.");
+                    log.info(msg);
+                }
+            }
+
+            tmpName.recycle();
+            tmpValue.recycle();
+            // Only recycle copies if we used them
+            if (log.isDebugEnabled()) {
+                origName.recycle();
+                origValue.recycle();
+            }
+        } while( pos<end );
+    }
+
+    private String urlDecode(ByteChunk bc, String enc)
+        throws IOException {
+        if( urlDec==null ) {
+            urlDec=new UDecoder();   
+        }
+        urlDec.convert(bc);
+        String result = null;
+        if (enc != null) {
+            bc.setEncoding(enc);
+            result = bc.toString();
+        } else {
+            CharChunk cc = tmpNameC;
+            int length = bc.getLength();
+            cc.allocate(length, -1);
+            // Default encoding: fast conversion
+            byte[] bbuf = bc.getBuffer();
+            char[] cbuf = cc.getBuffer();
+            int start = bc.getStart();
+            for (int i = 0; i < length; i++) {
+                cbuf[i] = (char) (bbuf[i + start] & 0xff);
+            }
+            cc.setChars(cbuf, 0, length);
+            result = cc.toString();
+            cc.recycle();
+        }
+        return result;
+    }
+
+    public void processParameters( MessageBytes data, String encoding ) {
+        if( data==null || data.isNull() || data.getLength() <= 0 ) return;
+
+        if( data.getType() != MessageBytes.T_BYTES ) {
+            data.toBytes();
+        }
+        ByteChunk bc=data.getByteChunk();
+        processParameters( bc.getBytes(), bc.getOffset(),
+                           bc.getLength(), encoding);
+    }
+
+    /** Debug purpose
+     */
+    public String paramsAsString() {
+        StringBuilder sb=new StringBuilder();
+        Enumeration<String> en= paramHashStringArray.keys();
+        while( en.hasMoreElements() ) {
+            String k = en.nextElement();
+            sb.append( k ).append("=");
+            String v[] = paramHashStringArray.get( k );
+            for( int i=0; i<v.length; i++ )
+                sb.append( v[i] ).append(",");
+            sb.append("\n");
+        }
+        return sb.toString();
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/ServerCookie.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/ServerCookie.java
new file mode 100644
index 0000000..ccd608f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/ServerCookie.java
@@ -0,0 +1,337 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import java.io.Serializable;
+import java.text.DateFormat;
+import java.text.FieldPosition;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.apache.tomcat.util.buf.MessageBytes;
+
+
+/**
+ *  Server-side cookie representation.
+ *  Allows recycling and uses MessageBytes as low-level
+ *  representation ( and thus the byte-> char conversion can be delayed
+ *  until we know the charset ).
+ *
+ *  Tomcat.core uses this recyclable object to represent cookies,
+ *  and the facade will convert it to the external representation.
+ */
+public class ServerCookie implements Serializable {
+    
+    private static final long serialVersionUID = 1L;
+    
+    // Version 0 (Netscape) attributes
+    private MessageBytes name=MessageBytes.newInstance();
+    private MessageBytes value=MessageBytes.newInstance();
+    // Expires - Not stored explicitly. Generated from Max-Age (see V1)
+    private MessageBytes path=MessageBytes.newInstance();
+    private MessageBytes domain=MessageBytes.newInstance();
+    private boolean secure;
+    
+    // Version 1 (RFC2109) attributes
+    private MessageBytes comment=MessageBytes.newInstance();
+    private int maxAge = -1;
+    private int version = 0;
+
+    // Other fields
+    private static final String OLD_COOKIE_PATTERN =
+        "EEE, dd-MMM-yyyy HH:mm:ss z";
+    private static final ThreadLocal<DateFormat> OLD_COOKIE_FORMAT =
+        new ThreadLocal<DateFormat>() {
+        @Override
+        protected DateFormat initialValue() {
+            DateFormat df =
+                new SimpleDateFormat(OLD_COOKIE_PATTERN, Locale.US);
+            df.setTimeZone(TimeZone.getTimeZone("GMT"));
+            return df;
+        }
+    };
+    private static final String ancientDate;
+
+    static {
+        ancientDate = OLD_COOKIE_FORMAT.get().format(new Date(10000));
+    }
+
+    // Note: Servlet Spec =< 3.0 only refers to Netscape and RFC2109,
+    // not RFC2965
+
+    // Version 2 (RFC2965) attributes that would need to be added to support
+    // v2 cookies
+    // CommentURL
+    // Discard - implied by maxAge <0
+    // Port
+
+    public ServerCookie() {
+        // NOOP
+    }
+
+    public void recycle() {
+        path.recycle();
+        name.recycle();
+        value.recycle();
+        comment.recycle();
+        maxAge=-1;
+        path.recycle();
+        domain.recycle();
+        version=0;
+        secure=false;
+    }
+
+    public MessageBytes getComment() {
+        return comment;
+    }
+
+    public MessageBytes getDomain() {
+        return domain;
+    }
+
+    public void setMaxAge(int expiry) {
+        maxAge = expiry;
+    }
+
+    public int getMaxAge() {
+        return maxAge;
+    }
+
+    public MessageBytes getPath() {
+        return path;
+    }
+
+    public void setSecure(boolean flag) {
+        secure = flag;
+    }
+
+    public boolean getSecure() {
+        return secure;
+    }
+
+    public MessageBytes getName() {
+        return name;
+    }
+
+    public MessageBytes getValue() {
+        return value;
+    }
+
+    public int getVersion() {
+        return version;
+    }
+
+    public void setVersion(int v) {
+        version = v;
+    }
+
+
+    // -------------------- utils --------------------
+
+    @Override
+    public String toString() {
+        return "Cookie " + getName() + "=" + getValue() + " ; "
+            + getVersion() + " " + getPath() + " " + getDomain();
+    }
+    
+    // -------------------- Cookie parsing tools
+
+    
+    public static void appendCookieValue( StringBuffer headerBuf,
+                                          int version,
+                                          String name,
+                                          String value,
+                                          String path,
+                                          String domain,
+                                          String comment,
+                                          int maxAge,
+                                          boolean isSecure,
+                                          boolean isHttpOnly)
+    {
+        StringBuffer buf = new StringBuffer();
+        // Servlet implementation checks name
+        buf.append( name );
+        buf.append("=");
+        // Servlet implementation does not check anything else
+        
+        /*
+         * The spec allows some latitude on when to send the version attribute
+         * with a Set-Cookie header. To be nice to clients, we'll make sure the
+         * version attribute is first. That means checking the various things
+         * that can cause us to switch to a v1 cookie first.
+         * 
+         * Note that by checking for tokens we will also throw an exception if a
+         * control character is encountered.
+         */
+        // Start by using the version we were asked for
+        int newVersion = version;
+        
+        // If it is v0, check if we need to switch
+        if (newVersion == 0 &&
+                (!CookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0 &&
+                 CookieSupport.isHttpToken(value) ||
+                 CookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0 &&
+                 CookieSupport.isV0Token(value))) {
+            // HTTP token in value - need to use v1
+            newVersion = 1;
+        }
+        
+        if (newVersion == 0 && comment != null) {
+            // Using a comment makes it a v1 cookie
+           newVersion = 1;
+        }
+
+        if (newVersion == 0 &&
+                (!CookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0 &&
+                 CookieSupport.isHttpToken(path) ||
+                 CookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0 &&
+                 CookieSupport.isV0Token(path))) {
+            // HTTP token in path - need to use v1
+            newVersion = 1;
+        }
+
+        if (newVersion == 0 &&
+                (!CookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0 &&
+                 CookieSupport.isHttpToken(domain) ||
+                 CookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0 &&
+                 CookieSupport.isV0Token(domain))) {
+            // HTTP token in domain - need to use v1
+            newVersion = 1;
+        }
+
+        // Now build the cookie header
+        // Value
+        maybeQuote(buf, value);
+        // Add version 1 specific information
+        if (newVersion == 1) {
+            // Version=1 ... required
+            buf.append ("; Version=1");
+
+            // Comment=comment
+            if ( comment!=null ) {
+                buf.append ("; Comment=");
+                maybeQuote(buf, comment);
+            }
+        }
+        
+        // Add domain information, if present
+        if (domain!=null) {
+            buf.append("; Domain=");
+            maybeQuote(buf, domain);
+        }
+
+        // Max-Age=secs ... or use old "Expires" format
+        if (maxAge >= 0) {
+            if (newVersion > 0) {
+                buf.append ("; Max-Age=");
+                buf.append (maxAge);
+            }
+            // IE6, IE7 and possibly other browsers don't understand Max-Age.
+            // They do understand Expires, even with V1 cookies!
+            if (newVersion == 0 || CookieSupport.ALWAYS_ADD_EXPIRES) {
+                // Wdy, DD-Mon-YY HH:MM:SS GMT ( Expires Netscape format )
+                buf.append ("; Expires=");
+                // To expire immediately we need to set the time in past
+                if (maxAge == 0)
+                    buf.append( ancientDate );
+                else
+                    OLD_COOKIE_FORMAT.get().format(
+                            new Date(System.currentTimeMillis() +
+                                    maxAge*1000L),
+                            buf, new FieldPosition(0));
+            }
+        }
+
+        // Path=path
+        if (path!=null) {
+            buf.append ("; Path=");
+            maybeQuote(buf, path);
+        }
+
+        // Secure
+        if (isSecure) {
+          buf.append ("; Secure");
+        }
+        
+        // HttpOnly
+        if (isHttpOnly) {
+            buf.append("; HttpOnly");
+        }
+        headerBuf.append(buf);
+    }
+
+    /**
+     * Quotes values if required.
+     * @param buf
+     * @param value
+     */
+    private static void maybeQuote (StringBuffer buf, String value) {
+        if (value==null || value.length()==0) {
+            buf.append("\"\"");
+        } else if (CookieSupport.alreadyQuoted(value)) {
+            buf.append('"');
+            buf.append(escapeDoubleQuotes(value,1,value.length()-1));
+            buf.append('"');
+        } else if (CookieSupport.isHttpToken(value) &&
+                !CookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0 ||
+                CookieSupport.isV0Token(value) &&
+                CookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0) {
+            buf.append('"');
+            buf.append(escapeDoubleQuotes(value,0,value.length()));
+            buf.append('"');
+        } else {
+            buf.append(value);
+        }
+    }
+
+
+    /**
+     * Escapes any double quotes in the given string.
+     *
+     * @param s the input string
+     * @param beginIndex start index inclusive
+     * @param endIndex exclusive
+     * @return The (possibly) escaped string
+     */
+    private static String escapeDoubleQuotes(String s, int beginIndex, int endIndex) {
+
+        if (s == null || s.length() == 0 || s.indexOf('"') == -1) {
+            return s;
+        }
+
+        StringBuffer b = new StringBuffer();
+        for (int i = beginIndex; i < endIndex; i++) {
+            char c = s.charAt(i);
+            if (c == '\\' ) {
+                b.append(c);
+                //ignore the character after an escape, just append it
+                if (++i>=endIndex) throw new IllegalArgumentException("Invalid escape character in cookie value.");
+                b.append(s.charAt(i));
+            } else if (c == '"')
+                b.append('\\').append('"');
+            else
+                b.append(c);
+        }
+
+        return b.toString();
+    }
+
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/ByteArrayOutputStream.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/ByteArrayOutputStream.java
new file mode 100644
index 0000000..db875ab
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/ByteArrayOutputStream.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.tomcat.util.http.fileupload;
+ 
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class implements an output stream in which the data is 
+ * written into a byte array. The buffer automatically grows as data 
+ * is written to it.
+ * <p> 
+ * The data can be retrieved using <code>toByteArray()</code> and
+ * <code>toString()</code>.
+ * <p>
+ * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
+ * this class can be called after the stream has been closed without
+ * generating an <tt>IOException</tt>.
+ * <p>
+ * This is an alternative implementation of the java.io.ByteArrayOutputStream
+ * class. The original implementation only allocates 32 bytes at the beginning.
+ * As this class is designed for heavy duty it starts at 1024 bytes. In contrast
+ * to the original it doesn't reallocate the whole memory block but allocates
+ * additional buffers. This way no buffers need to be garbage collected and
+ * the contents don't have to be copied to the new buffer. This class is
+ * designed to behave exactly like the original. The only exception is the
+ * deprecated toString(int) method that has been ignored.
+ * 
+ * @author <a href="mailto:jeremias@apache.org">Jeremias Maerki</a>
+ * @author Holger Hoffstatte
+ * @version $Id: ByteArrayOutputStream.java,v 1.1 2011/06/28 21:08:18 rherrmann Exp $
+ */
+public class ByteArrayOutputStream extends OutputStream {
+
+    /** A singleton empty byte array. */
+    private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+
+    /** The list of buffers, which grows and never reduces. */
+    private List<byte[]> buffers = new ArrayList<byte[]>();
+    /** The index of the current buffer. */
+    private int currentBufferIndex;
+    /** The total count of bytes in all the filled buffers. */
+    private int filledBufferSum;
+    /** The current buffer. */
+    private byte[] currentBuffer;
+    /** The total count of bytes written. */
+    private int count;
+
+    /**
+     * Creates a new byte array output stream. The buffer capacity is 
+     * initially 1024 bytes, though its size increases if necessary. 
+     */
+    public ByteArrayOutputStream() {
+        this(1024);
+    }
+
+    /**
+     * Creates a new byte array output stream, with a buffer capacity of 
+     * the specified size, in bytes. 
+     *
+     * @param size  the initial size
+     * @throws IllegalArgumentException if size is negative
+     */
+    public ByteArrayOutputStream(int size) {
+        if (size < 0) {
+            throw new IllegalArgumentException(
+                "Negative initial size: " + size);
+        }
+        needNewBuffer(size);
+    }
+
+    /**
+     * Return the appropriate <code>byte[]</code> buffer 
+     * specified by index.
+     *
+     * @param index  the index of the buffer required
+     * @return the buffer
+     */
+    private byte[] getBuffer(int index) {
+        return buffers.get(index);
+    }
+
+    /**
+     * Makes a new buffer available either by allocating
+     * a new one or re-cycling an existing one.
+     *
+     * @param newcount  the size of the buffer if one is created
+     */
+    private void needNewBuffer(int newcount) {
+        if (currentBufferIndex < buffers.size() - 1) {
+            //Recycling old buffer
+            filledBufferSum += currentBuffer.length;
+            
+            currentBufferIndex++;
+            currentBuffer = getBuffer(currentBufferIndex);
+        } else {
+            //Creating new buffer
+            int newBufferSize;
+            if (currentBuffer == null) {
+                newBufferSize = newcount;
+                filledBufferSum = 0;
+            } else {
+                newBufferSize = Math.max(
+                    currentBuffer.length << 1, 
+                    newcount - filledBufferSum);
+                filledBufferSum += currentBuffer.length;
+            }
+            
+            currentBufferIndex++;
+            currentBuffer = new byte[newBufferSize];
+            buffers.add(currentBuffer);
+        }
+    }
+
+    /**
+     * Write the bytes to byte array.
+     * @param b the bytes to write
+     * @param off The start offset
+     * @param len The number of bytes to write
+     */
+    @Override
+    public void write(byte[] b, int off, int len) {
+        if ((off < 0) 
+                || (off > b.length) 
+                || (len < 0) 
+                || ((off + len) > b.length) 
+                || ((off + len) < 0)) {
+            throw new IndexOutOfBoundsException();
+        } else if (len == 0) {
+            return;
+        }
+        synchronized (this) {
+            int newcount = count + len;
+            int remaining = len;
+            int inBufferPos = count - filledBufferSum;
+            while (remaining > 0) {
+                int part = Math.min(remaining, currentBuffer.length - inBufferPos);
+                System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);
+                remaining -= part;
+                if (remaining > 0) {
+                    needNewBuffer(newcount);
+                    inBufferPos = 0;
+                }
+            }
+            count = newcount;
+        }
+    }
+
+    /**
+     * Write a byte to byte array.
+     * @param b the byte to write
+     */
+    @Override
+    public synchronized void write(int b) {
+        int inBufferPos = count - filledBufferSum;
+        if (inBufferPos == currentBuffer.length) {
+            needNewBuffer(count + 1);
+            inBufferPos = 0;
+        }
+        currentBuffer[inBufferPos] = (byte) b;
+        count++;
+    }
+
+    /**
+     * Writes the entire contents of the specified input stream to this
+     * byte stream. Bytes from the input stream are read directly into the
+     * internal buffers of this streams.
+     *
+     * @param in the input stream to read from
+     * @return total number of bytes read from the input stream
+     *         (and written to this stream)
+     * @throws IOException if an I/O error occurs while reading the input stream
+     * @since Commons IO 1.4
+     */
+    public synchronized int write(InputStream in) throws IOException {
+        int readCount = 0;
+        int inBufferPos = count - filledBufferSum;
+        int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
+        while (n != -1) {
+            readCount += n;
+            inBufferPos += n;
+            count += n;
+            if (inBufferPos == currentBuffer.length) {
+                needNewBuffer(currentBuffer.length);
+                inBufferPos = 0;
+            }
+            n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
+        }
+        return readCount;
+    }
+
+    /**
+     * Return the current size of the byte array.
+     * @return the current size of the byte array
+     */
+    public synchronized int size() {
+        return count;
+    }
+
+    /**
+     * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
+     * this class can be called after the stream has been closed without
+     * generating an <tt>IOException</tt>.
+     *
+     * @throws IOException never (this method should not declare this exception
+     * but it has to now due to backwards compatability)
+     */
+    @Override
+    public void close() throws IOException {
+        //nop
+    }
+
+    /**
+     * @see java.io.ByteArrayOutputStream#reset()
+     */
+    public synchronized void reset() {
+        count = 0;
+        filledBufferSum = 0;
+        currentBufferIndex = 0;
+        currentBuffer = getBuffer(currentBufferIndex);
+    }
+
+    /**
+     * Writes the entire contents of this byte stream to the
+     * specified output stream.
+     *
+     * @param out  the output stream to write to
+     * @throws IOException if an I/O error occurs, such as if the stream is closed
+     * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
+     */
+    public synchronized void writeTo(OutputStream out) throws IOException {
+        int remaining = count;
+        for (int i = 0; i < buffers.size(); i++) {
+            byte[] buf = getBuffer(i);
+            int c = Math.min(buf.length, remaining);
+            out.write(buf, 0, c);
+            remaining -= c;
+            if (remaining == 0) {
+                break;
+            }
+        }
+    }
+
+    /**
+     * Gets the curent contents of this byte stream as a byte array.
+     * The result is independent of this stream.
+     *
+     * @return the current contents of this output stream, as a byte array
+     * @see java.io.ByteArrayOutputStream#toByteArray()
+     */
+    public synchronized byte[] toByteArray() {
+        int remaining = count;
+        if (remaining == 0) {
+            return EMPTY_BYTE_ARRAY; 
+        }
+        byte newbuf[] = new byte[remaining];
+        int pos = 0;
+        for (int i = 0; i < buffers.size(); i++) {
+            byte[] buf = getBuffer(i);
+            int c = Math.min(buf.length, remaining);
+            System.arraycopy(buf, 0, newbuf, pos, c);
+            pos += c;
+            remaining -= c;
+            if (remaining == 0) {
+                break;
+            }
+        }
+        return newbuf;
+    }
+
+    /**
+     * Gets the curent contents of this byte stream as a string.
+     * @return the contents of the byte array as a String
+     * @see java.io.ByteArrayOutputStream#toString()
+     */
+    @Override
+    public String toString() {
+        return new String(toByteArray());
+    }
+
+    /**
+     * Gets the curent contents of this byte stream as a string
+     * using the specified encoding.
+     *
+     * @param enc  the name of the character encoding
+     * @return the string converted from the byte array
+     * @throws UnsupportedEncodingException if the encoding is not supported
+     * @see java.io.ByteArrayOutputStream#toString(String)
+     */
+    public String toString(String enc) throws UnsupportedEncodingException {
+        return new String(toByteArray(), enc);
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/DeferredFileOutputStream.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/DeferredFileOutputStream.java
new file mode 100644
index 0000000..158e35d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/DeferredFileOutputStream.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.http.fileupload;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+
+/**
+ * An output stream which will retain data in memory until a specified
+ * threshold is reached, and only then commit it to disk. If the stream is
+ * closed before the threshold is reached, the data will not be written to
+ * disk at all.
+ * <p>
+ * This class originated in FileUpload processing. In this use case, you do
+ * not know in advance the size of the file being uploaded. If the file is small
+ * you want to store it in memory (for speed), but if the file is large you want
+ * to store it to file (to avoid memory issues).
+ *
+ * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ * @author gaxzerow
+ *
+ * @version $Id: DeferredFileOutputStream.java,v 1.1 2011/06/28 21:08:18 rherrmann Exp $
+ */
+public class DeferredFileOutputStream
+    extends ThresholdingOutputStream
+{
+
+    // ----------------------------------------------------------- Data members
+
+
+    /**
+     * The output stream to which data will be written prior to the theshold
+     * being reached.
+     */
+    private ByteArrayOutputStream memoryOutputStream;
+
+
+    /**
+     * The output stream to which data will be written at any given time. This
+     * will always be one of <code>memoryOutputStream</code> or
+     * <code>diskOutputStream</code>.
+     */
+    private OutputStream currentOutputStream;
+
+
+    /**
+     * The file to which output will be directed if the threshold is exceeded.
+     */
+    private File outputFile;
+
+    /**
+     * The temporary file prefix.
+     */
+    private String prefix;
+
+    /**
+     * The temporary file suffix.
+     */
+    private String suffix;
+
+    /**
+     * The directory to use for temporary files.
+     */
+    private File directory;
+
+    
+    /**
+     * True when close() has been called successfully.
+     */
+    private boolean closed = false;
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Constructs an instance of this class which will trigger an event at the
+     * specified threshold, and save data to a file beyond that point.
+     *
+     * @param threshold  The number of bytes at which to trigger an event.
+     * @param outputFile The file to which data is saved beyond the threshold.
+     */
+    public DeferredFileOutputStream(int threshold, File outputFile)
+    {
+        super(threshold);
+        this.outputFile = outputFile;
+
+        memoryOutputStream = new ByteArrayOutputStream();
+        currentOutputStream = memoryOutputStream;
+    }
+
+
+    /**
+     * Constructs an instance of this class which will trigger an event at the
+     * specified threshold, and save data to a temporary file beyond that point.
+     *
+     * @param threshold  The number of bytes at which to trigger an event.
+     * @param prefix Prefix to use for the temporary file.
+     * @param suffix Suffix to use for the temporary file.
+     * @param directory Temporary file directory.
+     *
+     * @since Commons IO 1.4
+     */
+    public DeferredFileOutputStream(int threshold, String prefix, String suffix, File directory)
+    {
+        this(threshold, (File)null);
+        if (prefix == null) {
+            throw new IllegalArgumentException("Temporary file prefix is missing");
+        }
+        this.prefix = prefix;
+        this.suffix = suffix;
+        this.directory = directory;
+    }
+
+
+    // --------------------------------------- ThresholdingOutputStream methods
+
+
+    /**
+     * Returns the current output stream. This may be memory based or disk
+     * based, depending on the current state with respect to the threshold.
+     *
+     * @return The underlying output stream.
+     *
+     * @exception IOException if an error occurs.
+     */
+    @Override
+    protected OutputStream getStream() throws IOException
+    {
+        return currentOutputStream;
+    }
+
+
+    /**
+     * Switches the underlying output stream from a memory based stream to one
+     * that is backed by disk. This is the point at which we realise that too
+     * much data is being written to keep in memory, so we elect to switch to
+     * disk-based storage.
+     *
+     * @exception IOException if an error occurs.
+     */
+    @Override
+    protected void thresholdReached() throws IOException
+    {
+        if (prefix != null) {
+            outputFile = File.createTempFile(prefix, suffix, directory);
+        }
+        FileOutputStream fos = new FileOutputStream(outputFile);
+        memoryOutputStream.writeTo(fos);
+        currentOutputStream = fos;
+        memoryOutputStream = null;
+    }
+
+
+    // --------------------------------------------------------- Public methods
+
+
+    /**
+     * Determines whether or not the data for this output stream has been
+     * retained in memory.
+     *
+     * @return <code>true</code> if the data is available in memory;
+     *         <code>false</code> otherwise.
+     */
+    public boolean isInMemory()
+    {
+        return (!isThresholdExceeded());
+    }
+
+
+    /**
+     * Returns the data for this output stream as an array of bytes, assuming
+     * that the data has been retained in memory. If the data was written to
+     * disk, this method returns <code>null</code>.
+     *
+     * @return The data for this output stream, or <code>null</code> if no such
+     *         data is available.
+     */
+    public byte[] getData()
+    {
+        if (memoryOutputStream != null)
+        {
+            return memoryOutputStream.toByteArray();
+        }
+        return null;
+    }
+
+
+    /**
+     * Returns either the output file specified in the constructor or
+     * the temporary file created or null.
+     * <p>
+     * If the constructor specifying the file is used then it returns that
+     * same output file, even when threashold has not been reached.
+     * <p>
+     * If constructor specifying a temporary file prefix/suffix is used
+     * then the temporary file created once the threashold is reached is returned
+     * If the threshold was not reached then <code>null</code> is returned.
+     *
+     * @return The file for this output stream, or <code>null</code> if no such
+     *         file exists.
+     */
+    public File getFile()
+    {
+        return outputFile;
+    }
+    
+        
+    /**
+     * Closes underlying output stream, and mark this as closed
+     *
+     * @exception IOException if an error occurs.
+     */
+    @Override
+    public void close() throws IOException
+    {
+        super.close();
+        closed = true;
+    }
+    
+    
+    /**
+     * Writes the data from this output stream to the specified output stream,
+     * after it has been closed.
+     *
+     * @param out output stream to write to.
+     * @exception IOException if this stream is not yet closed or an error occurs.
+     */
+    public void writeTo(OutputStream out) throws IOException 
+    {
+        // we may only need to check if this is closed if we are working with a file
+        // but we should force the habit of closing wether we are working with
+        // a file or memory.
+        if (!closed)
+        {
+            throw new IOException("Stream not closed");
+        }
+        
+        if(isInMemory())
+        {
+            memoryOutputStream.writeTo(out);
+        }
+        else
+        {
+            FileInputStream fis = new FileInputStream(outputFile);
+            try {
+                IOUtils.copy(fis, out);
+            } finally {
+                IOUtils.closeQuietly(fis);
+            }
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileCleaningTracker.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileCleaningTracker.java
new file mode 100644
index 0000000..55c72b3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileCleaningTracker.java
@@ -0,0 +1,259 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import java.io.File;
+import java.lang.ref.PhantomReference;
+import java.lang.ref.ReferenceQueue;
+import java.util.Collection;
+import java.util.Vector;
+
+/**
+ * Keeps track of files awaiting deletion, and deletes them when an associated
+ * marker object is reclaimed by the garbage collector.
+ * <p>
+ * This utility creates a background thread to handle file deletion.
+ * Each file to be deleted is registered with a handler object.
+ * When the handler object is garbage collected, the file is deleted.
+ * <p>
+ * In an environment with multiple class loaders (a servlet container, for
+ * example), you should consider stopping the background thread if it is no
+ * longer needed. This is done by invoking the method
+ * {@link #exitWhenFinished}, typically in
+ * {@link javax.servlet.ServletContextListener#contextDestroyed} or similar.
+ *
+ * @author Noel Bergman
+ * @author Martin Cooper
+ * @version $Id: FileCleaningTracker.java,v 1.1 2011/06/28 21:08:17 rherrmann Exp $
+ */
+public class FileCleaningTracker {
+    /**
+     * Queue of <code>Tracker</code> instances being watched.
+     */
+    ReferenceQueue /* Tracker */ q = new ReferenceQueue();
+    /**
+     * Collection of <code>Tracker</code> instances in existence.
+     */
+    final Collection<Tracker> trackers = new Vector<Tracker>();  // synchronized
+    /**
+     * Whether to terminate the thread when the tracking is complete.
+     */
+    volatile boolean exitWhenFinished = false;
+    /**
+     * The thread that will clean up registered files.
+     */
+    Thread reaper;
+
+    //-----------------------------------------------------------------------
+    /**
+     * Track the specified file, using the provided marker, deleting the file
+     * when the marker instance is garbage collected.
+     * The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used.
+     *
+     * @param file  the file to be tracked, not null
+     * @param marker  the marker object used to track the file, not null
+     * @throws NullPointerException if the file is null
+     */
+    public void track(File file, Object marker) {
+        track(file, marker, (FileDeleteStrategy) null);
+    }
+
+    /**
+     * Track the specified file, using the provided marker, deleting the file
+     * when the marker instance is garbage collected.
+     * The speified deletion strategy is used.
+     *
+     * @param file  the file to be tracked, not null
+     * @param marker  the marker object used to track the file, not null
+     * @param deleteStrategy  the strategy to delete the file, null means normal
+     * @throws NullPointerException if the file is null
+     */
+    public void track(File file, Object marker, FileDeleteStrategy deleteStrategy) {
+        if (file == null) {
+            throw new NullPointerException("The file must not be null");
+        }
+        addTracker(file.getPath(), marker, deleteStrategy);
+    }
+
+    /**
+     * Track the specified file, using the provided marker, deleting the file
+     * when the marker instance is garbage collected.
+     * The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used.
+     *
+     * @param path  the full path to the file to be tracked, not null
+     * @param marker  the marker object used to track the file, not null
+     * @throws NullPointerException if the path is null
+     */
+    public void track(String path, Object marker) {
+        track(path, marker, (FileDeleteStrategy) null);
+    }
+
+    /**
+     * Track the specified file, using the provided marker, deleting the file
+     * when the marker instance is garbage collected.
+     * The speified deletion strategy is used.
+     *
+     * @param path  the full path to the file to be tracked, not null
+     * @param marker  the marker object used to track the file, not null
+     * @param deleteStrategy  the strategy to delete the file, null means normal
+     * @throws NullPointerException if the path is null
+     */
+    public void track(String path, Object marker, FileDeleteStrategy deleteStrategy) {
+        if (path == null) {
+            throw new NullPointerException("The path must not be null");
+        }
+        addTracker(path, marker, deleteStrategy);
+    }
+
+    /**
+     * Adds a tracker to the list of trackers.
+     * 
+     * @param path  the full path to the file to be tracked, not null
+     * @param marker  the marker object used to track the file, not null
+     * @param deleteStrategy  the strategy to delete the file, null means normal
+     */
+    private synchronized void addTracker(String path, Object marker, FileDeleteStrategy deleteStrategy) {
+        // synchronized block protects reaper
+        if (exitWhenFinished) {
+            throw new IllegalStateException("No new trackers can be added once exitWhenFinished() is called");
+        }
+        if (reaper == null) {
+            reaper = new Reaper();
+            reaper.start();
+        }
+        trackers.add(new Tracker(path, deleteStrategy, marker, q));
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Retrieve the number of files currently being tracked, and therefore
+     * awaiting deletion.
+     *
+     * @return the number of files being tracked
+     */
+    public int getTrackCount() {
+        return trackers.size();
+    }
+
+    /**
+     * Call this method to cause the file cleaner thread to terminate when
+     * there are no more objects being tracked for deletion.
+     * <p>
+     * In a simple environment, you don't need this method as the file cleaner
+     * thread will simply exit when the JVM exits. In a more complex environment,
+     * with multiple class loaders (such as an application server), you should be
+     * aware that the file cleaner thread will continue running even if the class
+     * loader it was started from terminates. This can consitute a memory leak.
+     * <p>
+     * For example, suppose that you have developed a web application, which
+     * contains the commons-io jar file in your WEB-INF/lib directory. In other
+     * words, the FileCleaner class is loaded through the class loader of your
+     * web application. If the web application is terminated, but the servlet
+     * container is still running, then the file cleaner thread will still exist,
+     * posing a memory leak.
+     * <p>
+     * This method allows the thread to be terminated. Simply call this method
+     * in the resource cleanup code, such as {@link javax.servlet.ServletContextListener#contextDestroyed}.
+     * One called, no new objects can be tracked by the file cleaner.
+     */
+    public synchronized void exitWhenFinished() {
+        // synchronized block protects reaper
+        exitWhenFinished = true;
+        if (reaper != null) {
+            synchronized (reaper) {
+                reaper.interrupt();
+            }
+        }
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * The reaper thread.
+     */
+    private final class Reaper extends Thread {
+        /** Construct a new Reaper */
+        Reaper() {
+            super("File Reaper");
+            setPriority(Thread.MAX_PRIORITY);
+            setDaemon(true);
+        }
+
+        /**
+         * Run the reaper thread that will delete files as their associated
+         * marker objects are reclaimed by the garbage collector.
+         */
+        @Override
+        public void run() {
+            // thread exits when exitWhenFinished is true and there are no more tracked objects
+            while (exitWhenFinished == false || trackers.size() > 0) {
+                Tracker tracker = null;
+                try {
+                    // Wait for a tracker to remove.
+                    tracker = (Tracker) q.remove();
+                } catch (Exception e) {
+                    continue;
+                }
+                if (tracker != null) {
+                    tracker.delete();
+                    tracker.clear();
+                    trackers.remove(tracker);
+                }
+            }
+        }
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Inner class which acts as the reference for a file pending deletion.
+     */
+    private static final class Tracker extends PhantomReference {
+
+        /**
+         * The full path to the file being tracked.
+         */
+        private final String path;
+        /**
+         * The strategy for deleting files.
+         */
+        private final FileDeleteStrategy deleteStrategy;
+
+        /**
+         * Constructs an instance of this class from the supplied parameters.
+         *
+         * @param path  the full path to the file to be tracked, not null
+         * @param deleteStrategy  the strategy to delete the file, null means normal
+         * @param marker  the marker object used to track the file, not null
+         * @param queue  the queue on to which the tracker will be pushed, not null
+         */
+        Tracker(String path, FileDeleteStrategy deleteStrategy, Object marker, ReferenceQueue queue) {
+            super(marker, queue);
+            this.path = path;
+            this.deleteStrategy = (deleteStrategy == null ? FileDeleteStrategy.NORMAL : deleteStrategy);
+        }
+
+        /**
+         * Deletes the file associated with this tracker instance.
+         *
+         * @return <code>true</code> if the file was deleted successfully;
+         *         <code>false</code> otherwise.
+         */
+        public boolean delete() {
+            return deleteStrategy.deleteQuietly(new File(path));
+        }
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileDeleteStrategy.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileDeleteStrategy.java
new file mode 100644
index 0000000..f992783
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileDeleteStrategy.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;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Strategy for deleting files.
+ * <p>
+ * There is more than one way to delete a file.
+ * You may want to limit access to certain directories, to only delete
+ * directories if they are empty, or maybe to force deletion.
+ * <p>
+ * This class captures the strategy to use and is designed for user subclassing.
+ *
+ * @author Stephen Colebourne
+ * @version $Id: FileDeleteStrategy.java,v 1.1 2011/06/28 21:08:18 rherrmann Exp $
+ * @since Commons IO 1.3
+ */
+public class FileDeleteStrategy {
+
+    /**
+     * The singleton instance for normal file deletion, which does not permit
+     * the deletion of directories that are not empty.
+     */
+    public static final FileDeleteStrategy NORMAL = new FileDeleteStrategy("Normal");
+    /**
+     * The singleton instance for forced file deletion, which always deletes,
+     * even if the file represents a non-empty directory.
+     */
+    public static final FileDeleteStrategy FORCE = new ForceFileDeleteStrategy();
+
+    /** The name of the strategy. */
+    private final String name;
+
+    //-----------------------------------------------------------------------
+    /**
+     * Restricted constructor.
+     *
+     * @param name  the name by which the strategy is known
+     */
+    protected FileDeleteStrategy(String name) {
+        this.name = name;
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Deletes the file object, which may be a file or a directory.
+     * All <code>IOException</code>s are caught and false returned instead.
+     * If the file does not exist or is null, true is returned.
+     * <p>
+     * Subclass writers should override {@link #doDelete(File)}, not this method.
+     *
+     * @param fileToDelete  the file to delete, null returns true
+     * @return true if the file was deleted, or there was no such file
+     */
+    public boolean deleteQuietly(File fileToDelete) {
+        if (fileToDelete == null || fileToDelete.exists() == false) {
+            return true;
+        }
+        try {
+            return doDelete(fileToDelete);
+        } catch (IOException ex) {
+            return false;
+        }
+    }
+
+    /**
+     * Deletes the file object, which may be a file or a directory.
+     * If the file does not exist, the method just returns.
+     * <p>
+     * Subclass writers should override {@link #doDelete(File)}, not this method.
+     *
+     * @param fileToDelete  the file to delete, not null
+     * @throws NullPointerException if the file is null
+     * @throws IOException if an error occurs during file deletion
+     */
+    public void delete(File fileToDelete) throws IOException {
+        if (fileToDelete.exists() && doDelete(fileToDelete) == false) {
+            throw new IOException("Deletion failed: " + fileToDelete);
+        }
+    }
+
+    /**
+     * Actually deletes the file object, which may be a file or a directory.
+     * <p>
+     * This method is designed for subclasses to override.
+     * The implementation may return either false or an <code>IOException</code>
+     * when deletion fails. The {@link #delete(File)} and {@link #deleteQuietly(File)}
+     * methods will handle either response appropriately.
+     * A check has been made to ensure that the file will exist.
+     * <p>
+     * This implementation uses {@link File#delete()}.
+     *
+     * @param fileToDelete  the file to delete, exists, not null
+     * @return true if the file was deleteds
+     * @throws NullPointerException if the file is null
+     * @throws IOException if an error occurs during file deletion
+     */
+    protected boolean doDelete(File fileToDelete) throws IOException {
+        return fileToDelete.delete();
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Gets a string describing the delete strategy.
+     *
+     * @return a string describing the delete strategy
+     */
+    @Override
+    public String toString() {
+        return "FileDeleteStrategy[" + name + "]";
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Force file deletion strategy.
+     */
+    static class ForceFileDeleteStrategy extends FileDeleteStrategy {
+        /** Default Constructor */
+        ForceFileDeleteStrategy() {
+            super("Force");
+        }
+
+        /**
+         * Deletes the file object.
+         * <p>
+         * This implementation uses <code>FileUtils.forceDelete() <code>
+         * if the file exists.
+         *
+         * @param fileToDelete  the file to delete, not null
+         * @return Always returns <code>true</code>
+         * @throws NullPointerException if the file is null
+         * @throws IOException if an error occurs during file deletion
+         */
+        @Override
+        protected boolean doDelete(File fileToDelete) throws IOException {
+            FileUtils.forceDelete(fileToDelete);
+            return true;
+        }
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileItem.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileItem.java
new file mode 100644
index 0000000..02ba371
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileItem.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.tomcat.util.http.fileupload;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * <p> This class represents a file or form item that was received within a
+ * <code>multipart/form-data</code> POST request.
+ *
+ * <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(javax.servlet.http.HttpServletRequest)}), you may
+ * either request all contents of the 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> While this interface does not extend
+ * <code>javax.activation.DataSource</code> per se (to avoid a seldom used
+ * dependency), several of the defined methods are specifically defined with
+ * the same signatures as methods in that interface. This allows an
+ * implementation of this interface to also implement
+ * <code>javax.activation.DataSource</code> with minimal additional work.
+ *
+ * @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:martinc@apache.org">Martin Cooper</a>
+ *
+ * @version $Id: FileItem.java,v 1.1 2011/06/28 21:08:18 rherrmann Exp $
+ */
+public interface FileItem extends Serializable {
+
+
+    // ------------------------------- 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.
+     */
+    InputStream getInputStream() throws IOException;
+
+
+    /**
+     * Returns the content type passed by the browser or <code>null</code> if
+     * not defined.
+     *
+     * @return The content type passed by the browser or <code>null</code> if
+     *         not defined.
+     */
+    String getContentType();
+
+
+    /**
+     * Returns the original filename in the client's filesystem, as provided by
+     * the browser (or other client software). In most cases, this will be the
+     * base file name, without path information. However, some clients, such as
+     * the Opera browser, do include path information.
+     *
+     * @return The original filename in the client's filesystem.
+     * @throws 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().
+     */
+    String getName();
+
+
+    // ------------------------------------------------------- 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.
+     */
+    boolean isInMemory();
+
+
+    /**
+     * Returns the size of the file item.
+     *
+     * @return The size of the file item, in bytes.
+     */
+    long getSize();
+
+
+    /**
+     * Returns the contents of the file item as an array of bytes.
+     *
+     * @return The contents of the file item as an array of bytes.
+     */
+    byte[] get();
+
+
+    /**
+     * Returns the contents of the file item as a String, using the specified
+     * encoding.  This method uses {@link #get()} to retrieve the
+     * contents of the item.
+     *
+     * @param encoding The character encoding to use.
+     *
+     * @return The contents of the item, as a string.
+     *
+     * @throws UnsupportedEncodingException if the requested character
+     *                                      encoding is not available.
+     */
+    String getString(String encoding) throws UnsupportedEncodingException;
+
+
+    /**
+     * Returns the contents of the file item as a String, using the default
+     * character encoding.  This method uses {@link #get()} to retrieve the
+     * contents of the item.
+     *
+     * @return The contents of the item, as a string.
+     */
+    String getString();
+
+
+    /**
+     * 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 method is not guaranteed to succeed if called more than once for
+     * the same item. This allows a particular implementation to use, for
+     * example, file renaming, where possible, rather than copying all of the
+     * underlying data, thus gaining a significant performance benefit.
+     *
+     * @param file The <code>File</code> into which the uploaded item should
+     *             be stored.
+     *
+     * @throws Exception if an error occurs.
+     */
+    void write(File file) throws Exception;
+
+
+    /**
+     * 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.
+     */
+    void delete();
+
+
+    /**
+     * Returns the name of the field in the multipart form corresponding to
+     * this file item.
+     *
+     * @return The name of the form field.
+     */
+    String getFieldName();
+
+
+    /**
+     * Sets the field name used to reference this file item.
+     *
+     * @param name The name of the form field.
+     */
+    void setFieldName(String name);
+
+
+    /**
+     * 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.
+     */
+    boolean 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.
+     */
+    void setFormField(boolean 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 contents of the file.
+     *
+     * @throws IOException if an error occurs.
+     */
+    OutputStream getOutputStream() throws IOException;
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileItemFactory.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileItemFactory.java
new file mode 100644
index 0000000..05d6ed3
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileItemFactory.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.http.fileupload;
+
+
+/**
+ * <p>A factory interface for creating {@link FileItem} instances. Factories
+ * can provide their own custom configuration, over and above that provided
+ * by the default file upload implementation.</p>
+ *
+ * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ *
+ * @version $Id: FileItemFactory.java,v 1.1 2011/06/28 21:08:18 rherrmann Exp $
+ */
+public interface FileItemFactory {
+
+    /**
+     * Create a new {@link FileItem} instance from the supplied parameters and
+     * any 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.
+     */
+    FileItem createItem(
+            String fieldName,
+            String contentType,
+            boolean isFormField,
+            String fileName
+            );
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileItemHeaders.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileItemHeaders.java
new file mode 100644
index 0000000..429b438
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileItemHeaders.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.tomcat.util.http.fileupload;
+
+import java.util.Iterator;
+
+/**
+ * <p> This class provides support for accessing the headers for a file or form
+ * item that was received within a <code>multipart/form-data</code> POST
+ * request.</p>
+ *
+ * @author Michael C. Macaluso
+ * @since 1.3
+ */
+public interface FileItemHeaders {
+    /**
+     * Returns the value of the specified part header as a <code>String</code>.
+     * If the part did not include a header of the specified name, this method
+     * return <code>null</code>.  If there are multiple headers with the same
+     * name, this method returns the first header in the item.  The header
+     * name is case insensitive.
+     *
+     * @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 item does not have a header
+     *         of that name
+     */
+    String getHeader(String name);
+
+    /**
+     * <p>
+     * Returns all the values of the specified item header as an
+     * <code>Enumeration</code> of <code>String</code> objects.
+     * </p>
+     * <p>
+     * If the item did not include any headers of the specified name, this
+     * method returns an empty <code>Enumeration</code>. The header name is
+     * case insensitive.
+     * </p>
+     *
+     * @param name a <code>String</code> specifying the header name
+     * @return an <code>Enumeration</code> containing the values of the
+     *         requested header. If the item does not have any headers of
+     *         that name, return an empty <code>Enumeration</code>
+     */
+    Iterator<String> getHeaders(String name);
+
+    /**
+     * <p>
+     * Returns an <code>Enumeration</code> of all the header names.
+     * </p>
+     * <p>
+     * If the item did not include any headers of the specified name, this
+     * method returns an empty <code>Enumeration</code>. The header name is
+     * case insensitive.
+     * </p>
+     *
+     * @return an <code>Enumeration</code> containing the values of the
+     *         requested header. If the item does not have any headers of
+     *         that name return an empty <code>Enumeration</code>
+     */
+    Iterator<String> getHeaderNames();
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileItemHeadersSupport.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileItemHeadersSupport.java
new file mode 100644
index 0000000..1cafdeb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileItemHeadersSupport.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http.fileupload;
+
+/**
+ * Interface that will indicate that {@link FileItem} or {@link FileItemStream}
+ * implementations will accept the headers read for the item.
+ *
+ * @author Michael C. Macaluso
+ * @since 1.3
+ *
+ * @see FileItem
+ * @see FileItemStream
+ */
+public interface FileItemHeadersSupport {
+    /**
+     * Returns the collection of headers defined locally within this item.
+     *
+     * @return the {@link FileItemHeaders} present for this item.
+     */
+    FileItemHeaders getHeaders();
+
+    /**
+     * Sets the headers read from within an item.  Implementations of
+     * {@link FileItem} or {@link FileItemStream} should implement this
+     * interface to be able to get the raw headers found within the item
+     * header block.
+     *
+     * @param headers the instance that holds onto the headers
+     *         for this instance.
+     */
+    void setHeaders(FileItemHeaders headers);
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileItemIterator.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileItemIterator.java
new file mode 100644
index 0000000..7279c75
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileItemIterator.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.http.fileupload;
+import java.io.IOException;
+
+
+/**
+ * An iterator, as returned by
+ * {@link FileUploadBase#getItemIterator(RequestContext)}.
+ */
+public interface FileItemIterator {
+    /**
+     * Returns, whether another instance of {@link FileItemStream}
+     * is available.
+     * @throws FileUploadException Parsing or processing the
+     *   file item failed.
+     * @throws IOException Reading the file item failed.
+     * @return True, if one or more additional file items
+     *   are available, otherwise false.
+     */
+    boolean hasNext() throws FileUploadException, IOException;
+
+    /**
+     * Returns the next available {@link FileItemStream}.
+     * @throws java.util.NoSuchElementException No more items are available. Use
+     * {@link #hasNext()} to prevent this exception.
+     * @throws FileUploadException Parsing or processing the
+     *   file item failed.
+     * @throws IOException Reading the file item failed.
+     * @return FileItemStream instance, which provides
+     *   access to the next file item.
+     */
+    FileItemStream next() throws FileUploadException, IOException;
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileItemStream.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileItemStream.java
new file mode 100644
index 0000000..92f9487
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileItemStream.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.http.fileupload;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * <p> This interface provides access to a file or form item that was
+ * received within a <code>multipart/form-data</code> POST request.
+ * The items contents are retrieved by calling {@link #openStream()}.</p>
+ * <p>Instances of this class are created by accessing the
+ * iterator, returned by
+ * {@link FileUploadBase#getItemIterator(RequestContext)}.</p>
+ * <p><em>Note</em>: There is an interaction between the iterator and
+ * its associated instances of {@link FileItemStream}: By invoking
+ * {@link java.util.Iterator#hasNext()} on the iterator, you discard all data,
+ * which hasn't been read so far from the previous data.</p>
+ */
+public interface FileItemStream extends FileItemHeadersSupport {
+    /**
+     * This exception is thrown, if an attempt is made to read
+     * data from the {@link InputStream}, which has been returned
+     * by {@link FileItemStream#openStream()}, after
+     * {@link java.util.Iterator#hasNext()} has been invoked on the
+     * iterator, which created the {@link FileItemStream}.
+     */
+    public static class ItemSkippedException extends IOException {
+        /**
+         * The exceptions serial version UID, which is being used
+         * when serializing an exception instance.
+         */
+        private static final long serialVersionUID = -7280778431581963740L;
+    }
+
+    /** Creates an {@link InputStream}, which allows to read the
+     * items contents.
+     * @return The input stream, from which the items data may
+     *   be read.
+     * @throws IllegalStateException The method was already invoked on
+     * this item. It is not possible to recreate the data stream.
+     * @throws IOException An I/O error occurred.
+     * @see ItemSkippedException
+     */
+    InputStream openStream() throws IOException;
+
+    /**
+     * Returns the content type passed by the browser or <code>null</code> if
+     * not defined.
+     *
+     * @return The content type passed by the browser or <code>null</code> if
+     *         not defined.
+     */
+    String getContentType();
+
+    /**
+     * Returns the original filename in the client's filesystem, as provided by
+     * the browser (or other client software). In most cases, this will be the
+     * base file name, without path information. However, some clients, such as
+     * the Opera browser, do include path information.
+     *
+     * @return The original filename in the client's filesystem.
+     */
+    String getName();
+
+    /**
+     * Returns the name of the field in the multipart form corresponding to
+     * this file item.
+     *
+     * @return The name of the form field.
+     */
+    String getFieldName();
+
+    /**
+     * 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.
+     */
+    boolean isFormField();
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileUpload.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileUpload.java
new file mode 100644
index 0000000..a25704d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileUpload.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.tomcat.util.http.fileupload;
+
+
+/**
+ * <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(RequestContext)} to acquire a list
+ * of {@link org.apache.tomcat.util.http.fileupload.FileItem FileItems} 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: FileUpload.java,v 1.1 2011/06/28 21:08:18 rherrmann Exp $
+ */
+public class FileUpload
+    extends FileUploadBase {
+
+    // ----------------------------------------------------------- Data members
+
+
+    /**
+     * The factory to use to create new form items.
+     */
+    private FileItemFactory fileItemFactory;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Constructs an uninitialised instance of this class. A factory must be
+     * configured, using <code>setFileItemFactory()</code>, before attempting
+     * to parse requests.
+     *
+     * @see #FileUpload(FileItemFactory)
+     */
+    public FileUpload() {
+        super();
+    }
+
+
+    /**
+     * Constructs an instance of this class which uses the supplied factory to
+     * create <code>FileItem</code> instances.
+     *
+     * @see #FileUpload()
+     * @param fileItemFactory The factory to use for creating file items.
+     */
+    public FileUpload(FileItemFactory fileItemFactory) {
+        super();
+        this.fileItemFactory = fileItemFactory;
+    }
+
+
+    // ----------------------------------------------------- Property accessors
+
+
+    /**
+     * Returns the factory class used when creating file items.
+     *
+     * @return The factory class for new file items.
+     */
+    @Override
+    public FileItemFactory getFileItemFactory() {
+        return fileItemFactory;
+    }
+
+
+    /**
+     * Sets the factory class to use when creating file items.
+     *
+     * @param factory The factory class for new file items.
+     */
+    @Override
+    public void setFileItemFactory(FileItemFactory factory) {
+        this.fileItemFactory = factory;
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileUploadBase.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileUploadBase.java
new file mode 100644
index 0000000..3b3a6a1
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileUploadBase.java
@@ -0,0 +1,1197 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import org.apache.tomcat.util.http.fileupload.MultipartStream.ItemInputStream;
+import org.apache.tomcat.util.http.fileupload.util.Closeable;
+import org.apache.tomcat.util.http.fileupload.util.FileItemHeadersImpl;
+import org.apache.tomcat.util.http.fileupload.util.LimitedInputStream;
+import org.apache.tomcat.util.http.fileupload.util.Streams;
+
+
+/**
+ * <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(RequestContext)} 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: FileUploadBase.java,v 1.1 2011/06/28 21:08:18 rherrmann Exp $
+ */
+public abstract class FileUploadBase {
+
+    // ---------------------------------------------------------- Class methods
+
+
+    /**
+     * <p>Utility method that determines whether the request contains multipart
+     * content.</p>
+     *
+     * <p><strong>NOTE:</strong>This method will be moved to the
+     * <code>ServletFileUpload</code> class after the FileUpload 1.1 release.
+     * Unfortunately, since this method is static, it is not possible to
+     * provide its replacement until this method is removed.</p>
+     *
+     * @param ctx The request context 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(RequestContext ctx) {
+        String contentType = ctx.getContentType();
+        if (contentType == null) {
+            return false;
+        }
+        if (contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART)) {
+            return true;
+        }
+        return false;
+    }
+
+
+    // ----------------------------------------------------- Manifest constants
+
+
+    /**
+     * HTTP content type header name.
+     */
+    public static final String CONTENT_TYPE = "Content-type";
+
+
+    /**
+     * HTTP content disposition header name.
+     */
+    public static final String CONTENT_DISPOSITION = "Content-disposition";
+
+    /**
+     * HTTP content length header name.
+     */
+    public static final String CONTENT_LENGTH = "Content-length";
+
+
+    /**
+     * Content-disposition value for form data.
+     */
+    public static final String FORM_DATA = "form-data";
+
+
+    /**
+     * Content-disposition value for file attachment.
+     */
+    public static final String ATTACHMENT = "attachment";
+
+
+    /**
+     * Part of HTTP content type header.
+     */
+    public static final String MULTIPART = "multipart/";
+
+
+    /**
+     * HTTP content type header for multipart forms.
+     */
+    public static final String MULTIPART_FORM_DATA = "multipart/form-data";
+
+
+    /**
+     * HTTP content type header for multiple uploads.
+     */
+    public static final String MULTIPART_MIXED = "multipart/mixed";
+
+
+    // ----------------------------------------------------------- Data members
+
+
+    /**
+     * The maximum size permitted for the complete request, as opposed to
+     * {@link #fileSizeMax}. A value of -1 indicates no maximum.
+     */
+    private long sizeMax = -1;
+
+    /**
+     * The maximum size permitted for a single uploaded file, as opposed
+     * to {@link #sizeMax}. A value of -1 indicates no maximum.
+     */
+    private long fileSizeMax = -1;
+
+    /**
+     * The content encoding to use when reading part headers.
+     */
+    private String headerEncoding;
+
+    /**
+     * The progress listener.
+     */
+    private ProgressListener listener;
+
+    // ----------------------------------------------------- Property accessors
+
+
+    /**
+     * Returns the factory class used when creating file items.
+     *
+     * @return The factory class for new file items.
+     */
+    public abstract FileItemFactory getFileItemFactory();
+
+
+    /**
+     * Sets the factory class to use when creating file items.
+     *
+     * @param factory The factory class for new file items.
+     */
+    public abstract void setFileItemFactory(FileItemFactory factory);
+
+
+    /**
+     * Returns the maximum allowed size of a complete request, as opposed
+     * to {@link #getFileSizeMax()}.
+     *
+     * @return The maximum allowed size, in bytes. The default value of
+     *   -1 indicates, that there is no limit.
+     *
+     * @see #setSizeMax(long)
+     *
+     */
+    public long getSizeMax() {
+        return sizeMax;
+    }
+
+
+    /**
+     * Sets the maximum allowed size of a complete request, as opposed
+     * to {@link #setFileSizeMax(long)}.
+     *
+     * @param sizeMax The maximum allowed size, in bytes. The default value of
+     *   -1 indicates, that there is no limit.
+     *
+     * @see #getSizeMax()
+     *
+     */
+    public void setSizeMax(long sizeMax) {
+        this.sizeMax = sizeMax;
+    }
+
+    /**
+     * Returns the maximum allowed size of a single uploaded file,
+     * as opposed to {@link #getSizeMax()}.
+     *
+     * @see #setFileSizeMax(long)
+     * @return Maximum size of a single uploaded file.
+     */
+    public long getFileSizeMax() {
+        return fileSizeMax;
+    }
+
+    /**
+     * Sets the maximum allowed size of a single uploaded file,
+     * as opposed to {@link #getSizeMax()}.
+     *
+     * @see #getFileSizeMax()
+     * @param fileSizeMax Maximum size of a single uploaded file.
+     */
+    public void setFileSizeMax(long fileSizeMax) {
+        this.fileSizeMax = fileSizeMax;
+    }
+
+    /**
+     * Retrieves the character encoding used when reading the headers of an
+     * individual part. When not specified, or <code>null</code>, the request
+     * encoding is used. If that is also not specified, or <code>null</code>,
+     * the platform default encoding is used.
+     *
+     * @return The encoding used to read part headers.
+     */
+    public String getHeaderEncoding() {
+        return headerEncoding;
+    }
+
+
+    /**
+     * Specifies the character encoding to be used when reading the headers of
+     * individual part. When not specified, or <code>null</code>, the request
+     * encoding is used. If that is also not specified, or <code>null</code>,
+     * the platform default encoding is used.
+     *
+     * @param encoding The encoding used to read part headers.
+     */
+    public void setHeaderEncoding(String encoding) {
+        headerEncoding = encoding;
+    }
+
+
+    // --------------------------------------------------------- Public methods
+
+
+    /**
+     * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
+     * compliant <code>multipart/form-data</code> stream.
+     *
+     * @param ctx The context for the 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(RequestContext ctx)
+    throws FileUploadException, IOException {
+        return new FileItemIteratorImpl(ctx);
+    }
+
+    /**
+     * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
+     * compliant <code>multipart/form-data</code> stream.
+     *
+     * @param ctx The context for the 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(RequestContext ctx)
+            throws FileUploadException {
+        List<FileItem> items = new ArrayList<FileItem>();
+        boolean successful = false;
+        try {
+            FileItemIterator iter = getItemIterator(ctx);
+            FileItemFactory fac = getFileItemFactory();
+            if (fac == null) {
+                throw new NullPointerException(
+                    "No FileItemFactory has been set.");
+            }
+            while (iter.hasNext()) {
+                final FileItemStream item = iter.next();
+                // Don't use getName() here to prevent an InvalidFileNameException.
+                final String fileName = ((org.apache.tomcat.util.http.fileupload.FileUploadBase.FileItemIteratorImpl.FileItemStreamImpl) item).name;
+                FileItem fileItem = fac.createItem(item.getFieldName(),
+                        item.getContentType(), item.isFormField(),
+                        fileName);
+                items.add(fileItem);
+                try {
+                    Streams.copy(item.openStream(), fileItem.getOutputStream(),
+                            true);
+                } catch (FileUploadIOException e) {
+                    throw (FileUploadException) e.getCause();
+                } catch (IOException e) {
+                    throw new IOFileUploadException(
+                            "Processing of " + MULTIPART_FORM_DATA
+                            + " request failed. " + e.getMessage(), e);
+                }
+                if (fileItem instanceof FileItemHeadersSupport) {
+                    final FileItemHeaders fih = item.getHeaders();
+                    ((FileItemHeadersSupport) fileItem).setHeaders(fih);
+                }
+            }
+            successful = true;
+            return items;
+        } catch (FileUploadIOException e) {
+            throw (FileUploadException) e.getCause();
+        } catch (IOException e) {
+            throw new FileUploadException(e.getMessage(), e);
+        } finally {
+            if (!successful) {
+                for (Iterator<FileItem> iterator = items.iterator(); iterator.hasNext();) {
+                    FileItem fileItem = iterator.next();
+                    try {
+                        fileItem.delete();
+                    } catch (Exception e) {
+                        // ignore it
+                    }
+                }
+            }
+        }
+    }
+
+
+    // ------------------------------------------------------ Protected methods
+
+
+    /**
+     * Retrieves the boundary from the <code>Content-type</code> header.
+     *
+     * @param contentType The value of the content type header from which to
+     *                    extract the boundary value.
+     *
+     * @return The boundary, as a byte array.
+     */
+    protected byte[] getBoundary(String contentType) {
+        ParameterParser parser = new ParameterParser();
+        parser.setLowerCaseNames(true);
+        // Parameter parser can handle null input
+        Map<String,String> params =
+            parser.parse(contentType, new char[] {';', ','});
+        String boundaryStr = params.get("boundary");
+
+        if (boundaryStr == null) {
+            return null;
+        }
+        byte[] boundary;
+        try {
+            boundary = boundaryStr.getBytes("ISO-8859-1");
+        } catch (UnsupportedEncodingException e) {
+            boundary = boundaryStr.getBytes();
+        }
+        return boundary;
+    }
+
+
+    /**
+     * Retrieves the file name from the <code>Content-disposition</code>
+     * header.
+     *
+     * @param headers The HTTP headers object.
+     *
+     * @return The file name for the current <code>encapsulation</code>.
+     */
+    protected String getFileName(FileItemHeaders headers) {
+        return getFileName(headers.getHeader(CONTENT_DISPOSITION));
+    }
+
+    /**
+     * Returns the given content-disposition headers file name.
+     * @param pContentDisposition The content-disposition headers value.
+     * @return The file name
+     */
+    private String getFileName(String pContentDisposition) {
+        String fileName = null;
+        if (pContentDisposition != null) {
+            String cdl = pContentDisposition.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(pContentDisposition, ';');
+                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;
+    }
+
+
+    /**
+     * Retrieves the field name from the <code>Content-disposition</code>
+     * header.
+     *
+     * @param headers A <code>Map</code> containing the HTTP request headers.
+     *
+     * @return The field name for the current <code>encapsulation</code>.
+     */
+    protected String getFieldName(FileItemHeaders headers) {
+        return getFieldName(headers.getHeader(CONTENT_DISPOSITION));
+    }
+
+    /**
+     * Returns the field name, which is given by the content-disposition
+     * header.
+     * @param pContentDisposition The content-dispositions header value.
+     * @return The field jake
+     */
+    private String getFieldName(String pContentDisposition) {
+        String fieldName = null;
+        if (pContentDisposition != null
+                && pContentDisposition.toLowerCase(Locale.ENGLISH).startsWith(FORM_DATA)) {
+            ParameterParser parser = new ParameterParser();
+            parser.setLowerCaseNames(true);
+            // Parameter parser can handle null input
+            Map<String,String> params =
+                parser.parse(pContentDisposition, ';');
+            fieldName = params.get("name");
+            if (fieldName != null) {
+                fieldName = fieldName.trim();
+            }
+        }
+        return fieldName;
+    }
+
+    /**
+     * <p> Parses the <code>header-part</code> and returns as key/value
+     * pairs.
+     *
+     * <p> If there are multiple headers of the same names, the name
+     * will map to a comma-separated list containing the values.
+     *
+     * @param headerPart The <code>header-part</code> of the current
+     *                   <code>encapsulation</code>.
+     *
+     * @return A <code>Map</code> containing the parsed HTTP request headers.
+     */
+    protected FileItemHeaders getParsedHeaders(String headerPart) {
+        final int len = headerPart.length();
+        FileItemHeadersImpl headers = newFileItemHeaders();
+        int start = 0;
+        for (;;) {
+            int end = parseEndOfLine(headerPart, start);
+            if (start == end) {
+                break;
+            }
+            String header = headerPart.substring(start, end);
+            start = end + 2;
+            while (start < len) {
+                int nonWs = start;
+                while (nonWs < len) {
+                    char c = headerPart.charAt(nonWs);
+                    if (c != ' '  &&  c != '\t') {
+                        break;
+                    }
+                    ++nonWs;
+                }
+                if (nonWs == start) {
+                    break;
+                }
+                // Continuation line found
+                end = parseEndOfLine(headerPart, nonWs);
+                header += " " + headerPart.substring(nonWs, end);
+                start = end + 2;
+            }
+            parseHeaderLine(headers, header);
+        }
+        return headers;
+    }
+
+    /**
+     * Creates a new instance of {@link FileItemHeaders}.
+     * @return The new instance.
+     */
+    protected FileItemHeadersImpl newFileItemHeaders() {
+        return new FileItemHeadersImpl();
+    }
+
+    /**
+     * Skips bytes until the end of the current line.
+     * @param headerPart The headers, which are being parsed.
+     * @param end Index of the last byte, which has yet been
+     *   processed.
+     * @return Index of the \r\n sequence, which indicates
+     *   end of line.
+     */
+    private int parseEndOfLine(String headerPart, int end) {
+        int index = end;
+        for (;;) {
+            int offset = headerPart.indexOf('\r', index);
+            if (offset == -1  ||  offset + 1 >= headerPart.length()) {
+                throw new IllegalStateException(
+                    "Expected headers to be terminated by an empty line.");
+            }
+            if (headerPart.charAt(offset + 1) == '\n') {
+                return offset;
+            }
+            index = offset + 1;
+        }
+    }
+
+    /**
+     * Reads the next header line.
+     * @param headers String with all headers.
+     * @param header Map where to store the current header.
+     */
+    private void parseHeaderLine(FileItemHeadersImpl headers, String header) {
+        final int colonOffset = header.indexOf(':');
+        if (colonOffset == -1) {
+            // This header line is malformed, skip it.
+            return;
+        }
+        String headerName = header.substring(0, colonOffset).trim();
+        String headerValue =
+            header.substring(header.indexOf(':') + 1).trim();
+        headers.addHeader(headerName, headerValue);
+    }
+
+    /**
+     * The iterator, which is returned by
+     * {@link FileUploadBase#getItemIterator(RequestContext)}.
+     */
+    private class FileItemIteratorImpl implements FileItemIterator {
+        /**
+         * Default implementation of {@link FileItemStream}.
+         */
+        class FileItemStreamImpl implements FileItemStream {
+            /** The file items content type.
+             */
+            private final String contentType;
+            /** The file items field name.
+             */
+            private final String fieldName;
+            /** The file items file name.
+             */
+            private final String name;
+            /** Whether the file item is a form field.
+             */
+            private final boolean formField;
+            /** The file items input stream.
+             */
+            private final InputStream stream;
+            /** Whether the file item was already opened.
+             */
+            private boolean opened;
+            /** The headers, if any.
+             */
+            private FileItemHeaders headers;
+
+            /**
+             * Creates a new instance.
+             * @param pName The items file name, or null.
+             * @param pFieldName The items field name.
+             * @param pContentType The items content type, or null.
+             * @param pFormField Whether the item is a form field.
+             * @param pContentLength The items content length, if known, or -1
+             * @throws IOException Creating the file item failed.
+             */
+            FileItemStreamImpl(String pName, String pFieldName,
+                    String pContentType, boolean pFormField,
+                    long pContentLength) throws IOException {
+                name = pName;
+                fieldName = pFieldName;
+                contentType = pContentType;
+                formField = pFormField;
+                final ItemInputStream itemStream = multi.newInputStream();
+                InputStream istream = itemStream;
+                if (fileSizeMax != -1) {
+                    if (pContentLength != -1
+                            &&  pContentLength > fileSizeMax) {
+                        FileSizeLimitExceededException e =
+                            new FileSizeLimitExceededException(
+                                "The field " + fieldName
+                                + " exceeds its maximum permitted "
+                                + " size of " + fileSizeMax
+                                + " bytes.",
+                                pContentLength, fileSizeMax);
+                        e.setFileName(pName);
+                        e.setFieldName(pFieldName);
+                        throw new FileUploadIOException(e);
+                    }
+                    istream = new LimitedInputStream(istream, fileSizeMax) {
+                        @Override
+                        protected void raiseError(long pSizeMax, long pCount)
+                                throws IOException {
+                            itemStream.close(true);
+                            FileSizeLimitExceededException e =
+                                new FileSizeLimitExceededException(
+                                    "The field " + fieldName
+                                    + " exceeds its maximum permitted "
+                                    + " size of " + pSizeMax
+                                    + " bytes.",
+                                    pCount, pSizeMax);
+                            e.setFieldName(fieldName);
+                            e.setFileName(name);
+                            throw new FileUploadIOException(e);
+                        }
+                    };
+                }
+                stream = istream;
+            }
+
+            /**
+             * Returns the items content type, or null.
+             * @return Content type, if known, or null.
+             */
+            public String getContentType() {
+                return contentType;
+            }
+
+            /**
+             * Returns the items field name.
+             * @return Field name.
+             */
+            public String getFieldName() {
+                return fieldName;
+            }
+
+            /**
+             * Returns the items file name.
+             * @return File name, if known, or null.
+             * @throws 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(name);
+            }
+
+            /**
+             * Returns, whether this is a form field.
+             * @return True, if the item is a form field,
+             *   otherwise false.
+             */
+            public boolean isFormField() {
+                return formField;
+            }
+
+            /**
+             * Returns an input stream, which may be used to
+             * read the items contents.
+             * @return Opened input stream.
+             * @throws IOException An I/O error occurred.
+             */
+            public InputStream openStream() throws IOException {
+                if (opened) {
+                    throw new IllegalStateException(
+                            "The stream was already opened.");
+                }
+                if (((Closeable) stream).isClosed()) {
+                    throw new FileItemStream.ItemSkippedException();
+                }
+                return stream;
+            }
+
+            /**
+             * Closes the file item.
+             * @throws IOException An I/O error occurred.
+             */
+            void close() throws IOException {
+                stream.close();
+            }
+
+            /**
+             * Returns the file item headers.
+             * @return The items header object
+             */
+            public FileItemHeaders getHeaders() {
+                return headers;
+            }
+
+            /**
+             * Sets the file item headers.
+             * @param pHeaders The items header object
+             */
+            public void setHeaders(FileItemHeaders pHeaders) {
+                headers = pHeaders;
+            }
+        }
+
+        /**
+         * The multi part stream to process.
+         */
+        private final MultipartStream multi;
+        /**
+         * The notifier, which used for triggering the
+         * {@link ProgressListener}.
+         */
+        private final MultipartStream.ProgressNotifier notifier;
+        /**
+         * The boundary, which separates the various parts.
+         */
+        private final byte[] boundary;
+        /**
+         * The item, which we currently process.
+         */
+        private FileItemStreamImpl currentItem;
+        /**
+         * The current items field name.
+         */
+        private String currentFieldName;
+        /**
+         * Whether we are currently skipping the preamble.
+         */
+        private boolean skipPreamble;
+        /**
+         * Whether the current item may still be read.
+         */
+        private boolean itemValid;
+        /**
+         * Whether we have seen the end of the file.
+         */
+        private boolean eof;
+
+        /**
+         * Creates a new instance.
+         * @param ctx The request context.
+         * @throws FileUploadException An error occurred while
+         *   parsing the request.
+         * @throws IOException An I/O error occurred.
+         */
+        FileItemIteratorImpl(RequestContext ctx)
+                throws FileUploadException, IOException {
+            if (ctx == null) {
+                throw new NullPointerException("ctx parameter");
+            }
+
+            String contentType = ctx.getContentType();
+            if ((null == contentType)
+                    || (!contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART))) {
+                throw new InvalidContentTypeException(
+                        "the request doesn't contain a "
+                        + MULTIPART_FORM_DATA
+                        + " or "
+                        + MULTIPART_MIXED
+                        + " stream, content type header is "
+                        + contentType);
+            }
+
+            InputStream input = ctx.getInputStream();
+
+            if (sizeMax >= 0) {
+                int requestSize = ctx.getContentLength();
+                if (requestSize == -1) {
+                    input = new LimitedInputStream(input, sizeMax) {
+                        @Override
+                        protected void raiseError(long pSizeMax, long pCount)
+                                throws IOException {
+                            FileUploadException ex =
+                                new SizeLimitExceededException(
+                                    "the request was rejected because"
+                                    + " its size (" + pCount
+                                    + ") exceeds the configured maximum"
+                                    + " (" + pSizeMax + ")",
+                                    pCount, pSizeMax);
+                            throw new FileUploadIOException(ex);
+                        }
+                    };
+                } else {
+                    if (sizeMax >= 0 && requestSize > sizeMax) {
+                        throw new SizeLimitExceededException(
+                                "the request was rejected because its size ("
+                                + requestSize
+                                + ") exceeds the configured maximum ("
+                                + sizeMax + ")",
+                                requestSize, sizeMax);
+                    }
+                }
+            }
+
+            String charEncoding = headerEncoding;
+            if (charEncoding == null) {
+                charEncoding = ctx.getCharacterEncoding();
+            }
+
+            boundary = getBoundary(contentType);
+            if (boundary == null) {
+                throw new FileUploadException(
+                        "the request was rejected because "
+                        + "no multipart boundary was found");
+            }
+
+            notifier = new MultipartStream.ProgressNotifier(listener,
+                    ctx.getContentLength());
+            multi = new MultipartStream(input, boundary, notifier);
+            multi.setHeaderEncoding(charEncoding);
+
+            skipPreamble = true;
+            findNextItem();
+        }
+
+        /**
+         * Called for finding the nex item, if any.
+         * @return True, if an next item was found, otherwise false.
+         * @throws IOException An I/O error occurred.
+         */
+        private boolean findNextItem() throws IOException {
+            if (eof) {
+                return false;
+            }
+            if (currentItem != null) {
+                currentItem.close();
+                currentItem = null;
+            }
+            for (;;) {
+                boolean nextPart;
+                if (skipPreamble) {
+                    nextPart = multi.skipPreamble();
+                } else {
+                    nextPart = multi.readBoundary();
+                }
+                if (!nextPart) {
+                    if (currentFieldName == null) {
+                        // Outer multipart terminated -> No more data
+                        eof = true;
+                        return false;
+                    }
+                    // Inner multipart terminated -> Return to parsing the outer
+                    multi.setBoundary(boundary);
+                    currentFieldName = null;
+                    continue;
+                }
+                FileItemHeaders headers = getParsedHeaders(multi.readHeaders());
+                if (currentFieldName == null) {
+                    // We're parsing the outer multipart
+                    String fieldName = getFieldName(headers);
+                    if (fieldName != null) {
+                        String subContentType = headers.getHeader(CONTENT_TYPE);
+                        if (subContentType != null
+                                &&  subContentType.toLowerCase(Locale.ENGLISH)
+                                        .startsWith(MULTIPART_MIXED)) {
+                            currentFieldName = fieldName;
+                            // Multiple files associated with this field name
+                            byte[] subBoundary = getBoundary(subContentType);
+                            multi.setBoundary(subBoundary);
+                            skipPreamble = true;
+                            continue;
+                        }
+                        String fileName = getFileName(headers);
+                        currentItem = new FileItemStreamImpl(fileName,
+                                fieldName, headers.getHeader(CONTENT_TYPE),
+                                fileName == null, getContentLength(headers));
+                        currentItem.setHeaders(headers);
+                        notifier.noteItem();
+                        itemValid = true;
+                        return true;
+                    }
+                } else {
+                    String fileName = getFileName(headers);
+                    if (fileName != null) {
+                        currentItem = new FileItemStreamImpl(fileName,
+                                currentFieldName,
+                                headers.getHeader(CONTENT_TYPE),
+                                false, getContentLength(headers));
+                        currentItem.setHeaders(headers);
+                        notifier.noteItem();
+                        itemValid = true;
+                        return true;
+                    }
+                }
+                multi.discardBodyData();
+            }
+        }
+
+        private long getContentLength(FileItemHeaders pHeaders) {
+            try {
+                return Long.parseLong(pHeaders.getHeader(CONTENT_LENGTH));
+            } catch (Exception e) {
+                return -1;
+            }
+        }
+
+        /**
+         * Returns, whether another instance of {@link FileItemStream}
+         * is available.
+         * @throws FileUploadException Parsing or processing the
+         *   file item failed.
+         * @throws IOException Reading the file item failed.
+         * @return True, if one or more additional file items
+         *   are available, otherwise false.
+         */
+        public boolean hasNext() throws FileUploadException, IOException {
+            if (eof) {
+                return false;
+            }
+            if (itemValid) {
+                return true;
+            }
+            return findNextItem();
+        }
+
+        /**
+         * Returns the next available {@link FileItemStream}.
+         * @throws java.util.NoSuchElementException No more items are
+         *   available. Use {@link #hasNext()} to prevent this exception.
+         * @throws FileUploadException Parsing or processing the
+         *   file item failed.
+         * @throws IOException Reading the file item failed.
+         * @return FileItemStream instance, which provides
+         *   access to the next file item.
+         */
+        public FileItemStream next() throws FileUploadException, IOException {
+            if (eof  ||  (!itemValid && !hasNext())) {
+                throw new NoSuchElementException();
+            }
+            itemValid = false;
+            return currentItem;
+        }
+    }
+
+    /**
+     * This exception is thrown for hiding an inner
+     * {@link FileUploadException} in an {@link IOException}.
+     */
+    public static class FileUploadIOException extends IOException {
+        /** The exceptions UID, for serializing an instance.
+         */
+        private static final long serialVersionUID = -7047616958165584154L;
+        /** The exceptions cause; we overwrite the parent
+         * classes field, which is available since Java
+         * 1.4 only.
+         */
+        private final FileUploadException cause;
+
+        /**
+         * Creates a <code>FileUploadIOException</code> with the
+         * given cause.
+         * @param pCause The exceptions cause, if any, or null.
+         */
+        public FileUploadIOException(FileUploadException pCause) {
+            // We're not doing super(pCause) cause of 1.3 compatibility.
+            cause = pCause;
+        }
+
+        /**
+         * Returns the exceptions cause.
+         * @return The exceptions cause, if any, or null.
+         */
+        @Override
+        public Throwable getCause() {
+            return cause;
+        }
+    }
+
+    /**
+     * Thrown to indicate that the request is not a multipart request.
+     */
+    public static class InvalidContentTypeException
+            extends FileUploadException {
+        /** The exceptions UID, for serializing an instance.
+         */
+        private static final long serialVersionUID = -9073026332015646668L;
+
+        /**
+         * Constructs a <code>InvalidContentTypeException</code> with no
+         * detail message.
+         */
+        public InvalidContentTypeException() {
+            // Nothing to do.
+        }
+
+        /**
+         * Constructs an <code>InvalidContentTypeException</code> with
+         * the specified detail message.
+         *
+         * @param message The detail message.
+         */
+        public InvalidContentTypeException(String message) {
+            super(message);
+        }
+    }
+
+    /**
+     * Thrown to indicate an IOException.
+     */
+    public static class IOFileUploadException extends FileUploadException {
+        /** The exceptions UID, for serializing an instance.
+         */
+        private static final long serialVersionUID = 1749796615868477269L;
+        /** The exceptions cause; we overwrite the parent
+         * classes field, which is available since Java
+         * 1.4 only.
+         */
+        private final IOException cause;
+
+        /**
+         * Creates a new instance with the given cause.
+         * @param pMsg The detail message.
+         * @param pException The exceptions cause.
+         */
+        public IOFileUploadException(String pMsg, IOException pException) {
+            super(pMsg);
+            cause = pException;
+        }
+
+        /**
+         * Returns the exceptions cause.
+         * @return The exceptions cause, if any, or null.
+         */
+        @Override
+        public Throwable getCause() {
+            return cause;
+        }
+    }
+
+    /** This exception is thrown, if a requests permitted size
+     * is exceeded.
+     */
+    public abstract static class SizeException extends FileUploadException {
+
+        private static final long serialVersionUID = -8776225574705254126L;
+
+        /**
+         * The actual size of the request.
+         */
+        private final long actual;
+
+        /**
+         * The maximum permitted size of the request.
+         */
+        private final long permitted;
+
+        /**
+         * Creates a new instance.
+         * @param message The detail message.
+         * @param actual The actual number of bytes in the request.
+         * @param permitted The requests size limit, in bytes.
+         */
+        protected SizeException(String message, long actual, long permitted) {
+            super(message);
+            this.actual = actual;
+            this.permitted = permitted;
+        }
+
+        /**
+         * Retrieves the actual size of the request.
+         *
+         * @return The actual size of the request.
+         */
+        public long getActualSize() {
+            return actual;
+        }
+
+        /**
+         * Retrieves the permitted size of the request.
+         *
+         * @return The permitted size of the request.
+         */
+        public long getPermittedSize() {
+            return permitted;
+        }
+    }
+
+    /**
+     * Thrown to indicate that the request size exceeds the configured maximum.
+     */
+    public static class SizeLimitExceededException
+            extends SizeException {
+        /** The exceptions UID, for serializing an instance.
+         */
+        private static final long serialVersionUID = -2474893167098052828L;
+
+        /**
+         * Constructs a <code>SizeExceededException</code> with
+         * the specified detail message, and actual and permitted sizes.
+         *
+         * @param message   The detail message.
+         * @param actual    The actual request size.
+         * @param permitted The maximum permitted request size.
+         */
+        public SizeLimitExceededException(String message, long actual,
+                long permitted) {
+            super(message, actual, permitted);
+        }
+    }
+
+    /**
+     * Thrown to indicate that A files size exceeds the configured maximum.
+     */
+    public static class FileSizeLimitExceededException
+            extends SizeException {
+        /** The exceptions UID, for serializing an instance.
+         */
+        private static final long serialVersionUID = 8150776562029630058L;
+
+        /**
+         * File name of the item, which caused the exception.
+         */
+        private String fileName;
+
+        /**
+         * Field name of the item, which caused the exception.
+         */
+        private String fieldName;
+
+        /**
+         * Constructs a <code>SizeExceededException</code> with
+         * the specified detail message, and actual and permitted sizes.
+         *
+         * @param message   The detail message.
+         * @param actual    The actual request size.
+         * @param permitted The maximum permitted request size.
+         */
+        public FileSizeLimitExceededException(String message, long actual,
+                long permitted) {
+            super(message, actual, permitted);
+        }
+
+        /**
+         * Returns the file name of the item, which caused the
+         * exception.
+         * @return File name, if known, or null.
+         */
+        public String getFileName() {
+            return fileName;
+        }
+
+        /**
+         * Sets the file name of the item, which caused the
+         * exception.
+         */
+        public void setFileName(String pFileName) {
+            fileName = pFileName;
+        }
+
+        /**
+         * Returns the field name of the item, which caused the
+         * exception.
+         * @return Field name, if known, or null.
+         */
+        public String getFieldName() {
+            return fieldName;
+        }
+
+        /**
+         * Sets the field name of the item, which caused the
+         * exception.
+         */
+        public void setFieldName(String pFieldName) {
+            fieldName = pFieldName;
+        }
+    }
+
+    /**
+     * Returns the progress listener.
+     * @return The progress listener, if any, or null.
+     */
+    public ProgressListener getProgressListener() {
+        return listener;
+    }
+
+    /**
+     * Sets the progress listener.
+     * @param pListener The progress listener, if any. Defaults to null.
+     */
+    public void setProgressListener(ProgressListener pListener) {
+        listener = pListener;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileUploadException.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileUploadException.java
new file mode 100644
index 0000000..76182ca
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileUploadException.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.http.fileupload;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+
+
+/**
+ * Exception for errors encountered while processing the request.
+ *
+ * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
+ * @version $Id: FileUploadException.java,v 1.1 2011/06/28 21:08:18 rherrmann Exp $
+ */
+public class FileUploadException extends Exception {
+    /**
+     * Serial version UID, being used, if the exception
+     * is serialized.
+     */
+    private static final long serialVersionUID = 8881893724388807504L;
+    /**
+     * The exceptions cause. We overwrite the cause of
+     * the super class, which isn't available in Java 1.3.
+     */
+    private final Throwable cause;
+
+    /**
+     * Constructs a new <code>FileUploadException</code> without message.
+     */
+    public FileUploadException() {
+        this(null, null);
+    }
+
+    /**
+     * Constructs a new <code>FileUploadException</code> with specified detail
+     * message.
+     *
+     * @param msg the error message.
+     */
+    public FileUploadException(final String msg) {
+        this(msg, null);
+    }
+
+    /**
+     * Creates a new <code>FileUploadException</code> with the given
+     * detail message and cause.
+     * @param msg The exceptions detail message.
+     * @param cause The exceptions cause.
+     */
+    public FileUploadException(String msg, Throwable cause) {
+        super(msg);
+        this.cause = cause;
+    }
+
+    /**
+     * Prints this throwable and its backtrace to the specified print stream.
+     *
+     * @param stream <code>PrintStream</code> to use for output
+     */
+    @Override
+    public void printStackTrace(PrintStream stream) {
+        super.printStackTrace(stream);
+        if (cause != null) {
+            stream.println("Caused by:");
+            cause.printStackTrace(stream);
+        }
+    }
+
+    /**
+     * Prints this throwable and its backtrace to the specified
+     * print writer.
+     *
+     * @param writer <code>PrintWriter</code> to use for output
+     */
+    @Override
+    public void printStackTrace(PrintWriter writer) {
+        super.printStackTrace(writer);
+        if (cause != null) {
+            writer.println("Caused by:");
+            cause.printStackTrace(writer);
+        }
+    }
+
+    @Override
+    public Throwable getCause() {
+        return cause;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileUtils.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileUtils.java
new file mode 100644
index 0000000..e5edf37
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/FileUtils.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.tomcat.util.http.fileupload;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+
+/**
+ * General file manipulation utilities.
+ * <p>
+ * Facilities are provided in the following areas:
+ * <ul>
+ * <li>writing to a file
+ * <li>reading from a file
+ * <li>make a directory including parent directories
+ * <li>copying files and directories
+ * <li>deleting files and directories
+ * <li>converting to and from a URL
+ * <li>listing files and directories by filter and extension
+ * <li>comparing file content
+ * <li>file last changed date
+ * <li>calculating a checksum
+ * </ul>
+ * <p>
+ * Origin of code: Excalibur, Alexandria, Commons-Utils
+ *
+ * @author <a href="mailto:burton@relativity.yi.org">Kevin A. Burton</A>
+ * @author <a href="mailto:sanders@apache.org">Scott Sanders</a>
+ * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
+ * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph.Reck</a>
+ * @author <a href="mailto:peter@apache.org">Peter Donald</a>
+ * @author <a href="mailto:jefft@apache.org">Jeff Turner</a>
+ * @author Matthew Hawthorne
+ * @author <a href="mailto:jeremias@apache.org">Jeremias Maerki</a>
+ * @author Stephen Colebourne
+ * @author Ian Springer
+ * @author Chris Eldredge
+ * @author Jim Harrington
+ * @author Niall Pemberton
+ * @author Sandy McArthur
+ * @version $Id: FileUtils.java,v 1.1 2011/06/28 21:08:18 rherrmann Exp $
+ */
+public class FileUtils {
+
+    /**
+     * Instances should NOT be constructed in standard programming.
+     */
+    public FileUtils() {
+        super();
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Deletes a directory recursively. 
+     *
+     * @param directory  directory to delete
+     * @throws IOException in case deletion is unsuccessful
+     */
+    public static void deleteDirectory(File directory) throws IOException {
+        if (!directory.exists()) {
+            return;
+        }
+
+        cleanDirectory(directory);
+        if (!directory.delete()) {
+            String message =
+                "Unable to delete directory " + directory + ".";
+            throw new IOException(message);
+        }
+    }
+
+
+    /**
+     * Cleans a directory without deleting it.
+     *
+     * @param directory directory to clean
+     * @throws IOException in case cleaning is unsuccessful
+     */
+    public static void cleanDirectory(File directory) throws IOException {
+        if (!directory.exists()) {
+            String message = directory + " does not exist";
+            throw new IllegalArgumentException(message);
+        }
+
+        if (!directory.isDirectory()) {
+            String message = directory + " is not a directory";
+            throw new IllegalArgumentException(message);
+        }
+
+        File[] files = directory.listFiles();
+        if (files == null) {  // null if security restricted
+            throw new IOException("Failed to list contents of " + directory);
+        }
+
+        IOException exception = null;
+        for (int i = 0; i < files.length; i++) {
+            File file = files[i];
+            try {
+                forceDelete(file);
+            } catch (IOException ioe) {
+                exception = ioe;
+            }
+        }
+
+        if (null != exception) {
+            throw exception;
+        }
+    }
+    
+    
+    //-----------------------------------------------------------------------
+    /**
+     * Deletes a file. If file is a directory, delete it and all sub-directories.
+     * <p>
+     * The difference between File.delete() and this method are:
+     * <ul>
+     * <li>A directory to be deleted does not have to be empty.</li>
+     * <li>You get exceptions when a file or directory cannot be deleted.
+     *      (java.io.File methods returns a boolean)</li>
+     * </ul>
+     *
+     * @param file  file or directory to delete, must not be <code>null</code>
+     * @throws NullPointerException if the directory is <code>null</code>
+     * @throws FileNotFoundException if the file was not found
+     * @throws IOException in case deletion is unsuccessful
+     */
+    public static void forceDelete(File file) throws IOException {
+        if (file.isDirectory()) {
+            deleteDirectory(file);
+        } else {
+            boolean filePresent = file.exists();
+            if (!file.delete()) {
+                if (!filePresent){
+                    throw new FileNotFoundException("File does not exist: " + file);
+                }
+                String message =
+                    "Unable to delete file: " + file;
+                throw new IOException(message);
+            }
+        }
+    }
+
+    /**
+     * Schedules a file to be deleted when JVM exits.
+     * If file is directory delete it and all sub-directories.
+     *
+     * @param file  file or directory to delete, must not be <code>null</code>
+     * @throws NullPointerException if the file is <code>null</code>
+     * @throws IOException in case deletion is unsuccessful
+     */
+    public static void forceDeleteOnExit(File file) throws IOException {
+        if (file.isDirectory()) {
+            deleteDirectoryOnExit(file);
+        } else {
+            file.deleteOnExit();
+        }
+    }
+
+    /**
+     * Schedules a directory recursively for deletion on JVM exit.
+     *
+     * @param directory  directory to delete, must not be <code>null</code>
+     * @throws NullPointerException if the directory is <code>null</code>
+     * @throws IOException in case deletion is unsuccessful
+     */
+    private static void deleteDirectoryOnExit(File directory) throws IOException {
+        if (!directory.exists()) {
+            return;
+        }
+
+        cleanDirectoryOnExit(directory);
+        directory.deleteOnExit();
+    }
+
+    /**
+     * Cleans a directory without deleting it.
+     *
+     * @param directory  directory to clean, must not be <code>null</code>
+     * @throws NullPointerException if the directory is <code>null</code>
+     * @throws IOException in case cleaning is unsuccessful
+     */
+    private static void cleanDirectoryOnExit(File directory) throws IOException {
+        if (!directory.exists()) {
+            String message = directory + " does not exist";
+            throw new IllegalArgumentException(message);
+        }
+
+        if (!directory.isDirectory()) {
+            String message = directory + " is not a directory";
+            throw new IllegalArgumentException(message);
+        }
+
+        File[] files = directory.listFiles();
+        if (files == null) {  // null if security restricted
+            throw new IOException("Failed to list contents of " + directory);
+        }
+
+        IOException exception = null;
+        for (int i = 0; i < files.length; i++) {
+            File file = files[i];
+            try {
+                forceDeleteOnExit(file);
+            } catch (IOException ioe) {
+                exception = ioe;
+            }
+        }
+
+        if (null != exception) {
+            throw exception;
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/IOUtils.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/IOUtils.java
new file mode 100644
index 0000000..574cc5f
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/IOUtils.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;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+
+/**
+ * General IO stream manipulation utilities.
+ * <p>
+ * This class provides static utility methods for input/output operations.
+ * <ul>
+ * <li>closeQuietly - these methods close a stream ignoring nulls and exceptions
+ * <li>toXxx/read - these methods read data from a stream
+ * <li>write - these methods write data to a stream
+ * <li>copy - these methods copy all the data from one stream to another
+ * <li>contentEquals - these methods compare the content of two streams
+ * </ul>
+ * <p>
+ * The byte-to-char methods and char-to-byte methods involve a conversion step.
+ * Two methods are provided in each case, one that uses the platform default
+ * encoding and the other which allows you to specify an encoding. You are
+ * encouraged to always specify an encoding because relying on the platform
+ * default can lead to unexpected results, for example when moving from
+ * development to production.
+ * <p>
+ * All the methods in this class that read a stream are buffered internally.
+ * This means that there is no cause to use a <code>BufferedInputStream</code>
+ * or <code>BufferedReader</code>. The default buffer size of 4K has been shown
+ * to be efficient in tests.
+ * <p>
+ * Wherever possible, the methods in this class do <em>not</em> flush or close
+ * the stream. This is to avoid making non-portable assumptions about the
+ * streams' origin and further use. Thus the caller is still responsible for
+ * closing streams after use.
+ * <p>
+ * Origin of code: Excalibur.
+ *
+ * @author Peter Donald
+ * @author Jeff Turner
+ * @author Matthew Hawthorne
+ * @author Stephen Colebourne
+ * @author Gareth Davis
+ * @author Ian Springer
+ * @author Niall Pemberton
+ * @author Sandy McArthur
+ * @version $Id: IOUtils.java,v 1.1 2011/06/28 21:08:18 rherrmann Exp $
+ */
+public class IOUtils {
+    // NOTE: This class is focussed on InputStream, OutputStream, Reader and
+    // Writer. Each method should take at least one of these as a parameter,
+    // or return one of them.
+
+    /**
+     * The default buffer size to use.
+     */
+    private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
+
+    /**
+     * Instances should NOT be constructed in standard programming.
+     */
+    public IOUtils() {
+        super();
+    }
+
+
+    /**
+     * Unconditionally close an <code>InputStream</code>.
+     * <p>
+     * Equivalent to {@link InputStream#close()}, except any exceptions will be ignored.
+     * This is typically used in finally blocks.
+     *
+     * @param input  the InputStream to close, may be null or already closed
+     */
+    public static void closeQuietly(InputStream input) {
+        try {
+            if (input != null) {
+                input.close();
+            }
+        } catch (IOException ioe) {
+            // ignore
+        }
+    }
+
+    // copy from InputStream
+    //-----------------------------------------------------------------------
+    /**
+     * Copy bytes from an <code>InputStream</code> to an
+     * <code>OutputStream</code>.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedInputStream</code>.
+     * <p>
+     * Large streams (over 2GB) will return a bytes copied value of
+     * <code>-1</code> after the copy has completed since the correct
+     * number of bytes cannot be returned as an int. For large streams
+     * use the <code>copyLarge(InputStream, OutputStream)</code> method.
+     * 
+     * @param input  the <code>InputStream</code> to read from
+     * @param output  the <code>OutputStream</code> to write to
+     * @return the number of bytes copied
+     * @throws NullPointerException if the input or output is null
+     * @throws IOException if an I/O error occurs
+     * @throws ArithmeticException if the byte count is too large
+     * @since Commons IO 1.1
+     */
+    public static int copy(InputStream input, OutputStream output) throws IOException {
+        long count = copyLarge(input, output);
+        if (count > Integer.MAX_VALUE) {
+            return -1;
+        }
+        return (int) count;
+    }
+
+    /**
+     * Copy bytes from a large (over 2GB) <code>InputStream</code> to an
+     * <code>OutputStream</code>.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedInputStream</code>.
+     * 
+     * @param input  the <code>InputStream</code> to read from
+     * @param output  the <code>OutputStream</code> to write to
+     * @return the number of bytes copied
+     * @throws NullPointerException if the input or output is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.3
+     */
+    public static long copyLarge(InputStream input, OutputStream output)
+            throws IOException {
+        byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
+        long count = 0;
+        int n = 0;
+        while (-1 != (n = input.read(buffer))) {
+            output.write(buffer, 0, n);
+            count += n;
+        }
+        return count;
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/InvalidFileNameException.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/InvalidFileNameException.java
new file mode 100644
index 0000000..f86b6f5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/InvalidFileNameException.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.http.fileupload;
+
+
+/**
+ * This exception is thrown in case of an invalid file name.
+ * A file name is invalid, if it contains a NUL character.
+ * Attackers might use this to circumvent security checks:
+ * For example, a malicious user might upload a file with the name
+ * "foo.exe\0.png". This file name might pass security checks (i.e.
+ * checks for the extension ".png"), while, depending on the underlying
+ * C library, it might create a file named "foo.exe", as the NUL
+ * character is the string terminator in C.
+ */
+public class InvalidFileNameException extends RuntimeException {
+    private static final long serialVersionUID = 7922042602454350470L;
+    private final String name;
+
+    /**
+     * Creates a new instance.
+     * @param pName The file name causing the exception.
+     * @param pMessage A human readable error message.
+     */
+    public InvalidFileNameException(String pName, String pMessage) {
+        super(pMessage);
+        name = pName;
+    }
+
+    /**
+     * Returns the invalid file name.
+     */
+    public String getName() {
+        return name;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/MultipartStream.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/MultipartStream.java
new file mode 100644
index 0000000..16e545e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/MultipartStream.java
@@ -0,0 +1,967 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+import org.apache.tomcat.util.http.fileupload.util.Closeable;
+import org.apache.tomcat.util.http.fileupload.util.Streams;
+
+
+/**
+ * <p> Low level API for processing file uploads.
+ *
+ * <p> This class can be used to process data streams conforming to MIME
+ * 'multipart' format as defined in
+ * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>. Arbitrarily
+ * large amounts of data in the stream can be processed under constant
+ * memory usage.
+ *
+ * <p> The format of the stream is defined in the following way:<br>
+ *
+ * <code>
+ *   multipart-body := preamble 1*encapsulation close-delimiter epilogue<br>
+ *   encapsulation := delimiter body CRLF<br>
+ *   delimiter := "--" boundary CRLF<br>
+ *   close-delimiter := "--" boundary "--"<br>
+ *   preamble := &lt;ignore&gt;<br>
+ *   epilogue := &lt;ignore&gt;<br>
+ *   body := header-part CRLF body-part<br>
+ *   header-part := 1*header CRLF<br>
+ *   header := header-name ":" header-value<br>
+ *   header-name := &lt;printable ascii characters except ":"&gt;<br>
+ *   header-value := &lt;any ascii characters except CR & LF&gt;<br>
+ *   body-data := &lt;arbitrary data&gt;<br>
+ * </code>
+ *
+ * <p>Note that body-data can contain another mulipart entity.  There
+ * is limited support for single pass processing of such nested
+ * streams.  The nested stream is <strong>required</strong> to have a
+ * boundary token of the same length as the parent stream (see {@link
+ * #setBoundary(byte[])}).
+ *
+ * <p>Here is an example of usage of this class.<br>
+ *
+ * <pre>
+ *   try {
+ *     MultipartStream multipartStream = new MultipartStream(input, boundary);
+ *     boolean nextPart = multipartStream.skipPreamble();
+ *     OutputStream output;
+ *     while(nextPart) {
+ *       String header = multipartStream.readHeaders();
+ *       // process headers
+ *       // create some output stream
+ *       multipartStream.readBodyData(output);
+ *       nextPart = multipartStream.readBoundary();
+ *     }
+ *   } catch(MultipartStream.MalformedStreamException e) {
+ *     // the stream failed to follow required syntax
+ *   } catch(IOException e) {
+ *     // a read or write error occurred
+ *   }
+ * </pre>
+ *
+ * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
+ * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ * @author Sean C. Sullivan
+ *
+ * @version $Id: MultipartStream.java,v 1.1 2011/06/28 21:08:18 rherrmann Exp $
+ */
+public class MultipartStream {
+    /**
+     * Internal class, which is used to invoke the
+     * {@link ProgressListener}.
+     */
+    public static class ProgressNotifier {
+        /** The listener to invoke.
+         */
+        private final ProgressListener listener;
+        /** Number of expected bytes, if known, or -1.
+         */
+        private final long contentLength;
+        /** Number of bytes, which have been read so far.
+         */
+        private long bytesRead;
+        /** Number of items, which have been read so far.
+         */
+        private int items;
+        /** Creates a new instance with the given listener
+         * and content length.
+         * @param pListener The listener to invoke.
+         * @param pContentLength The expected content length.
+         */
+        ProgressNotifier(ProgressListener pListener, long pContentLength) {
+            listener = pListener;
+            contentLength = pContentLength;
+        }
+        /** Called to indicate that bytes have been read.
+         * @param pBytes Number of bytes, which have been read.
+         */
+        void noteBytesRead(int pBytes) {
+            /* Indicates, that the given number of bytes have been read from
+             * the input stream.
+             */
+            bytesRead += pBytes;
+            notifyListener();
+        }
+        /** Called to indicate, that a new file item has been detected.
+         */
+        void noteItem() {
+            ++items;
+            notifyListener();
+        }
+        /** Called for notifying the listener.
+         */
+        private void notifyListener() {
+            if (listener != null) {
+                listener.update(bytesRead, contentLength, items);
+            }
+        }
+    }
+
+    // ----------------------------------------------------- Manifest constants
+
+
+    /**
+     * The Carriage Return ASCII character value.
+     */
+    public static final byte CR = 0x0D;
+
+
+    /**
+     * The Line Feed ASCII character value.
+     */
+    public static final byte LF = 0x0A;
+
+
+    /**
+     * The dash (-) ASCII character value.
+     */
+    public static final byte DASH = 0x2D;
+
+
+    /**
+     * The maximum length of <code>header-part</code> that will be
+     * processed (10 kilobytes = 10240 bytes.).
+     */
+    public static final int HEADER_PART_SIZE_MAX = 10240;
+
+
+    /**
+     * The default length of the buffer used for processing a request.
+     */
+    protected static final int DEFAULT_BUFSIZE = 4096;
+
+
+    /**
+     * A byte sequence that marks the end of <code>header-part</code>
+     * (<code>CRLFCRLF</code>).
+     */
+    protected static final byte[] HEADER_SEPARATOR = {
+        CR, LF, CR, LF };
+
+
+    /**
+     * A byte sequence that that follows a delimiter that will be
+     * followed by an encapsulation (<code>CRLF</code>).
+     */
+    protected static final byte[] FIELD_SEPARATOR = {
+        CR, LF};
+
+
+    /**
+     * A byte sequence that that follows a delimiter of the last
+     * encapsulation in the stream (<code>--</code>).
+     */
+    protected static final byte[] STREAM_TERMINATOR = {
+        DASH, DASH};
+
+
+    /**
+     * A byte sequence that precedes a boundary (<code>CRLF--</code>).
+     */
+    protected static final byte[] BOUNDARY_PREFIX = {
+        CR, LF, DASH, DASH};
+
+
+    // ----------------------------------------------------------- Data members
+
+
+    /**
+     * The input stream from which data is read.
+     */
+    private final InputStream input;
+
+
+    /**
+     * The length of the boundary token plus the leading <code>CRLF--</code>.
+     */
+    private int boundaryLength;
+
+
+    /**
+     * The amount of data, in bytes, that must be kept in the buffer in order
+     * to detect delimiters reliably.
+     */
+    private int keepRegion;
+
+
+    /**
+     * The byte sequence that partitions the stream.
+     */
+    private byte[] boundary;
+
+
+    /**
+     * The length of the buffer used for processing the request.
+     */
+    private final int bufSize;
+
+
+    /**
+     * The buffer used for processing the request.
+     */
+    private final byte[] buffer;
+
+
+    /**
+     * The index of first valid character in the buffer.
+     * <br>
+     * 0 <= head < bufSize
+     */
+    private int head;
+
+
+    /**
+     * The index of last valid character in the buffer + 1.
+     * <br>
+     * 0 <= tail <= bufSize
+     */
+    private int tail;
+
+
+    /**
+     * The content encoding to use when reading headers.
+     */
+    private String headerEncoding;
+
+
+    /**
+     * The progress notifier, if any, or null.
+     */
+    private final ProgressNotifier notifier;
+
+    // ----------------------------------------------------------- Constructors
+
+    /**
+     * <p> Constructs a <code>MultipartStream</code> with a custom size buffer.
+     *
+     * <p> Note that the buffer must be at least big enough to contain the
+     * boundary string, plus 4 characters for CR/LF and double dash, plus at
+     * least one byte of data.  Too small a buffer size setting will degrade
+     * performance.
+     *
+     * @param input    The <code>InputStream</code> to serve as a data source.
+     * @param boundary The token used for dividing the stream into
+     *                 <code>encapsulations</code>.
+     * @param bufSize  The size of the buffer to be used, in bytes.
+     * @param pNotifier The notifier, which is used for calling the
+     *                  progress listener, if any.
+     *
+     * @see #MultipartStream(InputStream, byte[],
+     *     MultipartStream.ProgressNotifier)
+     */
+    MultipartStream(InputStream input,
+            byte[] boundary,
+            int bufSize,
+            ProgressNotifier pNotifier) {
+        this.input = input;
+        this.bufSize = bufSize;
+        this.buffer = new byte[bufSize];
+        this.notifier = pNotifier;
+
+        // We prepend CR/LF to the boundary to chop trailing CR/LF from
+        // body-data tokens.
+        this.boundary = new byte[boundary.length + BOUNDARY_PREFIX.length];
+        this.boundaryLength = boundary.length + BOUNDARY_PREFIX.length;
+        this.keepRegion = this.boundary.length;
+        System.arraycopy(BOUNDARY_PREFIX, 0, this.boundary, 0,
+                BOUNDARY_PREFIX.length);
+        System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length,
+                boundary.length);
+
+        head = 0;
+        tail = 0;
+    }
+
+
+    /**
+     * <p> Constructs a <code>MultipartStream</code> with a default size buffer.
+     *
+     * @param input    The <code>InputStream</code> to serve as a data source.
+     * @param boundary The token used for dividing the stream into
+     *                 <code>encapsulations</code>.
+     * @param pNotifier An object for calling the progress listener, if any.
+     *
+     *
+     * @see #MultipartStream(InputStream, byte[], int,
+     *     MultipartStream.ProgressNotifier)
+     */
+    MultipartStream(InputStream input,
+            byte[] boundary,
+            ProgressNotifier pNotifier) {
+        this(input, boundary, DEFAULT_BUFSIZE, pNotifier);
+    }
+
+    // --------------------------------------------------------- Public methods
+
+
+    /**
+     * Retrieves the character encoding used when reading the headers of an
+     * individual part. When not specified, or <code>null</code>, the platform
+     * default encoding is used.
+
+     *
+     * @return The encoding used to read part headers.
+     */
+    public String getHeaderEncoding() {
+        return headerEncoding;
+    }
+
+
+    /**
+     * Specifies the character encoding to be used when reading the headers of
+     * individual parts. When not specified, or <code>null</code>, the platform
+     * default encoding is used.
+     *
+     * @param encoding The encoding used to read part headers.
+     */
+    public void setHeaderEncoding(String encoding) {
+        headerEncoding = encoding;
+    }
+
+
+    /**
+     * Reads a byte from the <code>buffer</code>, and refills it as
+     * necessary.
+     *
+     * @return The next byte from the input stream.
+     *
+     * @throws IOException if there is no more data available.
+     */
+    public byte readByte() throws IOException {
+        // Buffer depleted ?
+        if (head == tail) {
+            head = 0;
+            // Refill.
+            tail = input.read(buffer, head, bufSize);
+            if (tail == -1) {
+                // No more data available.
+                throw new IOException("No more data is available");
+            }
+            if (notifier != null) {
+                notifier.noteBytesRead(tail);
+            }
+        }
+        return buffer[head++];
+    }
+
+
+    /**
+     * Skips a <code>boundary</code> token, and checks whether more
+     * <code>encapsulations</code> are contained in the stream.
+     *
+     * @return <code>true</code> if there are more encapsulations in
+     *         this stream; <code>false</code> otherwise.
+     *
+     * @throws MalformedStreamException if the stream ends unexpecetedly or
+     *                                  fails to follow required syntax.
+     */
+    public boolean readBoundary()
+            throws MalformedStreamException {
+        byte[] marker = new byte[2];
+        boolean nextChunk = false;
+
+        head += boundaryLength;
+        try {
+            marker[0] = readByte();
+            if (marker[0] == LF) {
+                // Work around IE5 Mac bug with input type=image.
+                // Because the boundary delimiter, not including the trailing
+                // CRLF, must not appear within any file (RFC 2046, section
+                // 5.1.1), we know the missing CR is due to a buggy browser
+                // rather than a file containing something similar to a
+                // boundary.
+                return true;
+            }
+
+            marker[1] = readByte();
+            if (arrayequals(marker, STREAM_TERMINATOR, 2)) {
+                nextChunk = false;
+            } else if (arrayequals(marker, FIELD_SEPARATOR, 2)) {
+                nextChunk = true;
+            } else {
+                throw new MalformedStreamException(
+                "Unexpected characters follow a boundary");
+            }
+        } catch (IOException e) {
+            throw new MalformedStreamException("Stream ended unexpectedly");
+        }
+        return nextChunk;
+    }
+
+
+    /**
+     * <p>Changes the boundary token used for partitioning the stream.
+     *
+     * <p>This method allows single pass processing of nested multipart
+     * streams.
+     *
+     * <p>The boundary token of the nested stream is <code>required</code>
+     * to be of the same length as the boundary token in parent stream.
+     *
+     * <p>Restoring the parent stream boundary token after processing of a
+     * nested stream is left to the application.
+     *
+     * @param boundary The boundary to be used for parsing of the nested
+     *                 stream.
+     *
+     * @throws IllegalBoundaryException if the <code>boundary</code>
+     *                                  has a different length than the one
+     *                                  being currently parsed.
+     */
+    public void setBoundary(byte[] boundary)
+            throws IllegalBoundaryException {
+        if (boundary.length != boundaryLength - BOUNDARY_PREFIX.length) {
+            throw new IllegalBoundaryException(
+            "The length of a boundary token can not be changed");
+        }
+        System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length,
+                boundary.length);
+    }
+
+
+    /**
+     * <p>Reads the <code>header-part</code> of the current
+     * <code>encapsulation</code>.
+     *
+     * <p>Headers are returned verbatim to the input stream, including the
+     * trailing <code>CRLF</code> marker. Parsing is left to the
+     * application.
+     *
+     * <p><strong>TODO</strong> allow limiting maximum header size to
+     * protect against abuse.
+     *
+     * @return The <code>header-part</code> of the current encapsulation.
+     *
+     * @throws MalformedStreamException if the stream ends unexpecetedly.
+     */
+    public String readHeaders()
+    throws MalformedStreamException {
+        int i = 0;
+        byte b;
+        // to support multi-byte characters
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        int size = 0;
+        while (i < HEADER_SEPARATOR.length) {
+            try {
+                b = readByte();
+            } catch (IOException e) {
+                throw new MalformedStreamException("Stream ended unexpectedly");
+            }
+            if (++size > HEADER_PART_SIZE_MAX) {
+                throw new MalformedStreamException(
+                        "Header section has more than " + HEADER_PART_SIZE_MAX
+                        + " bytes (maybe it is not properly terminated)");
+            }
+            if (b == HEADER_SEPARATOR[i]) {
+                i++;
+            } else {
+                i = 0;
+            }
+            baos.write(b);
+        }
+
+        String headers = null;
+        if (headerEncoding != null) {
+            try {
+                headers = baos.toString(headerEncoding);
+            } catch (UnsupportedEncodingException e) {
+                // Fall back to platform default if specified encoding is not
+                // supported.
+                headers = baos.toString();
+            }
+        } else {
+            headers = baos.toString();
+        }
+
+        return headers;
+    }
+
+
+    /**
+     * <p>Reads <code>body-data</code> from the current
+     * <code>encapsulation</code> and writes its contents into the
+     * output <code>Stream</code>.
+     *
+     * <p>Arbitrary large amounts of data can be processed by this
+     * method using a constant size buffer. (see {@link
+     * #MultipartStream(InputStream,byte[],int,
+     *   MultipartStream.ProgressNotifier) constructor}).
+     *
+     * @param output The <code>Stream</code> to write data into. May
+     *               be null, in which case this method is equivalent
+     *               to {@link #discardBodyData()}.
+     *
+     * @return the amount of data written.
+     *
+     * @throws MalformedStreamException if the stream ends unexpectedly.
+     * @throws IOException              if an i/o error occurs.
+     */
+    public int readBodyData(OutputStream output)
+            throws MalformedStreamException, IOException {
+        final InputStream istream = newInputStream();
+        return (int) Streams.copy(istream, output, false);
+    }
+
+    /**
+     * Creates a new {@link ItemInputStream}.
+     * @return A new instance of {@link ItemInputStream}.
+     */
+    ItemInputStream newInputStream() {
+        return new ItemInputStream();
+    }
+
+    /**
+     * <p> Reads <code>body-data</code> from the current
+     * <code>encapsulation</code> and discards it.
+     *
+     * <p>Use this method to skip encapsulations you don't need or don't
+     * understand.
+     *
+     * @return The amount of data discarded.
+     *
+     * @throws MalformedStreamException if the stream ends unexpectedly.
+     * @throws IOException              if an i/o error occurs.
+     */
+    public int discardBodyData()
+    throws MalformedStreamException,
+    IOException {
+        return readBodyData(null);
+    }
+
+
+    /**
+     * Finds the beginning of the first <code>encapsulation</code>.
+     *
+     * @return <code>true</code> if an <code>encapsulation</code> was found in
+     *         the stream.
+     *
+     * @throws IOException if an i/o error occurs.
+     */
+    public boolean skipPreamble()
+    throws IOException {
+        // First delimiter may be not preceeded with a CRLF.
+        System.arraycopy(boundary, 2, boundary, 0, boundary.length - 2);
+        boundaryLength = boundary.length - 2;
+        try {
+            // Discard all data up to the delimiter.
+            discardBodyData();
+
+            // Read boundary - if succeeded, the stream contains an
+            // encapsulation.
+            return readBoundary();
+        } catch (MalformedStreamException e) {
+            return false;
+        } finally {
+            // Restore delimiter.
+            System.arraycopy(boundary, 0, boundary, 2, boundary.length - 2);
+            boundaryLength = boundary.length;
+            boundary[0] = CR;
+            boundary[1] = LF;
+        }
+    }
+
+
+    /**
+     * Compares <code>count</code> first bytes in the arrays
+     * <code>a</code> and <code>b</code>.
+     *
+     * @param a     The first array to compare.
+     * @param b     The second array to compare.
+     * @param count How many bytes should be compared.
+     *
+     * @return <code>true</code> if <code>count</code> first bytes in arrays
+     *         <code>a</code> and <code>b</code> are equal.
+     */
+    public static boolean arrayequals(byte[] a,
+            byte[] b,
+            int count) {
+        for (int i = 0; i < count; i++) {
+            if (a[i] != b[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+
+    /**
+     * Searches for a byte of specified value in the <code>buffer</code>,
+     * starting at the specified <code>position</code>.
+     *
+     * @param value The value to find.
+     * @param pos   The starting position for searching.
+     *
+     * @return The position of byte found, counting from beginning of the
+     *         <code>buffer</code>, or <code>-1</code> if not found.
+     */
+    protected int findByte(byte value,
+            int pos) {
+        for (int i = pos; i < tail; i++) {
+            if (buffer[i] == value) {
+                return i;
+            }
+        }
+
+        return -1;
+    }
+
+
+    /**
+     * Searches for the <code>boundary</code> in the <code>buffer</code>
+     * region delimited by <code>head</code> and <code>tail</code>.
+     *
+     * @return The position of the boundary found, counting from the
+     *         beginning of the <code>buffer</code>, or <code>-1</code> if
+     *         not found.
+     */
+    protected int findSeparator() {
+        int first;
+        int match = 0;
+        int maxpos = tail - boundaryLength;
+        for (first = head;
+        (first <= maxpos) && (match != boundaryLength);
+        first++) {
+            first = findByte(boundary[0], first);
+            if (first == -1 || (first > maxpos)) {
+                return -1;
+            }
+            for (match = 1; match < boundaryLength; match++) {
+                if (buffer[first + match] != boundary[match]) {
+                    break;
+                }
+            }
+        }
+        if (match == boundaryLength) {
+            return first - 1;
+        }
+        return -1;
+    }
+
+    /**
+     * Thrown to indicate that the input stream fails to follow the
+     * required syntax.
+     */
+    public static class MalformedStreamException
+            extends IOException {
+
+        private static final long serialVersionUID = 1L;
+
+        /**
+         * Constructs a <code>MalformedStreamException</code> with no
+         * detail message.
+         */
+        public MalformedStreamException() {
+            super();
+        }
+
+        /**
+         * Constructs an <code>MalformedStreamException</code> with
+         * the specified detail message.
+         *
+         * @param message The detail message.
+         */
+        public MalformedStreamException(String message) {
+            super(message);
+        }
+    }
+
+
+    /**
+     * Thrown upon attempt of setting an invalid boundary token.
+     */
+    public static class IllegalBoundaryException
+            extends IOException {
+
+        private static final long serialVersionUID = 1L;
+
+        /**
+         * Constructs an <code>IllegalBoundaryException</code> with no
+         * detail message.
+         */
+        public IllegalBoundaryException() {
+            super();
+        }
+
+        /**
+         * Constructs an <code>IllegalBoundaryException</code> with
+         * the specified detail message.
+         *
+         * @param message The detail message.
+         */
+        public IllegalBoundaryException(String message) {
+            super(message);
+        }
+    }
+
+    /**
+     * An {@link InputStream} for reading an items contents.
+     */
+    public class ItemInputStream extends InputStream implements Closeable {
+        /** The number of bytes, which have been read so far.
+         */
+        private long total;
+        /** The number of bytes, which must be hold, because
+         * they might be a part of the boundary.
+         */
+        private int pad;
+        /** The current offset in the buffer.
+         */
+        private int pos;
+        /** Whether the stream is already closed.
+         */
+        private boolean closed;
+
+        /**
+         * Creates a new instance.
+         */
+        ItemInputStream() {
+            findSeparator();
+        }
+
+        /**
+         * Called for finding the separator.
+         */
+        private void findSeparator() {
+            pos = MultipartStream.this.findSeparator();
+            if (pos == -1) {
+                if (tail - head > keepRegion) {
+                    pad = keepRegion;
+                } else {
+                    pad = tail - head;
+                }
+            }
+        }
+
+        /**
+         * Returns the number of bytes, which have been read
+         * by the stream.
+         * @return Number of bytes, which have been read so far.
+         */
+        public long getBytesRead() {
+            return total;
+        }
+
+        /**
+         * Returns the number of bytes, which are currently
+         * available, without blocking.
+         * @throws IOException An I/O error occurs.
+         * @return Number of bytes in the buffer.
+         */
+        @Override
+        public int available() throws IOException {
+            if (pos == -1) {
+                return tail - head - pad;
+            }
+            return pos - head;
+        }
+
+        /** Offset when converting negative bytes to integers.
+         */
+        private static final int BYTE_POSITIVE_OFFSET = 256;
+
+        /**
+         * Returns the next byte in the stream.
+         * @return The next byte in the stream, as a non-negative
+         *   integer, or -1 for EOF.
+         * @throws IOException An I/O error occurred.
+         */
+        @Override
+        public int read() throws IOException {
+            if (closed) {
+                throw new FileItemStream.ItemSkippedException();
+            }
+            if (available() == 0) {
+                if (makeAvailable() == 0) {
+                    return -1;
+                }
+            }
+            ++total;
+            int b = buffer[head++];
+            if (b >= 0) {
+                return b;
+            }
+            return b + BYTE_POSITIVE_OFFSET;
+        }
+
+        /**
+         * Reads bytes into the given buffer.
+         * @param b The destination buffer, where to write to.
+         * @param off Offset of the first byte in the buffer.
+         * @param len Maximum number of bytes to read.
+         * @return Number of bytes, which have been actually read,
+         *   or -1 for EOF.
+         * @throws IOException An I/O error occurred.
+         */
+        @Override
+        public int read(byte[] b, int off, int len) throws IOException {
+            if (closed) {
+                throw new FileItemStream.ItemSkippedException();
+            }
+            if (len == 0) {
+                return 0;
+            }
+            int res = available();
+            if (res == 0) {
+                res = makeAvailable();
+                if (res == 0) {
+                    return -1;
+                }
+            }
+            res = Math.min(res, len);
+            System.arraycopy(buffer, head, b, off, res);
+            head += res;
+            total += res;
+            return res;
+        }
+
+        /**
+         * Closes the input stream.
+         * @throws IOException An I/O error occurred.
+         */
+        @Override
+        public void close() throws IOException {
+            close(false);
+        }
+
+        /**
+         * Closes the input stream.
+         * @param pCloseUnderlying Whether to close the underlying stream
+         *   (hard close)
+         * @throws IOException An I/O error occurred.
+         */
+        public void close(boolean pCloseUnderlying) throws IOException {
+            if (closed) {
+                return;
+            }
+            if (pCloseUnderlying) {
+                closed = true;
+                input.close();
+            } else {
+                for (;;) {
+                    int av = available();
+                    if (av == 0) {
+                        av = makeAvailable();
+                        if (av == 0) {
+                            break;
+                        }
+                    }
+                    skip(av);
+                }
+            }
+            closed = true;
+        }
+
+        /**
+         * Skips the given number of bytes.
+         * @param bytes Number of bytes to skip.
+         * @return The number of bytes, which have actually been
+         *   skipped.
+         * @throws IOException An I/O error occurred.
+         */
+        @Override
+        public long skip(long bytes) throws IOException {
+            if (closed) {
+                throw new FileItemStream.ItemSkippedException();
+            }
+            int av = available();
+            if (av == 0) {
+                av = makeAvailable();
+                if (av == 0) {
+                    return 0;
+                }
+            }
+            long res = Math.min(av, bytes);
+            head += res;
+            return res;
+        }
+
+        /**
+         * Attempts to read more data.
+         * @return Number of available bytes
+         * @throws IOException An I/O error occurred.
+         */
+        private int makeAvailable() throws IOException {
+            if (pos != -1) {
+                return 0;
+            }
+
+            // Move the data to the beginning of the buffer.
+            total += tail - head - pad;
+            System.arraycopy(buffer, tail - pad, buffer, 0, pad);
+
+            // Refill buffer with new data.
+            head = 0;
+            tail = pad;
+
+            for (;;) {
+                int bytesRead = input.read(buffer, tail, bufSize - tail);
+                if (bytesRead == -1) {
+                    // The last pad amount is left in the buffer.
+                    // Boundary can't be in there so signal an error
+                    // condition.
+                    final String msg = "Stream ended unexpectedly";
+                    throw new MalformedStreamException(msg);
+                }
+                if (notifier != null) {
+                    notifier.noteBytesRead(bytesRead);
+                }
+                tail += bytesRead;
+
+                findSeparator();
+                int av = available();
+
+                if (av > 0 || pos != -1) {
+                    return av;
+                }
+            }
+        }
+
+        /**
+         * Returns, whether the stream is closed.
+         * @return True, if the stream is closed, otherwise false.
+         */
+        public boolean isClosed() {
+            return closed;
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/ParameterParser.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/ParameterParser.java
new file mode 100644
index 0000000..42dadc2
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/ParameterParser.java
@@ -0,0 +1,330 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * A simple parser intended to parse sequences of name/value pairs.
+ * Parameter values are exptected to be enclosed in quotes if they
+ * contain unsafe characters, such as '=' characters or separators.
+ * Parameter values are optional and can be omitted.
+ *
+ * <p>
+ *  <code>param1 = value; param2 = "anything goes; really"; param3</code>
+ * </p>
+ *
+ * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
+ */
+
+public class ParameterParser {
+    /**
+     * String to be parsed.
+     */
+    private char[] chars = null;
+
+    /**
+     * Current position in the string.
+     */
+    private int pos = 0;
+
+    /**
+     * Maximum position in the string.
+     */
+    private int len = 0;
+
+    /**
+     * Start of a token.
+     */
+    private int i1 = 0;
+
+    /**
+     * End of a token.
+     */
+    private int i2 = 0;
+
+    /**
+     * Whether names stored in the map should be converted to lower case.
+     */
+    private boolean lowerCaseNames = false;
+
+    /**
+     * Default ParameterParser constructor.
+     */
+    public ParameterParser() {
+        super();
+    }
+
+    /**
+     * Are there any characters left to parse?
+     *
+     * @return <tt>true</tt> if there are unparsed characters,
+     *         <tt>false</tt> otherwise.
+     */
+    private boolean hasChar() {
+        return this.pos < this.len;
+    }
+
+    /**
+     * A helper method to process the parsed token. This method removes
+     * leading and trailing blanks as well as enclosing quotation marks,
+     * when necessary.
+     *
+     * @param quoted <tt>true</tt> if quotation marks are expected,
+     *               <tt>false</tt> otherwise.
+     * @return the token
+     */
+    private String getToken(boolean quoted) {
+        // Trim leading white spaces
+        while ((i1 < i2) && (Character.isWhitespace(chars[i1]))) {
+            i1++;
+        }
+        // Trim trailing white spaces
+        while ((i2 > i1) && (Character.isWhitespace(chars[i2 - 1]))) {
+            i2--;
+        }
+        // Strip away quotation marks if necessary
+        if (quoted) {
+            if (((i2 - i1) >= 2)
+                && (chars[i1] == '"')
+                && (chars[i2 - 1] == '"')) {
+                i1++;
+                i2--;
+            }
+        }
+        String result = null;
+        if (i2 > i1) {
+            result = new String(chars, i1, i2 - i1);
+        }
+        return result;
+    }
+
+    /**
+     * Tests if the given character is present in the array of characters.
+     *
+     * @param ch the character to test for presense in the array of characters
+     * @param charray the array of characters to test against
+     *
+     * @return <tt>true</tt> if the character is present in the array of
+     *   characters, <tt>false</tt> otherwise.
+     */
+    private boolean isOneOf(char ch, final char[] charray) {
+        boolean result = false;
+        for (int i = 0; i < charray.length; i++) {
+            if (ch == charray[i]) {
+                result = true;
+                break;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Parses out a token until any of the given terminators
+     * is encountered.
+     *
+     * @param terminators the array of terminating characters. Any of these
+     * characters when encountered signify the end of the token
+     *
+     * @return the token
+     */
+    private String parseToken(final char[] terminators) {
+        char ch;
+        i1 = pos;
+        i2 = pos;
+        while (hasChar()) {
+            ch = chars[pos];
+            if (isOneOf(ch, terminators)) {
+                break;
+            }
+            i2++;
+            pos++;
+        }
+        return getToken(false);
+    }
+
+    /**
+     * Parses out a token until any of the given terminators
+     * is encountered outside the quotation marks.
+     *
+     * @param terminators the array of terminating characters. Any of these
+     * characters when encountered outside the quotation marks signify the end
+     * of the token
+     *
+     * @return the token
+     */
+    private String parseQuotedToken(final char[] terminators) {
+        char ch;
+        i1 = pos;
+        i2 = pos;
+        boolean quoted = false;
+        boolean charEscaped = false;
+        while (hasChar()) {
+            ch = chars[pos];
+            if (!quoted && isOneOf(ch, terminators)) {
+                break;
+            }
+            if (!charEscaped && ch == '"') {
+                quoted = !quoted;
+            }
+            charEscaped = (!charEscaped && ch == '\\');
+            i2++;
+            pos++;
+
+        }
+        return getToken(true);
+    }
+
+    /**
+     * Returns <tt>true</tt> if parameter names are to be converted to lower
+     * case when name/value pairs are parsed.
+     *
+     * @return <tt>true</tt> if parameter names are to be
+     * converted to lower case when name/value pairs are parsed.
+     * Otherwise returns <tt>false</tt>
+     */
+    public boolean isLowerCaseNames() {
+        return this.lowerCaseNames;
+    }
+
+    /**
+     * Sets the flag if parameter names are to be converted to lower case when
+     * name/value pairs are parsed.
+     *
+     * @param b <tt>true</tt> if parameter names are to be
+     * converted to lower case when name/value pairs are parsed.
+     * <tt>false</tt> otherwise.
+     */
+    public void setLowerCaseNames(boolean b) {
+        this.lowerCaseNames = b;
+    }
+
+    /**
+     * Extracts a map of name/value pairs from the given string. Names are
+     * expected to be unique. Multiple separators may be specified and
+     * the earliest found in the input string is used.
+     *
+     * @param str the string that contains a sequence of name/value pairs
+     * @param separators the name/value pairs separators
+     *
+     * @return a map of name/value pairs
+     */
+    public Map<String,String> parse(final String str, char[] separators) {
+        if (separators == null || separators.length == 0) {
+            return new HashMap<String,String>();
+        }
+        char separator = separators[0];
+        if (str != null) {
+            int idx = str.length();
+            for (int i = 0;  i < separators.length;  i++) {
+                int tmp = str.indexOf(separators[i]);
+                if (tmp != -1) {
+                    if (tmp < idx) {
+                        idx = tmp;
+                        separator = separators[i];
+                    }
+                }
+            }
+        }
+        return parse(str, separator);
+    }
+
+    /**
+     * Extracts a map of name/value pairs from the given string. Names are
+     * expected to be unique.
+     *
+     * @param str the string that contains a sequence of name/value pairs
+     * @param separator the name/value pairs separator
+     *
+     * @return a map of name/value pairs
+     */
+    public Map<String,String> parse(final String str, char separator) {
+        if (str == null) {
+            return new HashMap<String,String>();
+        }
+        return parse(str.toCharArray(), separator);
+    }
+
+    /**
+     * Extracts a map of name/value pairs from the given array of
+     * characters. Names are expected to be unique.
+     *
+     * @param inputChars the array of characters that contains a sequence of
+     * name/value pairs
+     * @param separator the name/value pairs separator
+     *
+     * @return a map of name/value pairs
+     */
+    public Map<String,String> parse(final char[] inputChars, char separator) {
+        if (inputChars == null) {
+            return new HashMap<String,String>();
+        }
+        return parse(inputChars, 0, inputChars.length, separator);
+    }
+
+    /**
+     * Extracts a map of name/value pairs from the given array of
+     * characters. Names are expected to be unique.
+     *
+     * @param inputChars the array of characters that contains a sequence of
+     * name/value pairs
+     * @param offset - the initial offset.
+     * @param length - the length.
+     * @param separator the name/value pairs separator
+     *
+     * @return a map of name/value pairs
+     */
+    public Map<String,String> parse(
+        final char[] inputChars,
+        int offset,
+        int length,
+        char separator) {
+
+        if (inputChars == null) {
+            return new HashMap<String,String>();
+        }
+        HashMap<String,String> params = new HashMap<String,String>();
+        this.chars = inputChars;
+        this.pos = offset;
+        this.len = length;
+
+        String paramName = null;
+        String paramValue = null;
+        while (hasChar()) {
+            paramName = parseToken(new char[] {
+                    '=', separator });
+            paramValue = null;
+            if (hasChar() && (chars[pos] == '=')) {
+                pos++; // skip '='
+                paramValue = parseQuotedToken(new char[] {
+                        separator });
+            }
+            if (hasChar() && (chars[pos] == separator)) {
+                pos++; // skip separator
+            }
+            if ((paramName != null) && (paramName.length() > 0)) {
+                if (this.lowerCaseNames) {
+                    paramName = paramName.toLowerCase(Locale.ENGLISH);
+                }
+                params.put(paramName, paramValue);
+            }
+        }
+        return params;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/ProgressListener.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/ProgressListener.java
new file mode 100644
index 0000000..61744ca
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/ProgressListener.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.tomcat.util.http.fileupload;
+
+
+/**
+ * The {@link ProgressListener} may be used to display a progress bar
+ * or do stuff like that.
+ */
+public interface ProgressListener {
+    /** Updates the listeners status information.
+     * @param pBytesRead The total number of bytes, which have been read
+     *   so far.
+     * @param pContentLength The total number of bytes, which are being
+     *   read. May be -1, if this number is unknown.
+     * @param pItems The number of the field, which is currently being
+     *   read. (0 = no item so far, 1 = first item is being read, ...)
+     */
+    void update(long pBytesRead, long pContentLength, int pItems);
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/RequestContext.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/RequestContext.java
new file mode 100644
index 0000000..ad013c9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/RequestContext.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http.fileupload;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * <p>Abstracts access to the request information needed for file uploads. This
+ * interfsace should be implemented for each type of request that may be
+ * handled by FileUpload, such as servlets and portlets.</p>
+ *
+ * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ *
+ * @since FileUpload 1.1
+ *
+ * @version $Id: RequestContext.java,v 1.1 2011/06/28 21:08:18 rherrmann Exp $
+ */
+public interface RequestContext {
+
+    /**
+     * Retrieve the character encoding for the request.
+     *
+     * @return The character encoding for the request.
+     */
+    String getCharacterEncoding();
+
+    /**
+     * Retrieve the content type of the request.
+     *
+     * @return The content type of the request.
+     */
+    String getContentType();
+
+    /**
+     * Retrieve the content length of the request.
+     *
+     * @return The content length of the request.
+     */
+    int getContentLength();
+
+    /**
+     * Retrieve the input stream for the request.
+     *
+     * @return The input stream for the request.
+     *
+     * @throws IOException if a problem occurs.
+     */
+    InputStream getInputStream() throws IOException;
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/ThresholdingOutputStream.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/ThresholdingOutputStream.java
new file mode 100644
index 0000000..9d52efd
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/ThresholdingOutputStream.java
@@ -0,0 +1,262 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+
+/**
+ * An output stream which triggers an event when a specified number of bytes of
+ * data have been written to it. The event can be used, for example, to throw
+ * an exception if a maximum has been reached, or to switch the underlying
+ * stream type when the threshold is exceeded.
+ * <p>
+ * This class overrides all <code>OutputStream</code> methods. However, these
+ * overrides ultimately call the corresponding methods in the underlying output
+ * stream implementation.
+ * <p>
+ * NOTE: This implementation may trigger the event <em>before</em> the threshold
+ * is actually reached, since it triggers when a pending write operation would
+ * cause the threshold to be exceeded.
+ *
+ * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ *
+ * @version $Id: ThresholdingOutputStream.java,v 1.1 2011/06/28 21:08:18 rherrmann Exp $
+ */
+public abstract class ThresholdingOutputStream
+    extends OutputStream
+{
+
+    // ----------------------------------------------------------- Data members
+
+
+    /**
+     * The threshold at which the event will be triggered.
+     */
+    private int threshold;
+
+
+    /**
+     * The number of bytes written to the output stream.
+     */
+    private long written;
+
+
+    /**
+     * Whether or not the configured threshold has been exceeded.
+     */
+    private boolean thresholdExceeded;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Constructs an instance of this class which will trigger an event at the
+     * specified threshold.
+     *
+     * @param threshold The number of bytes at which to trigger an event.
+     */
+    public ThresholdingOutputStream(int threshold)
+    {
+        this.threshold = threshold;
+    }
+
+
+    // --------------------------------------------------- OutputStream methods
+
+
+    /**
+     * Writes the specified byte to this output stream.
+     *
+     * @param b The byte to be written.
+     *
+     * @exception IOException if an error occurs.
+     */
+    @Override
+    public void write(int b) throws IOException
+    {
+        checkThreshold(1);
+        getStream().write(b);
+        written++;
+    }
+
+
+    /**
+     * Writes <code>b.length</code> bytes from the specified byte array to this
+     * output stream.
+     *
+     * @param b The array of bytes to be written.
+     *
+     * @exception IOException if an error occurs.
+     */
+    @Override
+    public void write(byte b[]) throws IOException
+    {
+        checkThreshold(b.length);
+        getStream().write(b);
+        written += b.length;
+    }
+
+
+    /**
+     * Writes <code>len</code> bytes from the specified byte array starting at
+     * offset <code>off</code> to this output stream.
+     *
+     * @param b   The byte array from which the data will be written.
+     * @param off The start offset in the byte array.
+     * @param len The number of bytes to write.
+     *
+     * @exception IOException if an error occurs.
+     */
+    @Override
+    public void write(byte b[], int off, int len) throws IOException
+    {
+        checkThreshold(len);
+        getStream().write(b, off, len);
+        written += len;
+    }
+
+
+    /**
+     * Flushes this output stream and forces any buffered output bytes to be
+     * written out.
+     *
+     * @exception IOException if an error occurs.
+     */
+    @Override
+    public void flush() throws IOException
+    {
+        getStream().flush();
+    }
+
+
+    /**
+     * Closes this output stream and releases any system resources associated
+     * with this stream.
+     *
+     * @exception IOException if an error occurs.
+     */
+    @Override
+    public void close() throws IOException
+    {
+        try
+        {
+            flush();
+        }
+        catch (IOException ignored)
+        {
+            // ignore
+        }
+        getStream().close();
+    }
+
+
+    // --------------------------------------------------------- Public methods
+
+
+    /**
+     * Returns the threshold, in bytes, at which an event will be triggered.
+     *
+     * @return The threshold point, in bytes.
+     */
+    public int getThreshold()
+    {
+        return threshold;
+    }
+
+
+    /**
+     * Returns the number of bytes that have been written to this output stream.
+     *
+     * @return The number of bytes written.
+     */
+    public long getByteCount()
+    {
+        return written;
+    }
+
+
+    /**
+     * Determines whether or not the configured threshold has been exceeded for
+     * this output stream.
+     *
+     * @return <code>true</code> if the threshold has been reached;
+     *         <code>false</code> otherwise.
+     */
+    public boolean isThresholdExceeded()
+    {
+        return (written > threshold);
+    }
+
+
+    // ------------------------------------------------------ Protected methods
+
+
+    /**
+     * Checks to see if writing the specified number of bytes would cause the
+     * configured threshold to be exceeded. If so, triggers an event to allow
+     * a concrete implementation to take action on this.
+     *
+     * @param count The number of bytes about to be written to the underlying
+     *              output stream.
+     *
+     * @exception IOException if an error occurs.
+     */
+    protected void checkThreshold(int count) throws IOException
+    {
+        if (!thresholdExceeded && (written + count > threshold))
+        {
+            thresholdExceeded = true;
+            thresholdReached();
+        }
+    }
+
+    /**
+     * Resets the byteCount to zero.  You can call this from 
+     * {@link #thresholdReached()} if you want the event to be triggered again. 
+     */
+    protected void resetByteCount() 
+    {
+        this.thresholdExceeded = false;
+        this.written = 0;
+    }
+
+    // ------------------------------------------------------- Abstract methods
+
+
+    /**
+     * Returns the underlying output stream, to which the corresponding
+     * <code>OutputStream</code> methods in this class will ultimately delegate.
+     *
+     * @return The underlying output stream.
+     *
+     * @exception IOException if an error occurs.
+     */
+    protected abstract OutputStream getStream() throws IOException;
+
+
+    /**
+     * Indicates that the configured threshold has been reached, and that a
+     * subclass should take whatever action necessary on this event. This may
+     * include changing the underlying output stream.
+     *
+     * @exception IOException if an error occurs.
+     */
+    protected abstract void thresholdReached() throws IOException;
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/package.html b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/package.html
new file mode 100644
index 0000000..c543022
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/fileupload/package.html
@@ -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.
+-->
+<!-- $Id: package.html,v 1.1 2011/06/28 21:08:18 rherrmann Exp $ -->
+<html>
+  <head>
+    <title>
+      Overview of the org.apache.tomcat.util.http.fileupload component
+    </title>
+  </head>
+  <body>
+    <p><b>NOTE:</b> This code has been copied from commons-fileupload 1.2.1 and
+    commons-io 1.4 and package renamed to avoid clashes with any web apps that
+    may wish to use these libraries.
+    </p>
+    <p>
+      A component for handling HTML file uploads as specified by
+      <a href="http://www.ietf.org/rfc/rfc1867.txt" target="_top">RFC&nbsp;1867</a>.
+      This component provides support for uploads within both servlets (JSR 53)
+      and portlets (JSR 168).
+    </p>
+    <p>
+      While this package provides the generic functionality for file uploads,
+      these classes are not typically used directly. Instead, normal usage
+      involves one of the provided extensions of
+      {@link org.apache.tomcat.util.http.fileupload.FileUpload FileUpload} such as
+      {@link org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload ServletFileUpload}
+      together with a factory for 
+      {@link org.apache.tomcat.util.http.fileupload.FileItem FileItem} instances,
+      such as
+      {@link org.apache.tomcat.util.http.fileupload.disk.DiskFileItemFactory DiskFileItemFactory}.
+    </p>
+    <p>
+      The following is a brief example of typical usage in a servlet, storing
+      the uploaded files on disk.
+    </p>
+<pre>
+    public void doPost(HttpServletRequest req, HttpServletResponse res) {
+        DiskFileItemFactory factory = new DiskFileItemFactory();
+        // maximum size that will be stored in memory
+        factory.setSizeThreshold(4096);
+        // the location for saving data that is larger than getSizeThreshold()
+        factory.setRepository(new File("/tmp"));
+
+        ServletFileUpload upload = new ServletFileUpload(factory);
+        // maximum size before a FileUploadException will be thrown
+        upload.setSizeMax(1000000);
+
+        List fileItems = upload.parseRequest(req);
+        // assume we know there are two files. The first file is a small
+        // text file, the second is unknown and is written to a file on
+        // the server
+        Iterator i = fileItems.iterator();
+        String comment = ((FileItem)i.next()).getString();
+        FileItem fi = (FileItem)i.next();
+        // filename on the client
+        String fileName = fi.getName();
+        // save comment and filename to database
+        ...
+        // write the file
+        fi.write(new File("/www/uploads/", fileName));
+    }
+</pre>
+    <p>
+      In the example above, the first file is loaded into memory as a
+      <code>String</code>. Before calling the <code>getString</code> method,
+      the data may have been in memory or on disk depending on its size. The
+      second file we assume it will be large and therefore never explicitly
+      load it into memory, though if it is less than 4096 bytes it will be
+      in memory before it is written to its final location. When writing to
+      the final location, if the data is larger than the threshold, an attempt
+      is made to rename the temporary file to the given location.  If it cannot
+      be renamed, it is streamed to the new location.
+    </p>
+    <p>
+      Please see the FileUpload
+      <a href="http://commons.apache.org/fileupload/using.html" target="_top">User Guide</a>
+      for further details and examples of how to use this package.
+    </p>
+  </body>
+</html>
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/package.html b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/package.html
new file mode 100644
index 0000000..54792a5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/http/package.html
@@ -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.
+-->
+<html>
+<head>
+<title>util.http</title>
+<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+</head>
+
+<body bgcolor="#FFFFFF">
+Special utils for handling HTTP-specific entities - headers, parameters,
+cookies, etc.
+
+The utils are not specific to tomcat, but use util.MessageBytes.
+
+</body>
+</html>
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/AttributeInfo.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/AttributeInfo.java
new file mode 100644
index 0000000..21d65d9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/AttributeInfo.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import javax.management.MBeanAttributeInfo;
+
+
+/**
+ * <p>Internal configuration information for an <code>Attribute</code>
+ * descriptor.</p>
+ *
+ * @author Craig R. McClanahan
+ */
+public class AttributeInfo extends FeatureInfo {
+    static final long serialVersionUID = -2511626862303972143L;
+
+    // ----------------------------------------------------- Instance Variables
+    protected String displayName = null;
+
+    // Information about the method to use
+    protected String getMethod = null;
+    protected String setMethod = null;
+    protected boolean readable = true;
+    protected boolean writeable = true;
+    protected boolean is = false;
+    
+    // ------------------------------------------------------------- Properties
+
+    /**
+     * The display name of this attribute.
+     */
+    public String getDisplayName() {
+        return (this.displayName);
+    }
+
+    public void setDisplayName(String displayName) {
+        this.displayName = displayName;
+    }
+
+    /**
+     * The name of the property getter method, if non-standard.
+     */
+    public String getGetMethod() {
+        if(getMethod == null) 
+            getMethod = getMethodName(getName(), true, isIs());
+        return (this.getMethod);
+    }
+
+    public void setGetMethod(String getMethod) {
+        this.getMethod = getMethod;
+    }
+
+    /**
+     * Is this a boolean attribute with an "is" getter?
+     */
+    public boolean isIs() {
+        return (this.is);
+    }
+
+    public void setIs(boolean is) {
+        this.is = is;
+    }
+
+
+    /**
+     * Is this attribute readable by management applications?
+     */
+    public boolean isReadable() {
+        return (this.readable);
+    }
+
+    public void setReadable(boolean readable) {
+        this.readable = readable;
+    }
+
+
+    /**
+     * The name of the property setter method, if non-standard.
+     */
+    public String getSetMethod() {
+        if( setMethod == null )
+            setMethod = getMethodName(getName(), false, false);
+        return (this.setMethod);
+    }
+
+    public void setSetMethod(String setMethod) {
+        this.setMethod = setMethod;
+    }
+
+    /**
+     * Is this attribute writable by management applications?
+     */
+    public boolean isWriteable() {
+        return (this.writeable);
+    }
+
+    public void setWriteable(boolean writeable) {
+        this.writeable = writeable;
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Create and return a <code>ModelMBeanAttributeInfo</code> object that
+     * corresponds to the attribute described by this instance.
+     */
+    MBeanAttributeInfo createAttributeInfo() {
+        // Return our cached information (if any)
+        if (info == null) {
+            info = new MBeanAttributeInfo(getName(), getType(), getDescription(),
+                            isReadable(), isWriteable(), false);
+        }
+        return (MBeanAttributeInfo)info;
+    }
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Create and return the name of a default property getter or setter
+     * method, according to the specified values.
+     *
+     * @param name Name of the property itself
+     * @param getter Do we want a get method (versus a set method)?
+     * @param is If returning a getter, do we want the "is" form?
+     */
+    private String getMethodName(String name, boolean getter, boolean is) {
+
+        StringBuilder sb = new StringBuilder();
+        if (getter) {
+            if (is)
+                sb.append("is");
+            else
+                sb.append("get");
+        } else
+            sb.append("set");
+        sb.append(Character.toUpperCase(name.charAt(0)));
+        sb.append(name.substring(1));
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/BaseAttributeFilter.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/BaseAttributeFilter.java
new file mode 100644
index 0000000..d24e00e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/BaseAttributeFilter.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+
+import java.util.HashSet;
+
+import javax.management.AttributeChangeNotification;
+import javax.management.Notification;
+import javax.management.NotificationFilter;
+
+
+/**
+ * <p>Implementation of <code>NotificationFilter</code> for attribute change
+ * notifications.  This class is used by <code>BaseModelMBean</code> to
+ * construct attribute change notification event filters when a filter is not
+ * supplied by the application.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: BaseAttributeFilter.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public class BaseAttributeFilter implements NotificationFilter {
+
+    private static final long serialVersionUID = 1L;
+
+    // ----------------------------------------------------------- Constructors
+
+    /**
+     * Construct a new filter that accepts only the specified attribute
+     * name.
+     *
+     * @param name Name of the attribute to be accepted by this filter, or
+     *  <code>null</code> to accept all attribute names
+     */
+    public BaseAttributeFilter(String name) {
+
+        super();
+        if (name != null)
+            addAttribute(name);
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The set of attribute names that are accepted by this filter.  If this
+     * list is empty, all attribute names are accepted.
+     */
+    private HashSet<String> names = new HashSet<String>();
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add a new attribute name to the set of names accepted by this filter.
+     *
+     * @param name Name of the attribute to be accepted
+     */
+    public void addAttribute(String name) {
+
+        synchronized (names) {
+            names.add(name);
+        }
+
+    }
+
+
+    /**
+     * Clear all accepted names from this filter, so that it will accept
+     * all attribute names.
+     */
+    public void clear() {
+
+        synchronized (names) {
+            names.clear();
+        }
+
+    }
+
+
+    /**
+     * Return the set of names that are accepted by this filter.  If this
+     * filter accepts all attribute names, a zero length array will be
+     * returned.
+     */
+    public String[] getNames() {
+
+        synchronized (names) {
+            return names.toArray(new String[names.size()]);
+        }
+
+    }
+
+
+    /**
+     * <p>Test whether notification enabled for this event.
+     * Return true if:</p>
+     * <ul>
+     * <li>This is an attribute change notification</li>
+     * <li>Either the set of accepted names is empty (implying that all
+     *     attribute names are of interest) or the set of accepted names
+     *     includes the name of the attribute in this notification</li>
+     * </ul>
+     */
+    public boolean isNotificationEnabled(Notification notification) {
+
+        if (notification == null)
+            return (false);
+        if (!(notification instanceof AttributeChangeNotification))
+            return (false);
+        AttributeChangeNotification acn =
+            (AttributeChangeNotification) notification;
+        if (!AttributeChangeNotification.ATTRIBUTE_CHANGE.equals(acn.getType()))
+            return (false);
+        synchronized (names) {
+            if (names.size() < 1)
+                return (true);
+            else
+                return (names.contains(acn.getAttributeName()));
+        }
+
+    }
+
+
+    /**
+     * Remove an attribute name from the set of names accepted by this
+     * filter.
+     *
+     * @param name Name of the attribute to be removed
+     */
+    public void removeAttribute(String name) {
+
+        synchronized (names) {
+            names.remove(name);
+        }
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/BaseModelMBean.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/BaseModelMBean.java
new file mode 100644
index 0000000..c5438be
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/BaseModelMBean.java
@@ -0,0 +1,1174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Iterator;
+
+import javax.management.Attribute;
+import javax.management.AttributeChangeNotification;
+import javax.management.AttributeList;
+import javax.management.AttributeNotFoundException;
+import javax.management.DynamicMBean;
+import javax.management.InstanceNotFoundException;
+import javax.management.InvalidAttributeValueException;
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanRegistration;
+import javax.management.MBeanServer;
+import javax.management.Notification;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.ReflectionException;
+import javax.management.RuntimeErrorException;
+import javax.management.RuntimeOperationsException;
+import javax.management.modelmbean.InvalidTargetObjectTypeException;
+import javax.management.modelmbean.ModelMBeanNotificationBroadcaster;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/*
+ * Changes from commons.modeler:
+ * 
+ *  - use DynamicMBean
+ *  - remove methods not used in tomcat and redundant/not very generic
+ *  - must be created from the ManagedBean - I don't think there were any direct
+ *    uses, but now it is required.
+ *  - some of the gratuitous flexibility removed - instead this is more predictive and
+ *    strict with the use cases.
+ *  - all Method and metadata is stored in ManagedBean. BaseModelBMean and ManagedBean act
+ *    like Object and Class. 
+ *  - setModelMBean is no longer called on resources ( not used in tomcat )
+ *  - no caching of Methods for now - operations and setters are not called repeatedly in most 
+ *  management use cases. Getters should't be called very frequently either - and even if they
+ *  are, the overhead of getting the method should be small compared with other JMX costs ( RMI, etc ).
+ *  We can add getter cache if needed.
+ *  - removed unused constructor, fields
+ *  
+ *  TODO:
+ *   - clean up catalina.mbeans, stop using weird inheritance
+ */
+
+/**
+ * <p>Basic implementation of the <code>DynamicMBean</code> interface, which
+ * supports the minimal requirements of the interface contract.</p>
+ *
+ * <p>This can be used directly to wrap an existing java bean, or inside
+ * an mlet or anywhere an MBean would be used. 
+ *
+ * Limitations:
+ * <ul>
+ * <li>Only managed resources of type <code>objectReference</code> are
+ *     supported.</li>
+ * <li>Caching of attribute values and operation results is not supported.
+ *     All calls to <code>invoke()</code> are immediately executed.</li>
+ * <li>Persistence of MBean attributes and operations is not supported.</li>
+ * <li>All classes referenced as attribute types, operation parameters, or
+ *     operation return values must be one of the following:
+ *     <ul>
+ *     <li>One of the Java primitive types (boolean, byte, char, double,
+ *         float, integer, long, short).  Corresponding value will be wrapped
+ *         in the appropriate wrapper class automatically.</li>
+ *     <li>Operations that return no value should declare a return type of
+ *         <code>void</code>.</li>
+ *     </ul>
+ * <li>Attribute caching is not supported</li>
+ * </ul>
+ *
+ * @author Craig R. McClanahan
+ * @author Costin Manolache
+ */
+public class BaseModelMBean implements DynamicMBean, MBeanRegistration, ModelMBeanNotificationBroadcaster {
+    private static final Log log = LogFactory.getLog(BaseModelMBean.class);
+
+    // ----------------------------------------------------------- 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
+     */
+    protected BaseModelMBean() throws MBeanException, RuntimeOperationsException {
+        super();
+    }
+
+    // ----------------------------------------------------- Instance Variables
+
+    protected ObjectName oname=null;
+
+    /**
+     * Notification broadcaster for attribute changes.
+     */
+    protected BaseNotificationBroadcaster attributeBroadcaster = null;
+
+    /**
+     * Notification broadcaster for general notifications.
+     */
+    protected BaseNotificationBroadcaster generalBroadcaster = null;
+    
+    /** Metadata for the mbean instance.
+     */
+    protected ManagedBean managedBean = null;
+
+    /**
+     * The managed resource this MBean is associated with (if any).
+     */
+    protected Object resource = null;
+
+    // --------------------------------------------------- DynamicMBean Methods
+    // TODO: move to ManagedBean
+    static final Object[] NO_ARGS_PARAM = new Object[0];
+    static final Class<?>[] NO_ARGS_PARAM_SIG = new Class[0];
+    
+    protected String resourceType = null;
+
+    // key: operation val: invoke method
+    //private Hashtable invokeAttMap=new Hashtable();
+
+    /**
+     * 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
+     */
+    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");
+
+        if( (resource instanceof DynamicMBean) && 
+             ! ( resource instanceof BaseModelMBean )) {
+            return ((DynamicMBean)resource).getAttribute(name);
+        }
+        
+        Method m=managedBean.getGetter(name, this, resource);
+        Object result = null;
+        try {
+            Class<?> declaring = m.getDeclaringClass();
+            // workaround for catalina weird mbeans - the declaring class is BaseModelMBean.
+            // but this is the catalina class.
+            if( declaring.isAssignableFrom(this.getClass()) ) {
+                result = m.invoke(this, NO_ARGS_PARAM );
+            } else {
+                result = m.invoke(resource, NO_ARGS_PARAM );
+            }
+        } catch (InvocationTargetException e) {
+            Throwable t = e.getTargetException();
+            if (t == null)
+                t = e;
+            if (t instanceof RuntimeException)
+                throw new RuntimeOperationsException
+                    ((RuntimeException) t, "Exception invoking method " + name);
+            else if (t instanceof Error)
+                throw new RuntimeErrorException
+                    ((Error) t, "Error invoking method " + name);
+            else
+                throw new MBeanException
+                    (e, "Exception invoking method " + name);
+        } catch (Exception e) {
+            throw new MBeanException
+                (e, "Exception invoking method " + name);
+        }
+
+        // Return the results of this method invocation
+        // FIXME - should we validate the return type?
+        return (result);
+    }
+
+
+    /**
+     * Obtain and return the values of several attributes of this MBean.
+     *
+     * @param names Names of the requested attributes
+     */
+    public AttributeList getAttributes(String names[]) {
+
+        // Validate the input parameters
+        if (names == null)
+            throw new RuntimeOperationsException
+                (new IllegalArgumentException("Attribute names list is null"),
+                 "Attribute names list is null");
+
+        // Prepare our response, eating all exceptions
+        AttributeList response = new AttributeList();
+        for (int i = 0; i < names.length; i++) {
+            try {
+                response.add(new Attribute(names[i],getAttribute(names[i])));
+            } catch (Exception e) {
+                // Not having a particular attribute in the response
+                // is the indication of a getter problem
+            }
+        }
+        return (response);
+
+    }
+
+    public void setManagedBean(ManagedBean managedBean) {
+        this.managedBean = managedBean;
+    }
+
+    /**
+     * Return the <code>MBeanInfo</code> object for this MBean.
+     */
+    public MBeanInfo getMBeanInfo() {
+        return managedBean.getMBeanInfo();
+    }
+
+
+    /**
+     * Invoke a particular method on this MBean, and return any returned
+     * value.
+     *
+     * <p><strong>IMPLEMENTATION NOTE</strong> - This implementation will
+     * attempt to invoke this method on the MBean itself, or (if not
+     * available) on the managed resource object associated with this
+     * MBean.</p>
+     *
+     * @param name Name of the operation to be invoked
+     * @param params Array containing the method parameters of this operation
+     * @param signature Array containing the class names representing
+     *  the signature of this operation
+     *
+     * @exception MBeanException if the initializer of an object
+     *  throws an exception
+     * @exception ReflectioNException if a Java reflection exception
+     *  occurs when invoking a method
+     */
+    public Object invoke(String name, Object params[], String signature[])
+        throws MBeanException, ReflectionException 
+    {
+        if( (resource instanceof DynamicMBean) && 
+             ! ( resource instanceof BaseModelMBean )) {
+            return ((DynamicMBean)resource).invoke(name, params, signature);
+        }
+    
+        // Validate the input parameters
+        if (name == null)
+            throw new RuntimeOperationsException
+                (new IllegalArgumentException("Method name is null"),
+                 "Method name is null");
+
+        if( log.isDebugEnabled()) log.debug("Invoke " + name);
+
+        Method method= managedBean.getInvoke(name, params, signature, this, resource);
+        
+        // Invoke the selected method on the appropriate object
+        Object result = null;
+        try {
+            if( method.getDeclaringClass().isAssignableFrom( this.getClass()) ) {
+                result = method.invoke(this, params );
+            } else {
+                result = method.invoke(resource, params);
+            }
+        } catch (InvocationTargetException e) {
+            Throwable t = e.getTargetException();
+            log.error("Exception invoking method " + name , t );
+            if (t == null)
+                t = e;
+            if (t instanceof RuntimeException)
+                throw new RuntimeOperationsException
+                    ((RuntimeException) t, "Exception invoking method " + name);
+            else if (t instanceof Error)
+                throw new RuntimeErrorException
+                    ((Error) t, "Error invoking method " + name);
+            else
+                throw new MBeanException
+                    ((Exception)t, "Exception invoking method " + name);
+        } catch (Exception e) {
+            log.error("Exception invoking method " + name , e );
+            throw new MBeanException
+                (e, "Exception invoking method " + name);
+        }
+
+        // Return the results of this method invocation
+        // FIXME - should we validate the return type?
+        return (result);
+
+    }
+
+    static Class<?> getAttributeClass(String signature)
+        throws ReflectionException
+    {
+        if (signature.equals(Boolean.TYPE.getName()))
+            return Boolean.TYPE;
+        else if (signature.equals(Byte.TYPE.getName()))
+            return Byte.TYPE;
+        else if (signature.equals(Character.TYPE.getName()))
+            return Character.TYPE;
+        else if (signature.equals(Double.TYPE.getName()))
+            return Double.TYPE;
+        else if (signature.equals(Float.TYPE.getName()))
+            return Float.TYPE;
+        else if (signature.equals(Integer.TYPE.getName()))
+            return Integer.TYPE;
+        else if (signature.equals(Long.TYPE.getName()))
+            return Long.TYPE;
+        else if (signature.equals(Short.TYPE.getName()))
+            return Short.TYPE;
+        else {
+            try {
+                ClassLoader cl=Thread.currentThread().getContextClassLoader();
+                if( cl!=null )
+                    return cl.loadClass(signature); 
+            } catch( ClassNotFoundException e ) {
+            }
+            try {
+                return Class.forName(signature);
+            } catch (ClassNotFoundException e) {
+                throw new ReflectionException
+                    (e, "Cannot find Class for " + signature);
+            }
+        }
+    }
+
+    /**
+     * 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
+     */
+    public void setAttribute(Attribute attribute)
+        throws AttributeNotFoundException, MBeanException,
+        ReflectionException
+    {
+        if( log.isDebugEnabled() )
+            log.debug("Setting attribute " + this + " " + attribute );
+
+        if( (resource instanceof DynamicMBean) && 
+             ! ( resource instanceof BaseModelMBean )) {
+            try {
+                ((DynamicMBean)resource).setAttribute(attribute);
+            } catch (InvalidAttributeValueException e) {
+                throw new MBeanException(e);                
+            }
+            return;
+        }
+        
+        // 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");
+
+        Object oldValue=null;
+        //if( getAttMap.get(name) != null )
+        //    oldValue=getAttribute( name );
+
+        Method m=managedBean.getSetter(name,this,resource);
+
+        try {
+            if( m.getDeclaringClass().isAssignableFrom( this.getClass()) ) {
+                m.invoke(this, new Object[] { value });
+            } else {
+                m.invoke(resource, new Object[] { value });
+            }
+        } catch (InvocationTargetException e) {
+            Throwable t = e.getTargetException();
+            if (t == null)
+                t = e;
+            if (t instanceof RuntimeException)
+                throw new RuntimeOperationsException
+                    ((RuntimeException) t, "Exception invoking method " + name);
+            else if (t instanceof Error)
+                throw new RuntimeErrorException
+                    ((Error) t, "Error invoking method " + name);
+            else
+                throw new MBeanException
+                    (e, "Exception invoking method " + name);
+        } catch (Exception e) {
+            log.error("Exception invoking method " + name , e );
+            throw new MBeanException
+                (e, "Exception invoking method " + name);
+        }
+        try {
+            sendAttributeChangeNotification(new Attribute( name, oldValue),
+                    attribute);
+        } catch(Exception ex) {
+            log.error("Error sending notification " + name, ex);
+        }
+        //attributes.put( name, value );
+//        if( source != null ) {
+//            // this mbean is associated with a source - maybe we want to persist
+//            source.updateField(oname, name, value);
+//        }
+    }
+
+    @Override
+    public String toString() {
+        if( resource==null ) 
+            return "BaseModelMbean[" + resourceType + "]";
+        return resource.toString();
+    }
+
+    /**
+     * Set the values of several attributes of this MBean.
+     *
+     * @param attributes THe names and values to be set
+     *
+     * @return The list of attributes that were set and their new values
+     */
+    public AttributeList setAttributes(AttributeList attributes) {
+        AttributeList response = new AttributeList();
+
+        // Validate the input parameters
+        if (attributes == null)
+            return response;
+        
+        // Prepare and return our response, eating all exceptions
+        String names[] = new String[attributes.size()];
+        int n = 0;
+        Iterator<?> items = attributes.iterator();
+        while (items.hasNext()) {
+            Attribute item = (Attribute) items.next();
+            names[n++] = item.getName();
+            try {
+                setAttribute(item);
+            } catch (Exception e) {
+                // Ignore all exceptions
+            }
+        }
+
+        return (getAttributes(names));
+
+    }
+
+
+    // ----------------------------------------------------- ModelMBean Methods
+
+
+    /**
+     * Get the instance handle of the object against which we execute
+     * all methods in this ModelMBean management interface.
+     *
+     * @exception InstanceNotFoundException if the managed resource object
+     *  cannot be found
+     * @exception MBeanException if the initializer of the object throws
+     *  an exception
+     * @exception RuntimeOperationsException if the managed resource or the
+     *  resource type is <code>null</code> or invalid
+     */
+    public Object getManagedResource()
+        throws InstanceNotFoundException, InvalidTargetObjectTypeException,
+        MBeanException, RuntimeOperationsException {
+
+        if (resource == null)
+            throw new RuntimeOperationsException
+                (new IllegalArgumentException("Managed resource is null"),
+                 "Managed resource is null");
+
+        return resource;
+
+    }
+
+
+    /**
+     * Set the instance handle of the object against which we will execute
+     * all methods in this ModelMBean management interface.
+     *
+     * <strike>This method will detect and call "setModelMbean" method. A resource
+     * can implement this method to get a reference to the model mbean.
+     * The reference can be used to send notification and access the
+     * registry.
+     * </strike> The caller can provide the mbean instance or the object name to
+     * the resource, if needed.
+     *
+     * @param resource The resource object to be managed
+     * @param type The type of reference for the managed resource
+     *  ("ObjectReference", "Handle", "IOR", "EJBHandle", or
+     *  "RMIReference")
+     *
+     * @exception InstanceNotFoundException if the managed resource object
+     *  cannot be found
+     * @exception InvalidTargetObjectTypeException if this ModelMBean is
+     *  asked to handle a reference type it cannot deal with
+     * @exception MBeanException if the initializer of the object throws
+     *  an exception
+     * @exception RuntimeOperationsException if the managed resource or the
+     *  resource type is <code>null</code> or invalid
+     */
+    public void setManagedResource(Object resource, String type)
+        throws InstanceNotFoundException, 
+        MBeanException, RuntimeOperationsException
+    {
+        if (resource == null)
+            throw new RuntimeOperationsException
+                (new IllegalArgumentException("Managed resource is null"),
+                 "Managed resource is null");
+
+//        if (!"objectreference".equalsIgnoreCase(type))
+//            throw new InvalidTargetObjectTypeException(type);
+
+        this.resource = resource;
+        this.resourceType = resource.getClass().getName();
+        
+//        // Make the resource aware of the model mbean.
+//        try {
+//            Method m=resource.getClass().getMethod("setModelMBean",
+//                    new Class[] {ModelMBean.class});
+//            if( m!= null ) {
+//                m.invoke(resource, new Object[] {this});
+//            }
+//        } catch( NoSuchMethodException t ) {
+//            // ignore
+//        } catch( Throwable t ) {
+//            log.error( "Can't set model mbean ", t );
+//        }
+    }
+
+
+    // ------------------------------ ModelMBeanNotificationBroadcaster Methods
+
+
+    /**
+     * Add an attribute change notification event listener to this MBean.
+     *
+     * @param listener Listener that will receive event notifications
+     * @param name Name of the attribute of interest, or <code>null</code>
+     *  to indicate interest in all attributes
+     * @param handback Handback object to be sent along with event
+     *  notifications
+     *
+     * @exception IllegalArgumentException if the listener parameter is null
+     */
+    public void addAttributeChangeNotificationListener
+        (NotificationListener listener, String name, Object handback)
+        throws IllegalArgumentException {
+
+        if (listener == null)
+            throw new IllegalArgumentException("Listener is null");
+        if (attributeBroadcaster == null)
+            attributeBroadcaster = new BaseNotificationBroadcaster();
+
+        if( log.isDebugEnabled() )
+            log.debug("addAttributeNotificationListener " + listener);
+
+        BaseAttributeFilter filter = new BaseAttributeFilter(name);
+        attributeBroadcaster.addNotificationListener
+            (listener, filter, handback);
+
+    }
+
+
+    /**
+     * Remove an attribute change notification event listener from
+     * this MBean.
+     *
+     * @param listener The listener to be removed
+     * @param name The attribute name for which no more events are required
+     *
+     *
+     * @exception ListenerNotFoundException if this listener is not
+     *  registered in the MBean
+     */
+    public void removeAttributeChangeNotificationListener
+        (NotificationListener listener, String name)
+        throws ListenerNotFoundException {
+
+        if (listener == null)
+            throw new IllegalArgumentException("Listener is null");
+        if (attributeBroadcaster == null)
+            attributeBroadcaster = new BaseNotificationBroadcaster();
+
+        // FIXME - currently this removes *all* notifications for this listener
+        attributeBroadcaster.removeNotificationListener(listener);
+
+    }
+
+
+    /**
+     * Remove an attribute change notification event listener from
+     * this MBean.
+     *
+     * @param listener The listener to be removed
+     * @param attributeName The attribute name for which no more events are required
+     * @param handback Handback object to be sent along with event
+     *  notifications
+     *
+     *
+     * @exception ListenerNotFoundException if this listener is not
+     *  registered in the MBean
+     */
+    public void removeAttributeChangeNotificationListener
+        (NotificationListener listener, String attributeName, Object handback)
+        throws ListenerNotFoundException {
+
+        removeAttributeChangeNotificationListener(listener, attributeName);
+
+    }
+
+
+    /**
+     * Send an <code>AttributeChangeNotification</code> to all registered
+     * listeners.
+     *
+     * @param notification The <code>AttributeChangeNotification</code>
+     *  that will be passed
+     *
+     * @exception MBeanException if an object initializer throws an
+     *  exception
+     * @exception RuntimeOperationsException wraps IllegalArgumentException
+     *  when the specified notification is <code>null</code> or invalid
+     */
+    public void sendAttributeChangeNotification
+        (AttributeChangeNotification notification)
+        throws MBeanException, RuntimeOperationsException {
+
+        if (notification == null)
+            throw new RuntimeOperationsException
+                (new IllegalArgumentException("Notification is null"),
+                 "Notification is null");
+        if (attributeBroadcaster == null)
+            return; // This means there are no registered listeners
+        if( log.isDebugEnabled() )
+            log.debug( "AttributeChangeNotification " + notification );
+        attributeBroadcaster.sendNotification(notification);
+
+    }
+
+
+    /**
+     * Send an <code>AttributeChangeNotification</code> to all registered
+     * listeners.
+     *
+     * @param oldValue The original value of the <code>Attribute</code>
+     * @param newValue The new value of the <code>Attribute</code>
+     *
+     * @exception MBeanException if an object initializer throws an
+     *  exception
+     * @exception RuntimeOperationsException wraps IllegalArgumentException
+     *  when the specified notification is <code>null</code> or invalid
+     */
+    public void sendAttributeChangeNotification
+        (Attribute oldValue, Attribute newValue)
+        throws MBeanException, RuntimeOperationsException {
+
+        // Calculate the class name for the change notification
+        String type = null;
+        if (newValue.getValue() != null)
+            type = newValue.getValue().getClass().getName();
+        else if (oldValue.getValue() != null)
+            type = oldValue.getValue().getClass().getName();
+        else
+            return;  // Old and new are both null == no change
+
+        AttributeChangeNotification notification =
+            new AttributeChangeNotification
+            (this, 1, System.currentTimeMillis(),
+             "Attribute value has changed",
+             oldValue.getName(), type,
+             oldValue.getValue(), newValue.getValue());
+        sendAttributeChangeNotification(notification);
+
+    }
+
+
+    /**
+     * Send a <code>Notification</code> to all registered listeners as a
+     * <code>jmx.modelmbean.general</code> notification.
+     *
+     * @param notification The <code>Notification</code> that will be passed
+     *
+     * @exception MBeanException if an object initializer throws an
+     *  exception
+     * @exception RuntimeOperationsException wraps IllegalArgumentException
+     *  when the specified notification is <code>null</code> or invalid
+     */
+    public void sendNotification(Notification notification)
+        throws MBeanException, RuntimeOperationsException {
+
+        if (notification == null)
+            throw new RuntimeOperationsException
+                (new IllegalArgumentException("Notification is null"),
+                 "Notification is null");
+        if (generalBroadcaster == null)
+            return; // This means there are no registered listeners
+        generalBroadcaster.sendNotification(notification);
+
+    }
+
+
+    /**
+     * Send a <code>Notification</code> which contains the specified string
+     * as a <code>jmx.modelmbean.generic</code> notification.
+     *
+     * @param message The message string to be passed
+     *
+     * @exception MBeanException if an object initializer throws an
+     *  exception
+     * @exception RuntimeOperationsException wraps IllegalArgumentException
+     *  when the specified notification is <code>null</code> or invalid
+     */
+    public void sendNotification(String message)
+        throws MBeanException, RuntimeOperationsException {
+
+        if (message == null)
+            throw new RuntimeOperationsException
+                (new IllegalArgumentException("Message is null"),
+                 "Message is null");
+        Notification notification = new Notification
+            ("jmx.modelmbean.generic", this, 1, message);
+        sendNotification(notification);
+
+    }
+
+
+    // ---------------------------------------- NotificationBroadcaster Methods
+
+
+    /**
+     * Add a notification event listener to this MBean.
+     *
+     * @param listener Listener that will receive event notifications
+     * @param filter Filter object used to filter event notifications
+     *  actually delivered, or <code>null</code> for no filtering
+     * @param handback Handback object to be sent along with event
+     *  notifications
+     *
+     * @exception IllegalArgumentException if the listener parameter is null
+     */
+    public void addNotificationListener(NotificationListener listener,
+                                        NotificationFilter filter,
+                                        Object handback)
+        throws IllegalArgumentException {
+
+        if (listener == null)
+            throw new IllegalArgumentException("Listener is null");
+
+        if( log.isDebugEnabled() ) log.debug("addNotificationListener " + listener);
+
+        if (generalBroadcaster == null)
+            generalBroadcaster = new BaseNotificationBroadcaster();
+        generalBroadcaster.addNotificationListener
+            (listener, filter, handback);
+
+        // We'll send the attribute change notifications to all listeners ( who care )
+        // The normal filtering can be used.
+        // The problem is that there is no other way to add attribute change listeners
+        // to a model mbean ( AFAIK ). I suppose the spec should be fixed.
+        if (attributeBroadcaster == null)
+            attributeBroadcaster = new BaseNotificationBroadcaster();
+
+        if( log.isDebugEnabled() )
+            log.debug("addAttributeNotificationListener " + listener);
+
+        attributeBroadcaster.addNotificationListener
+                (listener, filter, handback);
+    }
+
+
+    /**
+     * Return an <code>MBeanNotificationInfo</code> object describing the
+     * notifications sent by this MBean.
+     */
+    public MBeanNotificationInfo[] getNotificationInfo() {
+
+        // Acquire the set of application notifications
+        MBeanNotificationInfo current[] = getMBeanInfo().getNotifications();
+        if (current == null)
+            current = new MBeanNotificationInfo[0];
+        MBeanNotificationInfo response[] =
+            new MBeanNotificationInfo[current.length + 2];
+ //       Descriptor descriptor = null;
+
+        // Fill in entry for general notifications
+//        descriptor = new DescriptorSupport
+//            (new String[] { "name=GENERIC",
+//                            "descriptorType=notification",
+//                            "log=T",
+//                            "severity=5",
+//                            "displayName=jmx.modelmbean.generic" });
+        response[0] = new MBeanNotificationInfo
+            (new String[] { "jmx.modelmbean.generic" },
+             "GENERIC",
+             "Text message notification from the managed resource");
+             //descriptor);
+
+        // Fill in entry for attribute change notifications
+//        descriptor = new DescriptorSupport
+//            (new String[] { "name=ATTRIBUTE_CHANGE",
+//                            "descriptorType=notification",
+//                            "log=T",
+//                            "severity=5",
+//                            "displayName=jmx.attribute.change" });
+        response[1] = new MBeanNotificationInfo
+            (new String[] { "jmx.attribute.change" },
+             "ATTRIBUTE_CHANGE",
+             "Observed MBean attribute value has changed");
+             //descriptor);
+
+        // Copy remaining notifications as reported by the application
+        System.arraycopy(current, 0, response, 2, current.length);
+        return (response);
+
+    }
+
+
+    /**
+     * Remove a notification event listener from this MBean.
+     *
+     * @param listener The listener to be removed (any and all registrations
+     *  for this listener will be eliminated)
+     *
+     * @exception ListenerNotFoundException if this listener is not
+     *  registered in the MBean
+     */
+    public void removeNotificationListener(NotificationListener listener)
+        throws ListenerNotFoundException {
+
+        if (listener == null)
+            throw new IllegalArgumentException("Listener is null");
+        if (generalBroadcaster == null)
+            generalBroadcaster = new BaseNotificationBroadcaster();
+        generalBroadcaster.removeNotificationListener(listener);
+
+
+    }
+
+
+    /**
+     * Remove a notification event listener from this MBean.
+     *
+     * @param listener The listener to be removed (any and all registrations
+     *  for this listener will be eliminated)
+     * @param handback Handback object to be sent along with event
+     *  notifications
+     *
+     * @exception ListenerNotFoundException if this listener is not
+     *  registered in the MBean
+     */
+    public void removeNotificationListener(NotificationListener listener,
+                                           Object handback)
+        throws ListenerNotFoundException {
+
+        removeNotificationListener(listener);
+
+    }
+
+
+    /**
+     * Remove a notification event listener from this MBean.
+     *
+     * @param listener The listener to be removed (any and all registrations
+     *  for this listener will be eliminated)
+     * @param filter Filter object used to filter event notifications
+     *  actually delivered, or <code>null</code> for no filtering
+     * @param handback Handback object to be sent along with event
+     *  notifications
+     *
+     * @exception ListenerNotFoundException if this listener is not
+     *  registered in the MBean
+     */
+    public void removeNotificationListener(NotificationListener listener,
+                                           NotificationFilter filter,
+                                           Object handback)
+        throws ListenerNotFoundException {
+
+        removeNotificationListener(listener);
+
+    }
+
+
+    // ------------------------------------------------ PersistentMBean Methods
+
+
+//    /**
+//     * Instantiates this MBean instance from data found in the persistent
+//     * store.  The data loaded could include attribute and operation values.
+//     * This method should be called during construction or initialization
+//     * of the instance, and before the MBean is registered with the
+//     * <code>MBeanServer</code>.
+//     *
+//     * <p><strong>IMPLEMENTATION NOTE</strong> - This implementation does
+//     * not support persistence.</p>
+//     *
+//     * @exception InstanceNotFoundException if the managed resource object
+//     *  cannot be found
+//     * @exception MBeanException if the initializer of the object throws
+//     *  an exception
+//     * @exception RuntimeOperationsException if an exception is reported
+//     *  by the persistence mechanism
+//     */
+//    public void load() throws InstanceNotFoundException,
+//        MBeanException, RuntimeOperationsException {
+//        // XXX If a context was set, use it to load the data
+//        throw new MBeanException
+//            (new IllegalStateException("Persistence is not supported"),
+//             "Persistence is not supported");
+//
+//    }
+
+
+//    /**
+//     * Capture the current state of this MBean instance and write it out
+//     * to the persistent store.  The state stored could include attribute
+//     * and operation values.  If one of these methods of persistence is not
+//     * supported, a "service not found" exception will be thrown.
+//     *
+//     * <p><strong>IMPLEMENTATION NOTE</strong> - This implementation does
+//     * not support persistence.</p>
+//     *
+//     * @exception InstanceNotFoundException if the managed resource object
+//     *  cannot be found
+//     * @exception MBeanException if the initializer of the object throws
+//     *  an exception, or persistence is not supported
+//     * @exception RuntimeOperationsException if an exception is reported
+//     *  by the persistence mechanism
+//     */
+//    public void store() throws InstanceNotFoundException,
+//        MBeanException, RuntimeOperationsException {
+//
+//        // XXX if a context was set, use it to store the data
+//        throw new MBeanException
+//            (new IllegalStateException("Persistence is not supported"),
+//             "Persistence is not supported");
+//
+//    }
+
+    // --------------------  BaseModelMBean methods --------------------
+
+//    /** Set the type of the mbean. This is used as a key to locate
+//     * the description in the Registry.
+//     *
+//     * @param type the type of classname of the modeled object
+//     */
+//    void setModeledType( String type ) {
+//        initModelInfo(type);
+//        createResource();
+//    }
+//    /** Set the type of the mbean. This is used as a key to locate
+//     * the description in the Registry.
+//     *
+//     * @param type the type of classname of the modeled object
+//     */
+//    void initModelInfo( String type ) {
+//        try {
+//            if( log.isDebugEnabled())
+//                log.debug("setModeledType " + type);
+//
+//            log.debug( "Set model Info " + type);
+//            if(type==null) {
+//                return;
+//            }
+//            resourceType=type;
+//            //Thread.currentThread().setContextClassLoader(BaseModelMBean.class.getClassLoader());
+//            Class c=null;
+//            try {
+//                c=Class.forName( type);
+//            } catch( Throwable t ) {
+//                log.debug( "Error creating class " + t);
+//            }
+//
+//            // The class c doesn't need to exist
+//            ManagedBean descriptor=getRegistry().findManagedBean(c, type);
+//            if( descriptor==null ) 
+//                return;
+//            this.setModelMBeanInfo(descriptor.createMBeanInfo());
+//        } catch( Throwable ex) {
+//            log.error( "TCL: " + Thread.currentThread().getContextClassLoader(),
+//                    ex);
+//        }
+//    }
+
+//    /** Set the type of the mbean. This is used as a key to locate
+//     * the description in the Registry.
+//     */
+//    protected void createResource() {
+//        try {
+//            //Thread.currentThread().setContextClassLoader(BaseModelMBean.class.getClassLoader());
+//            Class c=null;
+//            try {
+//                c=Class.forName( resourceType );
+//                resource = c.newInstance();
+//            } catch( Throwable t ) {
+//                log.error( "Error creating class " + t);
+//            }
+//        } catch( Throwable ex) {
+//            log.error( "TCL: " + Thread.currentThread().getContextClassLoader(),
+//                    ex);
+//        }
+//    }
+
+
+    public String getModelerType() {
+        return resourceType;
+    }
+
+    public String getClassName() {
+        return getModelerType();
+    }
+
+    public ObjectName getJmxName() {
+        return oname;
+    }
+
+    public String getObjectName() {
+        if (oname != null) {
+            return oname.toString();
+        } else {
+            return null;
+        }
+    }
+
+//    public void setRegistry(Registry registry) {
+//        this.registry = registry;
+//    }
+//
+//    public Registry getRegistry() {
+//        // XXX Need a better solution - to avoid the static
+//        if( registry == null )
+//            registry=Registry.getRegistry();
+//
+//        return registry;
+//    }
+
+    // ------------------------------------------------------ Protected Methods
+
+
+//    /**
+//     * Create and return a default <code>ModelMBeanInfo</code> object.
+//     */
+//    protected ModelMBeanInfo createDefaultModelMBeanInfo() {
+//
+//        return (new ModelMBeanInfoSupport(this.getClass().getName(),
+//                                          "Default ModelMBean",
+//                                          null, null, null, null));
+//
+//    }
+
+//    /**
+//     * Is the specified <code>ModelMBeanInfo</code> instance valid?
+//     *
+//     * <p><strong>IMPLEMENTATION NOTE</strong> - This implementation
+//     * does not check anything, but this method can be overridden
+//     * as required.</p>
+//     *
+//     * @param info The <code>ModelMBeanInfo object to check
+//     */
+//    protected boolean isModelMBeanInfoValid(ModelMBeanInfo info) {
+//        return (true);
+//    }
+
+    // -------------------- Registration  --------------------
+    // XXX We can add some method patterns here- like setName() and
+    // setDomain() for code that doesn't implement the Registration
+
+    public ObjectName preRegister(MBeanServer server,
+                                  ObjectName name)
+            throws Exception
+    {
+        if( log.isDebugEnabled())
+            log.debug("preRegister " + resource + " " + name );
+        oname=name;
+        if( resource instanceof MBeanRegistration ) {
+            oname = ((MBeanRegistration)resource).preRegister(server, name );
+        }
+        return oname;
+    }
+
+    public void postRegister(Boolean registrationDone) {
+        if( resource instanceof MBeanRegistration ) {
+            ((MBeanRegistration)resource).postRegister(registrationDone);
+        }
+    }
+
+    public void preDeregister() throws Exception {
+        if( resource instanceof MBeanRegistration ) {
+            ((MBeanRegistration)resource).preDeregister();
+        }
+    }
+
+    public void postDeregister() {
+        if( resource instanceof MBeanRegistration ) {
+            ((MBeanRegistration)resource).postDeregister();
+        }
+    }
+
+    static class MethodKey {
+        private String name;
+        private String[] signature;
+
+        MethodKey(String name, String[] signature) {
+            this.name = name;
+            if(signature == null) {
+                signature = new String[0];
+            }
+            this.signature = signature;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if(!(other instanceof MethodKey)) {
+                return false;
+            }
+            MethodKey omk = (MethodKey)other;
+            if(!name.equals(omk.name)) {
+                return false;
+            }
+            if(signature.length != omk.signature.length) {
+                return false;
+            }
+            for(int i=0; i < signature.length; i++) {
+                if(!signature[i].equals(omk.signature[i])) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            return name.hashCode();
+        }
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/BaseNotificationBroadcaster.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/BaseNotificationBroadcaster.java
new file mode 100644
index 0000000..d13436e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/BaseNotificationBroadcaster.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.tomcat.util.modeler;
+
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanNotificationInfo;
+import javax.management.Notification;
+import javax.management.NotificationBroadcaster;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+
+
+/**
+ * <p>Implementation of <code>NotificationBroadcaster</code> for attribute
+ * change notifications.  This class is used by <code>BaseModelMBean</code> to
+ * handle notifications of attribute change events to interested listeners.
+ *</p>
+ *
+ * @author Craig R. McClanahan
+ * @author Costin Manolache
+ */
+
+public class BaseNotificationBroadcaster implements NotificationBroadcaster {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The set of registered <code>BaseNotificationBroadcasterEntry</code>
+     * entries.
+     */
+    protected ArrayList<BaseNotificationBroadcasterEntry> entries =
+        new ArrayList<BaseNotificationBroadcasterEntry>();
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add a notification event listener to this MBean.
+     *
+     * @param listener Listener that will receive event notifications
+     * @param filter Filter object used to filter event notifications
+     *  actually delivered, or <code>null</code> for no filtering
+     * @param handback Handback object to be sent along with event
+     *  notifications
+     *
+     * @exception IllegalArgumentException if the listener parameter is null
+     */
+    public void addNotificationListener(NotificationListener listener,
+                                        NotificationFilter filter,
+                                        Object handback)
+        throws IllegalArgumentException {
+
+        synchronized (entries) {
+
+            // Optimization to coalesce attribute name filters
+            if (filter instanceof BaseAttributeFilter) {
+                BaseAttributeFilter newFilter = (BaseAttributeFilter) filter;
+                Iterator<BaseNotificationBroadcasterEntry> items =
+                    entries.iterator();
+                while (items.hasNext()) {
+                    BaseNotificationBroadcasterEntry item = items.next();
+                    if ((item.listener == listener) &&
+                        (item.filter != null) &&
+                        (item.filter instanceof BaseAttributeFilter) &&
+                        (item.handback == handback)) {
+                        BaseAttributeFilter oldFilter =
+                            (BaseAttributeFilter) item.filter;
+                        String newNames[] = newFilter.getNames();
+                        String oldNames[] = oldFilter.getNames();
+                        if (newNames.length == 0) {
+                            oldFilter.clear();
+                        } else {
+                            if (oldNames.length != 0) {
+                                for (int i = 0; i < newNames.length; i++)
+                                    oldFilter.addAttribute(newNames[i]);
+                            }
+                        }
+                        return;
+                    }
+                }
+            }
+
+            // General purpose addition of a new entry
+            entries.add(new BaseNotificationBroadcasterEntry
+                        (listener, filter, handback));
+        }
+
+    }
+
+
+    /**
+     * Return an <code>MBeanNotificationInfo</code> object describing the
+     * notifications sent by this MBean.
+     */
+    public MBeanNotificationInfo[] getNotificationInfo() {
+
+        return (new MBeanNotificationInfo[0]);
+
+    }
+
+
+    /**
+     * Remove a notification event listener from this MBean.
+     *
+     * @param listener The listener to be removed (any and all registrations
+     *  for this listener will be eliminated)
+     *
+     * @exception ListenerNotFoundException if this listener is not
+     *  registered in the MBean
+     */
+    public void removeNotificationListener(NotificationListener listener)
+        throws ListenerNotFoundException {
+
+        synchronized (entries) {
+            Iterator<BaseNotificationBroadcasterEntry> items =
+                entries.iterator();
+            while (items.hasNext()) {
+                BaseNotificationBroadcasterEntry item = items.next();
+                if (item.listener == listener)
+                    items.remove();
+            }
+        }
+
+    }
+
+
+    /**
+     * Remove a notification event listener from this MBean.
+     *
+     * @param listener The listener to be removed (any and all registrations
+     *  for this listener will be eliminated)
+     * @param handback Handback object to be sent along with event
+     *  notifications
+     *
+     * @exception ListenerNotFoundException if this listener is not
+     *  registered in the MBean
+     */
+    public void removeNotificationListener(NotificationListener listener,
+                                           Object handback)
+        throws ListenerNotFoundException {
+
+        removeNotificationListener(listener);
+
+    }
+
+
+    /**
+     * Remove a notification event listener from this MBean.
+     *
+     * @param listener The listener to be removed (any and all registrations
+     *  for this listener will be eliminated)
+     * @param filter Filter object used to filter event notifications
+     *  actually delivered, or <code>null</code> for no filtering
+     * @param handback Handback object to be sent along with event
+     *  notifications
+     *
+     * @exception ListenerNotFoundException if this listener is not
+     *  registered in the MBean
+     */
+    public void removeNotificationListener(NotificationListener listener,
+                                           NotificationFilter filter,
+                                           Object handback)
+        throws ListenerNotFoundException {
+
+        removeNotificationListener(listener);
+
+    }
+
+
+    /**
+     * Send the specified notification to all interested listeners.
+     *
+     * @param notification The notification to be sent
+     */
+    public void sendNotification(Notification notification) {
+
+        synchronized (entries) {
+            Iterator<BaseNotificationBroadcasterEntry> items =
+                entries.iterator();
+            while (items.hasNext()) {
+                BaseNotificationBroadcasterEntry item = items.next();
+                if ((item.filter != null) &&
+                    (!item.filter.isNotificationEnabled(notification)))
+                    continue;
+                item.listener.handleNotification(notification, item.handback);
+            }
+        }
+
+    }
+
+}
+
+
+/**
+ * Utility class representing a particular registered listener entry.
+ */
+
+class BaseNotificationBroadcasterEntry {
+
+    public BaseNotificationBroadcasterEntry(NotificationListener listener,
+                                            NotificationFilter filter,
+                                            Object handback) {
+        this.listener = listener;
+        this.filter = filter;
+        this.handback = handback;
+    }
+
+    public NotificationFilter filter = null;
+
+    public Object handback = null;
+
+    public NotificationListener listener = null;
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/ConstructorInfo.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/ConstructorInfo.java
new file mode 100644
index 0000000..9cb2714
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/ConstructorInfo.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.modeler;
+
+
+import javax.management.MBeanConstructorInfo;
+
+
+/**
+ * <p>Internal configuration information for a <code>Constructor</code>
+ * descriptor.</p>
+ *
+ * @author Craig R. McClanahan
+ */
+public class ConstructorInfo extends OperationInfo {
+    static final long serialVersionUID = -5735336213417238238L;
+    // ------------------------------------------------------------- Properties
+
+    public ConstructorInfo() {
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Create and return a <code>ModelMBeanConstructorInfo</code> object that
+     * corresponds to the attribute described by this instance.
+     */
+    public MBeanConstructorInfo createConstructorInfo() {
+        // Return our cached information (if any)
+        if (info == null) {
+            info = new MBeanConstructorInfo(getName(), getDescription(), 
+                    getMBeanParameterInfo());
+        }
+        return (MBeanConstructorInfo)info;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/FeatureInfo.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/FeatureInfo.java
new file mode 100644
index 0000000..829cdf9
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/FeatureInfo.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.tomcat.util.modeler;
+
+
+import java.io.Serializable;
+
+import javax.management.MBeanFeatureInfo;
+
+
+/**
+ * <p>Convenience base class for <code>AttributeInfo</code>,
+ * <code>ConstructorInfo</code>, and <code>OperationInfo</code> classes
+ * that will be used to collect configuration information for the
+ * <code>ModelMBean</code> beans exposed for management.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: FeatureInfo.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public class FeatureInfo implements Serializable {
+    static final long serialVersionUID = -911529176124712296L;
+    
+    protected String description = null;
+    protected String name = null;
+    protected MBeanFeatureInfo info = null;
+    
+    // all have type except Constructor
+    protected String type = null;
+
+    
+    // ------------------------------------------------------------- Properties
+
+    /**
+     * The human-readable description of this feature.
+     */
+    public String getDescription() {
+        return (this.description);
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+
+    /**
+     * The name of this feature, which must be unique among features in the
+     * same collection.
+     */
+    public String getName() {
+        return (this.name);
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * The fully qualified Java class name of this element.
+     */
+    public String getType() {
+        return (this.type);
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/FixedNotificationFilter.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/FixedNotificationFilter.java
new file mode 100644
index 0000000..882c85b
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/FixedNotificationFilter.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.util.modeler;
+
+
+import java.util.HashSet;
+
+import javax.management.Notification;
+import javax.management.NotificationFilter;
+
+
+/**
+ * Special NotificationFilter that allows modeler to optimize its notifications.
+ *
+ * This class is immutable - after you construct it it'll filter based on
+ * a fixed set of notification names.
+ *
+ * The JMX specification requires the filters to be called before the
+ * notifications are sent. We can call this filter well in advance, when
+ * the listener is added. Based on the result we can maintain separate
+ * channels for each notification - and reduce the overhead.
+ *
+ * @author Costin Manolache
+ */
+public class FixedNotificationFilter implements NotificationFilter {
+
+    private static final long serialVersionUID = 1L;
+    /**
+     * The set of attribute names that are accepted by this filter.  If this
+     * list is empty, all attribute names are accepted.
+     */
+    private HashSet<String> names = new HashSet<String>();
+    String namesA[]=null;
+
+    /**
+     * Construct a new filter that accepts only the specified notification
+     * names.
+     *
+     * @param names Names of the notification types
+     */
+    public FixedNotificationFilter(String names[]) {
+        super();
+    }
+
+    /**
+     * Return the set of names that are accepted by this filter.  If this
+     * filter accepts all attribute names, a zero length array will be
+     * returned.
+     */
+    public String[] getNames() {
+        synchronized (names) {
+            return names.toArray(new String[names.size()]);
+        }
+    }
+
+
+    /**
+     * <p>Test whether notification enabled for this event.
+     * Return true if:</p>
+     * <ul>
+     * <li>Either the set of accepted names is empty (implying that all
+     *     attribute names are of interest) or the set of accepted names
+     *     includes the name of the attribute in this notification</li>
+     * </ul>
+     */
+    public boolean isNotificationEnabled(Notification notification) {
+
+        if (notification == null)
+            return (false);
+        synchronized (names) {
+            if (names.size() < 1)
+                return (true);
+            else
+                return (names.contains(notification.getType()));
+        }
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/ManagedBean.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/ManagedBean.java
new file mode 100644
index 0000000..66188b4
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/ManagedBean.java
@@ -0,0 +1,632 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.management.AttributeNotFoundException;
+import javax.management.DynamicMBean;
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanConstructorInfo;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.ReflectionException;
+import javax.management.RuntimeOperationsException;
+import javax.management.ServiceNotFoundException;
+
+
+/**
+ * <p>Internal configuration information for a managed bean (MBean)
+ * descriptor.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: ManagedBean.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public class ManagedBean implements java.io.Serializable {
+
+    private static final long serialVersionUID = 1L;
+    
+    private static final String BASE_MBEAN = "org.apache.tomcat.util.modeler.BaseModelMBean";
+    // ----------------------------------------------------- Instance Variables
+    static final Object[] NO_ARGS_PARAM = new Object[0];
+    static final Class<?>[] NO_ARGS_PARAM_SIG = new Class[0];
+
+
+    /**
+     * The <code>ModelMBeanInfo</code> object that corresponds
+     * to this <code>ManagedBean</code> instance.
+     */
+    transient MBeanInfo info = null;
+
+    private Map<String,AttributeInfo> attributes =
+        new HashMap<String,AttributeInfo>();
+
+    private Map<String,OperationInfo> operations =
+        new HashMap<String,OperationInfo>();
+    
+    protected String className = BASE_MBEAN;
+    //protected ConstructorInfo constructors[] = new ConstructorInfo[0];
+    protected String description = null;
+    protected String domain = null;
+    protected String group = null;
+    protected String name = null;
+
+    //protected List fields = new ArrayList();
+    protected NotificationInfo notifications[] = new NotificationInfo[0];
+    protected String type = null;
+
+    /** Constructor. Will add default attributes. 
+     *  
+     */ 
+    public ManagedBean() {
+        AttributeInfo ai=new AttributeInfo();
+        ai.setName("modelerType");
+        ai.setDescription("Type of the modeled resource. Can be set only once");
+        ai.setType("java.lang.String");
+        ai.setWriteable(false);
+        addAttribute(ai);
+    }
+    
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * The collection of attributes for this MBean.
+     */
+    public AttributeInfo[] getAttributes() {
+        AttributeInfo result[] = new AttributeInfo[attributes.size()];
+        attributes.values().toArray(result);
+        return result;
+    }
+
+
+    /**
+     * The fully qualified name of the Java class of the MBean
+     * described by this descriptor.  If not specified, the standard JMX
+     * class (<code>javax.management.modelmbean.RequiredModeLMBean</code>)
+     * will be utilized.
+     */
+    public String getClassName() {
+        return (this.className);
+    }
+
+    public void setClassName(String className) {
+        this.className = className;
+        this.info = null;
+    }
+
+
+//    /**
+//     * The collection of constructors for this MBean.
+//     */
+//    public ConstructorInfo[] getConstructors() {
+//        return (this.constructors);
+//    }
+
+
+    /**
+     * The human-readable description of this MBean.
+     */
+    public String getDescription() {
+        return (this.description);
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+        this.info = null;
+    }
+
+
+    /**
+     * The (optional) <code>ObjectName</code> domain in which this MBean
+     * should be registered in the MBeanServer.
+     */
+    public String getDomain() {
+        return (this.domain);
+    }
+
+    public void setDomain(String domain) {
+        this.domain = domain;
+    }
+
+
+    /**
+     * <p>Return a <code>List</code> of the {@link FieldInfo} objects for
+     * the name/value pairs that should be
+     * added to the Descriptor created from this metadata.</p>
+     */
+//    public List getFields() {
+//        return (this.fields);
+//    }
+//
+
+    /**
+     * The (optional) group to which this MBean belongs.
+     */
+    public String getGroup() {
+        return (this.group);
+    }
+
+    public void setGroup(String group) {
+        this.group = group;
+    }
+
+
+    /**
+     * The name of this managed bean, which must be unique among all
+     * MBeans managed by a particular MBeans server.
+     */
+    public String getName() {
+        return (this.name);
+    }
+
+    public void setName(String name) {
+        this.name = name;
+        this.info = null;
+    }
+
+
+    /**
+     * The collection of notifications for this MBean.
+     */
+    public NotificationInfo[] getNotifications() {
+        return (this.notifications);
+    }
+
+
+    /**
+     * The collection of operations for this MBean.
+     */
+    public OperationInfo[] getOperations() {
+        OperationInfo[] result = new OperationInfo[operations.size()];
+        operations.values().toArray(result);
+        return result;
+    }
+
+
+    /**
+     * The fully qualified name of the Java class of the resource
+     * implementation class described by the managed bean described
+     * by this descriptor.
+     */
+    public String getType() {
+        return (this.type);
+    }
+
+    public void setType(String type) {
+        this.type = type;
+        this.info = null;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add a new attribute to the set of attributes for this MBean.
+     *
+     * @param attribute The new attribute descriptor
+     */
+    public void addAttribute(AttributeInfo attribute) {
+        attributes.put(attribute.getName(), attribute);
+    }
+
+
+    /**
+     * Add a new constructor to the set of constructors for this MBean.
+     *
+     * @param constructor The new constructor descriptor
+     */
+//    public void addConstructor(ConstructorInfo constructor) {
+//
+//        synchronized (constructors) {
+//            ConstructorInfo results[] =
+//                new ConstructorInfo[constructors.length + 1];
+//            System.arraycopy(constructors, 0, results, 0, constructors.length);
+//            results[constructors.length] = constructor;
+//            constructors = results;
+//            this.info = null;
+//        }
+//
+//    }
+
+
+    /**
+     * <p>Add a new field to the fields associated with the
+     * Descriptor that will be created from this metadata.</p>
+     *
+     * @param field The field to be added
+     */
+//    public void addField(FieldInfo field) {
+//        fields.add(field);
+//    }
+
+
+    /**
+     * Add a new notification to the set of notifications for this MBean.
+     *
+     * @param notification The new notification descriptor
+     */
+    public void addNotification(NotificationInfo notification) {
+
+        synchronized (notifications) {
+            NotificationInfo results[] =
+                new NotificationInfo[notifications.length + 1];
+            System.arraycopy(notifications, 0, results, 0,
+                             notifications.length);
+            results[notifications.length] = notification;
+            notifications = results;
+            this.info = null;
+        }
+
+    }
+
+
+    /**
+     * Add a new operation to the set of operations for this MBean.
+     *
+     * @param operation The new operation descriptor
+     */
+    public void addOperation(OperationInfo operation) {
+        operations.put(operation.getName(), operation);
+    }
+
+
+    /**
+     * Create and return a <code>ModelMBean</code> that has been
+     * preconfigured with the <code>ModelMBeanInfo</code> information
+     * for this managed bean, but is not associated with any particular
+     * managed resource.  The returned <code>ModelMBean</code> will
+     * <strong>NOT</strong> have been registered with our
+     * <code>MBeanServer</code>.
+     *
+     * @exception InstanceNotFoundException if the managed resource
+     *  object cannot be found
+     * @exception javax.management.modelmbean.InvalidTargetObjectTypeException
+     *  if our MBean cannot handle object references (should never happen)
+     * @exception MBeanException if a problem occurs instantiating the
+     *  <code>ModelMBean</code> instance
+     * @exception RuntimeOperationsException if a JMX runtime error occurs
+     */
+    public DynamicMBean createMBean()
+        throws InstanceNotFoundException,
+        MBeanException, RuntimeOperationsException {
+
+        return (createMBean(null));
+
+    }
+
+
+    /**
+     * Create and return a <code>ModelMBean</code> that has been
+     * preconfigured with the <code>ModelMBeanInfo</code> information
+     * for this managed bean, and is associated with the specified
+     * managed object instance.  The returned <code>ModelMBean</code>
+     * will <strong>NOT</strong> have been registered with our
+     * <code>MBeanServer</code>.
+     *
+     * @param instance Instanced of the managed object, or <code>null</code>
+     *  for no associated instance
+     *
+     * @exception InstanceNotFoundException if the managed resource
+     *  object cannot be found
+     * @exception javax.management.modelmbean.InvalidTargetObjectTypeException
+     *  if our MBean cannot handle object references (should never happen)
+     * @exception MBeanException if a problem occurs instantiating the
+     *  <code>ModelMBean</code> instance
+     * @exception RuntimeOperationsException if a JMX runtime error occurs
+     */
+    public DynamicMBean createMBean(Object instance)
+        throws InstanceNotFoundException,
+        MBeanException, RuntimeOperationsException {
+
+        BaseModelMBean mbean = null;
+
+        // Load the ModelMBean implementation class
+        if(getClassName().equals(BASE_MBEAN)) {
+            // Skip introspection
+            mbean = new BaseModelMBean();
+        } else {
+            Class<?> clazz = null;
+            Exception ex = null;
+            try {
+                clazz = Class.forName(getClassName());
+            } catch (Exception e) {
+            }
+          
+            if( clazz==null ) {  
+                try {
+                    ClassLoader cl= Thread.currentThread().getContextClassLoader();
+                    if ( cl != null)
+                        clazz= cl.loadClass(getClassName());
+                } catch (Exception e) {
+                    ex=e;
+                }
+            }
+    
+            if( clazz==null) { 
+                throw new MBeanException
+                    (ex, "Cannot load ModelMBean class " + getClassName());
+            }
+            try {
+                // Stupid - this will set the default minfo first....
+                mbean = (BaseModelMBean) clazz.newInstance();
+            } catch (RuntimeOperationsException e) {
+                throw e;
+            } catch (Exception e) {
+                throw new MBeanException
+                    (e, "Cannot instantiate ModelMBean of class " +
+                     getClassName());
+            }
+        }
+        
+        mbean.setManagedBean(this);
+        
+        // Set the managed resource (if any)
+        try {
+            if (instance != null)
+                mbean.setManagedResource(instance, "ObjectReference");
+        } catch (InstanceNotFoundException e) {
+            throw e;
+        }
+        return (mbean);
+
+    }
+
+
+    /**
+     * Create and return a <code>ModelMBeanInfo</code> object that
+     * describes this entire managed bean.
+     */
+    MBeanInfo getMBeanInfo() {
+
+        // Return our cached information (if any)
+        if (info != null)
+            return (info);
+
+        // Create subordinate information descriptors as required
+        AttributeInfo attrs[] = getAttributes();
+        MBeanAttributeInfo attributes[] =
+            new MBeanAttributeInfo[attrs.length];
+        for (int i = 0; i < attrs.length; i++)
+            attributes[i] = attrs[i].createAttributeInfo();
+
+        OperationInfo opers[] = getOperations();
+        MBeanOperationInfo operations[] =
+            new MBeanOperationInfo[opers.length];
+        for (int i = 0; i < opers.length; i++)
+            operations[i] = opers[i].createOperationInfo();
+
+
+//        ConstructorInfo consts[] = getConstructors();
+//        ModelMBeanConstructorInfo constructors[] =
+//            new ModelMBeanConstructorInfo[consts.length];
+//        for (int i = 0; i < consts.length; i++)
+//            constructors[i] = consts[i].createConstructorInfo();
+        
+        NotificationInfo notifs[] = getNotifications();
+        MBeanNotificationInfo notifications[] =
+            new MBeanNotificationInfo[notifs.length];
+        for (int i = 0; i < notifs.length; i++)
+            notifications[i] = notifs[i].createNotificationInfo();
+
+        
+        // Construct and return a new ModelMBeanInfo object
+        info = new MBeanInfo(getClassName(), 
+                             getDescription(),
+                             attributes, 
+                             new MBeanConstructorInfo[] {}, 
+                             operations, 
+                             notifications);
+//        try {
+//            Descriptor descriptor = info.getMBeanDescriptor();
+//            Iterator fields = getFields().iterator();
+//            while (fields.hasNext()) {
+//                FieldInfo field = (FieldInfo) fields.next();
+//                descriptor.setField(field.getName(), field.getValue());
+//            }
+//            info.setMBeanDescriptor(descriptor);
+//        } catch (MBeanException e) {
+//            ;
+//        }
+
+        return (info);
+
+    }
+
+
+    /**
+     * Return a string representation of this managed bean.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("ManagedBean[");
+        sb.append("name=");
+        sb.append(name);
+        sb.append(", className=");
+        sb.append(className);
+        sb.append(", description=");
+        sb.append(description);
+        if (group != null) {
+            sb.append(", group=");
+            sb.append(group);
+        }
+        sb.append(", type=");
+        sb.append(type);
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+    Method getGetter(String aname, BaseModelMBean mbean, Object resource) 
+            throws AttributeNotFoundException, MBeanException, ReflectionException {
+        // TODO: do we need caching ? JMX is for management, it's not supposed to require lots of performance.
+        Method m=null; // (Method)getAttMap.get( name );
+
+        if( m==null ) {
+            AttributeInfo attrInfo = attributes.get(aname);
+            // Look up the actual operation to be used
+            if (attrInfo == null)
+                throw new AttributeNotFoundException(" Cannot find attribute " + aname + " for " + resource);
+            
+            String getMethod = attrInfo.getGetMethod();
+            if (getMethod == null)
+                throw new AttributeNotFoundException("Cannot find attribute " + aname + " get method name");
+
+            Object object = null;
+            NoSuchMethodException exception = null;
+            try {
+                object = mbean;
+                m = object.getClass().getMethod(getMethod, NO_ARGS_PARAM_SIG);
+            } catch (NoSuchMethodException e) {
+                exception = e;
+            }
+            if( m== null && resource != null ) {
+                try {
+                    object = resource;
+                    m = object.getClass().getMethod(getMethod, NO_ARGS_PARAM_SIG);
+                    exception=null;
+                } catch (NoSuchMethodException e) {
+                    exception = e;
+                }
+            }
+            if( exception != null )
+                throw new ReflectionException(exception,
+                                              "Cannot find getter method " + getMethod);
+            //getAttMap.put( name, m );
+        }
+
+        return m;
+    }
+
+    public Method getSetter(String aname, BaseModelMBean bean, Object resource) 
+            throws AttributeNotFoundException, MBeanException, ReflectionException {
+        // Cache may be needed for getters, but it is a really bad idea for setters, this is far 
+        // less frequent.
+        Method m=null;//(Method)setAttMap.get( name );
+
+        if( m==null ) {
+            AttributeInfo attrInfo = attributes.get(aname);
+            if (attrInfo == null)
+                throw new AttributeNotFoundException(" Cannot find attribute " + aname);
+
+            // Look up the actual operation to be used
+            String setMethod = attrInfo.getSetMethod();
+            if (setMethod == null)
+                throw new AttributeNotFoundException("Cannot find attribute " + aname + " set method name");
+
+            String argType=attrInfo.getType();
+
+            Class<?> signature[] =
+                new Class[] { BaseModelMBean.getAttributeClass( argType ) };
+
+            Object object = null;
+            NoSuchMethodException exception = null;
+            try {
+                object = bean;
+                m = object.getClass().getMethod(setMethod, signature);
+            } catch (NoSuchMethodException e) {
+                exception = e;
+            }
+            if( m== null && resource != null ) {
+                try {
+                    object = resource;
+                    m = object.getClass().getMethod(setMethod, signature);
+                    exception=null;
+                } catch (NoSuchMethodException e) {
+                    exception = e;
+                }
+            }
+            if( exception != null )
+                throw new ReflectionException(exception,
+                                              "Cannot find setter method " + setMethod +
+                        " " + resource);
+            //setAttMap.put( name, m );
+        }
+
+        return m;
+    }
+
+    public Method getInvoke(String aname, Object[] params, String[] signature, BaseModelMBean bean, Object resource) 
+            throws MBeanException, ReflectionException {
+        Method method = null;
+        if (method == null) {
+            if (params == null)
+                params = new Object[0];
+            if (signature == null)
+                signature = new String[0];
+            if (params.length != signature.length)
+                throw new RuntimeOperationsException(
+                        new IllegalArgumentException(
+                                "Inconsistent arguments and signature"),
+                        "Inconsistent arguments and signature");
+
+            // Acquire the ModelMBeanOperationInfo information for
+            // the requested operation
+            OperationInfo opInfo = operations.get(aname);
+            if (opInfo == null)
+                throw new MBeanException(new ServiceNotFoundException(
+                        "Cannot find operation " + aname),
+                        "Cannot find operation " + aname);
+
+            // Prepare the signature required by Java reflection APIs
+            // FIXME - should we use the signature from opInfo?
+            Class<?> types[] = new Class[signature.length];
+            for (int i = 0; i < signature.length; i++) {
+                types[i] = BaseModelMBean.getAttributeClass(signature[i]);
+            }
+
+            // Locate the method to be invoked, either in this MBean itself
+            // or in the corresponding managed resource
+            // FIXME - Accessible methods in superinterfaces?
+            Object object = null;
+            Exception exception = null;
+            try {
+                object = bean;
+                method = object.getClass().getMethod(aname, types);
+            } catch (NoSuchMethodException e) {
+                exception = e;
+            }
+            try {
+                if ((method == null) && (resource != null)) {
+                    object = resource;
+                    method = object.getClass().getMethod(aname, types);
+                }
+            } catch (NoSuchMethodException e) {
+                exception = e;
+            }
+            if (method == null) {
+                throw new ReflectionException(exception, "Cannot find method "
+                        + aname + " with this signature");
+            }
+            // invokeAttMap.put(mkey, method);
+        }
+        return method;
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/NotificationInfo.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/NotificationInfo.java
new file mode 100644
index 0000000..c9f0d52
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/NotificationInfo.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.tomcat.util.modeler;
+
+
+import javax.management.MBeanNotificationInfo;
+
+
+/**
+ * <p>Internal configuration information for a <code>Notification</code>
+ * descriptor.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: NotificationInfo.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public class NotificationInfo extends FeatureInfo {
+    static final long serialVersionUID = -6319885418912650856L;
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The <code>ModelMBeanNotificationInfo</code> object that corresponds
+     * to this <code>NotificationInfo</code> instance.
+     */
+    transient MBeanNotificationInfo info = null;
+    protected String notifTypes[] = new String[0];
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Override the <code>description</code> property setter.
+     *
+     * @param description The new description
+     */
+    @Override
+    public void setDescription(String description) {
+        super.setDescription(description);
+        this.info = null;
+    }
+
+
+    /**
+     * Override the <code>name</code> property setter.
+     *
+     * @param name The new name
+     */
+    @Override
+    public void setName(String name) {
+        super.setName(name);
+        this.info = null;
+    }
+
+
+    /**
+     * The set of notification types for this MBean.
+     */
+    public String[] getNotifTypes() {
+        return (this.notifTypes);
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add a new notification type to the set managed by an MBean.
+     *
+     * @param notifType The new notification type
+     */
+    public void addNotifType(String notifType) {
+
+        synchronized (notifTypes) {
+            String results[] = new String[notifTypes.length + 1];
+            System.arraycopy(notifTypes, 0, results, 0, notifTypes.length);
+            results[notifTypes.length] = notifType;
+            notifTypes = results;
+            this.info = null;
+        }
+
+    }
+
+
+    /**
+     * Create and return a <code>ModelMBeanNotificationInfo</code> object that
+     * corresponds to the attribute described by this instance.
+     */
+    public MBeanNotificationInfo createNotificationInfo() {
+
+        // Return our cached information (if any)
+        if (info != null)
+            return (info);
+
+        // Create and return a new information object
+        info = new MBeanNotificationInfo
+            (getNotifTypes(), getName(), getDescription());
+        //Descriptor descriptor = info.getDescriptor();
+        //addFields(descriptor);
+        //info.setDescriptor(descriptor);
+        return (info);
+
+    }
+
+
+    /**
+     * Return a string representation of this notification descriptor.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder("NotificationInfo[");
+        sb.append("name=");
+        sb.append(name);
+        sb.append(", description=");
+        sb.append(description);
+        sb.append(", notifTypes=");
+        sb.append(notifTypes.length);
+        sb.append("]");
+        return (sb.toString());
+
+    }
+
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/OperationInfo.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/OperationInfo.java
new file mode 100644
index 0000000..3796471
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/OperationInfo.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+
+import java.util.Locale;
+
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanParameterInfo;
+
+
+/**
+ * <p>Internal configuration information for an <code>Operation</code>
+ * descriptor.</p>
+ *
+ * @author Craig R. McClanahan
+ */
+public class OperationInfo extends FeatureInfo {
+    static final long serialVersionUID = 4418342922072614875L;
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Standard zero-arguments constructor.
+     */
+    public OperationInfo() {
+
+        super();
+
+    }
+   
+    // ----------------------------------------------------- Instance Variables
+
+    protected String impact = "UNKNOWN";
+    protected String role = "operation";
+    protected ParameterInfo parameters[] = new ParameterInfo[0];
+
+
+    // ------------------------------------------------------------- Properties
+
+    /**
+     * The "impact" of this operation, which should be a (case-insensitive)
+     * string value "ACTION", "ACTION_INFO", "INFO", or "UNKNOWN".
+     */
+    public String getImpact() {
+        return (this.impact);
+    }
+
+    public void setImpact(String impact) {
+        if (impact == null)
+            this.impact = null;
+        else
+            this.impact = impact.toUpperCase(Locale.ENGLISH);
+    }
+
+
+    /**
+     * The role of this operation ("getter", "setter", "operation", or
+     * "constructor").
+     */
+    public String getRole() {
+        return (this.role);
+    }
+
+    public void setRole(String role) {
+        this.role = role;
+    }
+
+
+    /**
+     * The fully qualified Java class name of the return type for this
+     * operation.
+     */
+    public String getReturnType() {
+        if(type == null) {
+            type = "void";
+        }
+        return type;
+    }
+
+    public void setReturnType(String returnType) {
+        this.type = returnType;
+    }
+
+    /**
+     * The set of parameters for this operation.
+     */
+    public ParameterInfo[] getSignature() {
+        return (this.parameters);
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Add a new parameter to the set of arguments for this operation.
+     *
+     * @param parameter The new parameter descriptor
+     */
+    public void addParameter(ParameterInfo parameter) {
+
+        synchronized (parameters) {
+            ParameterInfo results[] = new ParameterInfo[parameters.length + 1];
+            System.arraycopy(parameters, 0, results, 0, parameters.length);
+            results[parameters.length] = parameter;
+            parameters = results;
+            this.info = null;
+        }
+
+    }
+
+
+    /**
+     * Create and return a <code>ModelMBeanOperationInfo</code> object that
+     * corresponds to the attribute described by this instance.
+     */
+    MBeanOperationInfo createOperationInfo() {
+
+        // Return our cached information (if any)
+        if (info == null) {
+            // Create and return a new information object
+            int impact = MBeanOperationInfo.UNKNOWN;
+            if ("ACTION".equals(getImpact()))
+                impact = MBeanOperationInfo.ACTION;
+            else if ("ACTION_INFO".equals(getImpact()))
+                impact = MBeanOperationInfo.ACTION_INFO;
+            else if ("INFO".equals(getImpact()))
+                impact = MBeanOperationInfo.INFO;
+    
+            info = new MBeanOperationInfo(getName(), getDescription(), 
+                                          getMBeanParameterInfo(),
+                                          getReturnType(), impact);
+        }
+        return (MBeanOperationInfo)info;
+    }
+
+    protected MBeanParameterInfo[] getMBeanParameterInfo() {
+        ParameterInfo params[] = getSignature();
+        MBeanParameterInfo parameters[] =
+            new MBeanParameterInfo[params.length];
+        for (int i = 0; i < params.length; i++)
+            parameters[i] = params[i].createParameterInfo();
+        return parameters;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/ParameterInfo.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/ParameterInfo.java
new file mode 100644
index 0000000..f4d4399
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/ParameterInfo.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.modeler;
+
+
+import javax.management.MBeanParameterInfo;
+
+
+/**
+ * <p>Internal configuration information for a <code>Parameter</code>
+ * descriptor.</p>
+ *
+ * @author Craig R. McClanahan
+ * @version $Id: ParameterInfo.java,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+ */
+
+public class ParameterInfo extends FeatureInfo {
+    static final long serialVersionUID = 2222796006787664020L;
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Standard zero-arguments constructor.
+     */
+    public ParameterInfo() {
+        super();
+    }
+
+    /**
+     * Create and return a <code>MBeanParameterInfo</code> object that
+     * corresponds to the parameter described by this instance.
+     */
+    public MBeanParameterInfo createParameterInfo() {
+
+        // Return our cached information (if any)
+        if (info == null) {
+            info = new MBeanParameterInfo
+                (getName(), getType(), getDescription());
+        }
+        return (MBeanParameterInfo)info;
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/Registry.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/Registry.java
new file mode 100644
index 0000000..b0d4856
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/Registry.java
@@ -0,0 +1,902 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.lang.management.ManagementFactory;
+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 javax.management.DynamicMBean;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanRegistration;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.modeler.modules.ModelerSource;
+
+/*
+   Issues:
+   - exceptions - too many "throws Exception"
+   - double check the interfaces 
+   - start removing the use of the experimental methods in tomcat, then remove
+     the methods ( before 1.1 final )
+   - is the security enough to prevent Registry beeing used to avoid the permission
+    checks in the mbean server ?
+*/ 
+
+/**
+ * Registry for modeler MBeans. 
+ *
+ * This is the main entry point into modeler. It provides methods to create
+ * and manipulate model mbeans and simplify their use.
+ *
+ * Starting with version 1.1, this is no longer a singleton and the static
+ * methods are strongly deprecated. In a container environment we can expect
+ * different applications to use different registries.
+ * 
+ * This class is itself an mbean.
+ * 
+ * IMPORTANT: public methods not marked with @since x.x are experimental or 
+ * internal. Should not be used.  
+ * 
+ * @author Craig R. McClanahan
+ * @author Costin Manolache
+ */
+public class Registry implements RegistryMBean, MBeanRegistration  {
+    /**
+     * The Log instance to which we will write our log messages.
+     */
+    private static final Log log = LogFactory.getLog(Registry.class);
+
+    // Support for the factory methods
+    
+    /** Will be used to isolate different apps and enhance security.
+     */
+    private static HashMap<Object,Registry> perLoaderRegistries = null;
+
+    /**
+     * The registry instance created by our factory method the first time
+     * it is called.
+     */
+    private static Registry registry = null;
+
+    // Per registy fields
+    
+    /**
+     * The <code>MBeanServer</code> instance that we will use to register
+     * management beans.
+     */
+    private MBeanServer server = null;
+
+    /**
+     * The set of ManagedBean instances for the beans this registry
+     * knows about, keyed by name.
+     */
+    private HashMap<String,ManagedBean> descriptors =
+        new HashMap<String,ManagedBean>();
+
+    /** List of managed beans, keyed by class name
+     */
+    private HashMap<String,ManagedBean> descriptorsByClass =
+        new HashMap<String,ManagedBean>();
+
+    // map to avoid duplicated searching or loading descriptors 
+    private HashMap<String,URL> searchedPaths=new HashMap<String,URL>();
+    
+    private Object guard;
+
+    // Id - small ints to use array access. No reset on stop()
+    // Used for notifications
+    private Hashtable<String,Hashtable<String,Integer>> idDomains =
+        new Hashtable<String,Hashtable<String,Integer>>();
+    private Hashtable<String,int[]> ids = new Hashtable<String,int[]>();
+
+    
+    // ----------------------------------------------------------- Constructors
+
+    /**
+     */
+     public Registry() {
+        super();
+    }
+
+    // -------------------- Static methods  --------------------
+    // Factories
+    
+    /**
+     * Factory method to create (if necessary) and return our
+     * <code>Registry</code> instance.
+     *
+     * Use this method to obtain a Registry - all other static methods
+     * are deprecated and shouldn't be used.
+     *
+     * The current version uses a static - future versions could use
+     * the thread class loader.
+     * 
+     * @param key Support for application isolation. If null, the context class
+     * loader will be used ( if setUseContextClassLoader is called ) or the 
+     * default registry is returned. 
+     * @param guard Prevent access to the registry by untrusted components
+     *
+     * @since 1.1
+     */
+    public static synchronized Registry getRegistry(Object key, Object guard) {
+        Registry localRegistry;
+        if( perLoaderRegistries!=null ) {
+            if( key==null ) 
+                key=Thread.currentThread().getContextClassLoader();
+            if( key != null ) {
+                localRegistry = perLoaderRegistries.get(key);
+                if( localRegistry == null ) {
+                    localRegistry=new Registry();
+//                    localRegistry.key=key;
+                    localRegistry.guard=guard;
+                    perLoaderRegistries.put( key, localRegistry );
+                    return localRegistry;
+                }
+                if( localRegistry.guard != null &&
+                        localRegistry.guard != guard ) {
+                    return null; // XXX Should I throw a permission ex ? 
+                }
+                return localRegistry;
+            }
+        }
+
+        // static 
+        if (registry == null) {
+            registry = new Registry();
+        }
+        if( registry.guard != null &&
+                registry.guard != guard ) {
+            return null;
+        }
+        return (registry);
+    }
+    
+    /** 
+     * Allow containers to isolate apps. Can be called only once.
+     * It  is highly recommended you call this method if using Registry in
+     * a container environment. The default is false for backward compatibility
+     * 
+     * @param enable
+     * @since 1.1
+     */
+    public static void setUseContextClassLoader( boolean enable ) {
+        if( enable ) {
+            perLoaderRegistries = new HashMap<Object,Registry>();
+        }
+    }
+    
+    // -------------------- Generic methods  --------------------
+
+    /** Lifecycle method - clean up the registry metadata.
+     *  Called from resetMetadata().
+     * 
+     * @since 1.1
+     */ 
+    public void stop() {
+        descriptorsByClass = new HashMap<String,ManagedBean>();
+        descriptors = new HashMap<String,ManagedBean>();
+        searchedPaths=new HashMap<String,URL>();
+    }
+    
+    /** 
+     * Load an extended mlet file. The source can be an URL, File or
+     * InputStream. 
+     * 
+     * All mbeans will be instantiated, registered and the attributes will be 
+     * set. The result is a list of ObjectNames.
+     *
+     * @param source InputStream or URL of the file
+     * @param cl ClassLoader to be used to load the mbeans, or null to use the
+     *        default JMX mechanism ( i.e. all registered loaders )
+     * @return List of ObjectName for the loaded mbeans
+     * @throws Exception
+     * 
+     * @since 1.1
+     */ 
+    public List<ObjectName> loadMBeans( Object source, ClassLoader cl )
+            throws Exception
+    {
+        return load("MbeansSource", source, null );
+    }    
+
+
+    /** Load descriptors. The source can be a File or URL or InputStream for the 
+     * descriptors file. In the case of File and URL, if the extension is ".ser"
+     * a serialized version will be loaded. 
+     * 
+     * This method should be used to explicitly load metadata - but this is not
+     * required in most cases. The registerComponent() method will find metadata
+     * in the same package.
+     * 
+     * @param source
+     */ 
+    public void loadMetadata(Object source ) throws Exception {
+        loadDescriptors( null, source, null );
+    }
+
+    /** Register a bean by creating a modeler mbean and adding it to the 
+     * MBeanServer.
+     * 
+     * If metadata is not loaded, we'll look up and read a file named
+     * "mbeans-descriptors.ser" or "mbeans-descriptors.xml" in the same package
+     * or parent.
+     *
+     * If the bean is an instance of DynamicMBean. it's metadata will be converted
+     * to a model mbean and we'll wrap it - so modeler services will be supported
+     *
+     * If the metadata is still not found, introspection will be used to extract
+     * it automatically. 
+     * 
+     * If an mbean is already registered under this name, it'll be first
+     * unregistered.
+     * 
+     * If the component implements MBeanRegistration, the methods will be called.
+     * If the method has a method "setRegistry" that takes a RegistryMBean as
+     * parameter, it'll be called with the current registry.
+     * 
+     *
+     * @param bean Object to be registered
+     * @param oname Name used for registration
+     * @param type The type of the mbean, as declared in mbeans-descriptors. If
+     * null, the name of the class will be used. This can be used as a hint or
+     * by subclasses.
+     *
+     * @since 1.1
+     */ 
+    public void registerComponent(Object bean, String oname, String type)
+           throws Exception
+    {
+        registerComponent(bean, new ObjectName(oname), type);        
+    }    
+
+    /** Unregister a component. We'll first check if it is registered,
+     * and mask all errors. This is mostly a helper.
+     * 
+     * @param oname
+     * 
+     * @since 1.1
+     */ 
+    public void unregisterComponent( String oname ) {
+        try {
+            unregisterComponent(new ObjectName(oname));
+        } catch (MalformedObjectNameException e) {
+            log.info("Error creating object name " + e );
+        }
+    }    
+    
+
+    /** Invoke a operation on a list of mbeans. Can be used to implement
+     * lifecycle operations.
+     *
+     * @param mbeans list of ObjectName on which we'll invoke the operations
+     * @param operation  Name of the operation ( init, start, stop, etc)
+     * @param failFirst  If false, exceptions will be ignored
+     * @throws Exception
+     * @since 1.1
+     */
+    public void invoke(List<ObjectName> mbeans, String operation,
+            boolean failFirst ) throws Exception {
+        if( mbeans==null ) {
+            return;
+        }
+        Iterator<ObjectName> itr = mbeans.iterator();
+        while(itr.hasNext()) {
+            ObjectName current = itr.next();
+            try {
+                if(current == null) {
+                    continue;
+                }
+                if(getMethodInfo(current, operation) == null) {
+                    continue;
+                }
+                getMBeanServer().invoke(current, operation,
+                        new Object[] {}, new String[] {});
+
+            } catch( Exception t ) {
+                if( failFirst ) throw t;
+                log.info("Error initializing " + current + " " + t.toString());
+            }
+        }
+    }
+
+    // -------------------- ID registry --------------------
+
+    /** Return an int ID for faster access. Will be used for notifications
+     * and for other operations we want to optimize. 
+     *
+     * @param domain Namespace 
+     * @param name  Type of the notification
+     * @return  An unique id for the domain:name combination
+     * @since 1.1
+     */
+    public synchronized int getId( String domain, String name) {
+        if( domain==null) {
+            domain="";
+        }
+        Hashtable<String,Integer> domainTable = idDomains.get(domain);
+        if( domainTable == null ) {
+            domainTable = new Hashtable<String,Integer>();
+            idDomains.put( domain, domainTable); 
+        }
+        if( name==null ) {
+            name="";
+        }
+        Integer i = domainTable.get(name);
+        
+        if( i!= null ) {
+            return i.intValue();
+        }
+
+        int id[] = ids.get(domain);
+        if( id == null ) {
+            id=new int[1];
+            ids.put( domain, id); 
+        }
+        int code=id[0]++;
+        domainTable.put( name, new Integer( code ));
+        return code;
+    }
+    
+    // -------------------- Metadata   --------------------
+    // methods from 1.0
+
+    /**
+     * Add a new bean metadata to the set of beans known to this registry.
+     * This is used by internal components.
+     *
+     * @param bean The managed bean to be added
+     * @since 1.0
+     */
+    public void addManagedBean(ManagedBean bean) {
+        // XXX Use group + name
+        descriptors.put(bean.getName(), bean);
+        if( bean.getType() != null ) {
+            descriptorsByClass.put( bean.getType(), bean );
+        }
+    }
+
+
+    /**
+     * Find and return the managed bean definition for the specified
+     * bean name, if any; otherwise return <code>null</code>.
+     *
+     * @param name Name of the managed bean to be returned. Since 1.1, both
+     *   short names or the full name of the class can be used.
+     * @since 1.0
+     */
+    public ManagedBean findManagedBean(String name) {
+        // XXX Group ?? Use Group + Type
+        ManagedBean mb = descriptors.get(name);
+        if( mb==null )
+            mb = descriptorsByClass.get(name);
+        return mb;
+    }
+    
+    /**
+     * Return the set of bean names for all managed beans known to
+     * this registry.
+     *
+     * @since 1.0
+     */
+    public String[] findManagedBeans() {
+        return descriptors.keySet().toArray(new String[0]);
+    }
+
+
+    /**
+     * Return the set of bean names for all managed beans known to
+     * this registry that belong to the specified group.
+     *
+     * @param group Name of the group of interest, or <code>null</code>
+     *  to select beans that do <em>not</em> belong to a group
+     * @since 1.0
+     */
+    public String[] findManagedBeans(String group) {
+
+        ArrayList<String> results = new ArrayList<String>();
+        Iterator<ManagedBean> items = descriptors.values().iterator();
+        while (items.hasNext()) {
+            ManagedBean item = items.next();
+            if ((group == null)) {
+                if (item.getGroup() == null){
+                    results.add(item.getName());
+                }
+            } else if (group.equals(item.getGroup())) {
+                results.add(item.getName());
+            }
+        }
+        String values[] = new String[results.size()];
+        return results.toArray(values);
+
+    }
+
+
+    /**
+     * Remove an existing bean from the set of beans known to this registry.
+     *
+     * @param bean The managed bean to be removed
+     * @since 1.0
+     */
+    public void removeManagedBean(ManagedBean bean) {
+       // TODO: change this to use group/name
+        descriptors.remove(bean.getName());
+        descriptorsByClass.remove( bean.getType());
+    }
+
+    // -------------------- Helpers  --------------------
+
+    /** Get the type of an attribute of the object, from the metadata.
+     *
+     * @param oname
+     * @param attName
+     * @return null if metadata about the attribute is not found
+     * @since 1.1
+     */
+    public String getType( ObjectName oname, String attName )
+    {
+        String type=null;
+        MBeanInfo info=null;
+        try {
+            info=server.getMBeanInfo(oname);
+        } catch (Exception e) {
+            log.info( "Can't find metadata for object" + oname );
+            return null;
+        }
+
+        MBeanAttributeInfo attInfo[]=info.getAttributes();
+        for( int i=0; i<attInfo.length; i++ ) {
+            if( attName.equals(attInfo[i].getName())) {
+                type=attInfo[i].getType();
+                return type;
+            }
+        }
+        return null;
+    }
+
+    /** Find the operation info for a method
+     * 
+     * @param oname
+     * @param opName
+     * @return the operation info for the specified operation
+     */ 
+    public MBeanOperationInfo getMethodInfo( ObjectName oname, String opName )
+    {
+        MBeanInfo info=null;
+        try {
+            info=server.getMBeanInfo(oname);
+        } catch (Exception e) {
+            log.info( "Can't find metadata " + oname );
+            return null;
+        }
+        MBeanOperationInfo attInfo[]=info.getOperations();
+        for( int i=0; i<attInfo.length; i++ ) {
+            if( opName.equals(attInfo[i].getName())) {
+                return attInfo[i];
+            }
+        }
+        return null;
+    }
+
+    /** Unregister a component. This is just a helper that
+     * avoids exceptions by checking if the mbean is already registered
+     *
+     * @param oname
+     */
+    public void unregisterComponent( ObjectName oname ) {
+        try {
+            if( getMBeanServer().isRegistered(oname)) {
+                getMBeanServer().unregisterMBean(oname);
+            }
+        } catch( Throwable t ) {
+            log.error( "Error unregistering mbean ", t);
+        }
+    }
+
+    /**
+     * Factory method to create (if necessary) and return our
+     * <code>MBeanServer</code> instance.
+     *
+     */
+    public synchronized MBeanServer getMBeanServer() {
+        long t1=System.currentTimeMillis();
+
+        if (server == null) {
+            if( MBeanServerFactory.findMBeanServer(null).size() > 0 ) {
+                server = MBeanServerFactory.findMBeanServer(null).get(0);
+                if( log.isDebugEnabled() ) {
+                    log.debug("Using existing MBeanServer " + (System.currentTimeMillis() - t1 ));
+                }
+            } else {
+                server = ManagementFactory.getPlatformMBeanServer();
+                if( log.isDebugEnabled() ) {
+                    log.debug("Creating MBeanServer"+ (System.currentTimeMillis() - t1 ));
+                }
+            }
+        }
+        return (server);
+    }
+
+    /** Find or load metadata. 
+     */ 
+    public ManagedBean findManagedBean(Object bean, Class<?> beanClass,
+            String type) throws Exception {
+        if( bean!=null && beanClass==null ) {
+            beanClass=bean.getClass();
+        }
+        
+        if( type==null ) {
+            type=beanClass.getName();
+        }
+        
+        // first look for existing descriptor
+        ManagedBean managed = findManagedBean(type);
+
+        // Search for a descriptor in the same package
+        if( managed==null ) {
+            // check package and parent packages
+            if( log.isDebugEnabled() ) {
+                log.debug( "Looking for descriptor ");
+            }
+            findDescriptor( beanClass, type );
+
+            managed=findManagedBean(type);
+        }
+        
+        if( bean instanceof DynamicMBean ) {
+            if( log.isDebugEnabled() ) {
+                log.debug( "Dynamic mbean support ");
+            }
+            // Dynamic mbean
+            loadDescriptors("MbeansDescriptorsDynamicMBeanSource",
+                    bean, type);
+
+            managed=findManagedBean(type);
+        }
+
+        // Still not found - use introspection
+        if( managed==null ) {
+            if( log.isDebugEnabled() ) {
+                log.debug( "Introspecting ");
+            }
+
+            // introspection
+            loadDescriptors("MbeansDescriptorsIntrospectionSource",
+                    beanClass, type);
+
+            managed=findManagedBean(type);
+            if( managed==null ) {
+                log.warn( "No metadata found for " + type );
+                return null;
+            }
+            managed.setName( type );
+            addManagedBean(managed);
+        }
+        return managed;
+    }
+    
+
+    /** EXPERIMENTAL Convert a string to object, based on type. Used by several
+     * components. We could provide some pluggability. It is here to keep
+     * things consistent and avoid duplication in other tasks 
+     * 
+     * @param type Fully qualified class name of the resulting value
+     * @param value String value to be converted
+     * @return Converted value
+     */ 
+    public Object convertValue(String type, String value)
+    {
+        Object objValue=value;
+        
+        if( type==null || "java.lang.String".equals( type )) {
+            // string is default
+            objValue=value;
+        } else if( "javax.management.ObjectName".equals( type ) ||
+                "ObjectName".equals( type )) {
+            try {
+                objValue=new ObjectName( value );
+            } catch (MalformedObjectNameException e) {
+                return null;
+            }
+        } else if( "java.lang.Integer".equals( type ) ||
+                "int".equals( type )) {
+            objValue=new Integer( value );
+        } else if( "java.lang.Long".equals( type ) ||
+                "long".equals( type )) {
+            objValue=new Long( value );
+        } else if( "java.lang.Boolean".equals( type ) ||
+                "boolean".equals( type )) {
+            objValue=Boolean.valueOf( value );
+        }
+        return objValue;
+    }
+    
+    /** Experimental.
+     *
+     * @param sourceType
+     * @param source
+     * @param param
+     * @return List of descriptors
+     * @throws Exception
+     */
+    public List<ObjectName> load( String sourceType, Object source,
+            String param) throws Exception {
+        if( log.isTraceEnabled()) {
+            log.trace("load " + source );
+        }
+        String location=null;
+        String type=null;
+        Object inputsource=null;
+
+        if( source instanceof DynamicMBean ) {
+            sourceType="MbeansDescriptorsDynamicMBeanSource";
+            inputsource=source;
+        } else if( source instanceof URL ) {
+            URL url=(URL)source;
+            location=url.toString();
+            type=param;
+            inputsource=url.openStream();
+            if( sourceType == null ) {
+                sourceType = sourceTypeFromExt(location);
+            }
+        } else if( source instanceof File ) {
+            location=((File)source).getAbsolutePath();
+            inputsource=new FileInputStream((File)source);            
+            type=param;
+            if( sourceType == null ) {
+                sourceType = sourceTypeFromExt(location);
+            }
+        } else if( source instanceof InputStream ) {
+            type=param;
+            inputsource=source;
+        } else if( source instanceof Class<?> ) {
+            location=((Class<?>)source).getName();
+            type=param;
+            inputsource=source;
+            if( sourceType== null ) {
+                sourceType="MbeansDescriptorsIntrospectionSource";
+            }
+        }
+        
+        if( sourceType==null ) {
+            sourceType="MbeansDescriptorsDigesterSource";
+        }
+        ModelerSource ds=getModelerSource(sourceType);
+        List<ObjectName> mbeans =
+            ds.loadDescriptors(this, location, type, inputsource);
+
+        return mbeans;
+    }
+
+    private String sourceTypeFromExt( String s ) {
+        if( s.endsWith( ".ser")) {
+            return "MbeansDescriptorsSerSource";
+        }
+        else if( s.endsWith(".xml")) {
+            return "MbeansDescriptorsDigesterSource";
+        }
+        return null;
+    }
+
+    /** Register a component 
+     * XXX make it private 
+     * 
+     * @param bean
+     * @param oname
+     * @param type
+     * @throws Exception
+     */ 
+    public void registerComponent(Object bean, ObjectName oname, String type)
+           throws Exception
+    {
+        if( log.isDebugEnabled() ) {
+            log.debug( "Managed= "+ oname);
+        }
+
+        if( bean ==null ) {
+            log.error("Null component " + oname );
+            return;
+        }
+
+        try {
+            if( type==null ) {
+                type=bean.getClass().getName();
+            }
+
+            ManagedBean managed = findManagedBean(bean.getClass(), type);
+
+            // The real mbean is created and registered
+            DynamicMBean mbean = managed.createMBean(bean);
+
+            if(  getMBeanServer().isRegistered( oname )) {
+                if( log.isDebugEnabled()) {
+                    log.debug("Unregistering existing component " + oname );
+                }
+                getMBeanServer().unregisterMBean( oname );
+            }
+
+            getMBeanServer().registerMBean( mbean, oname);
+        } catch( Exception ex) {
+            log.error("Error registering " + oname, ex );
+            throw ex;
+        }
+    }
+
+    /** Lookup the component descriptor in the package and
+     * in the parent packages.
+     *
+     * @param packageName
+     */
+    public void loadDescriptors( String packageName, ClassLoader classLoader  ) {
+        String res=packageName.replace( '.', '/');
+
+        if( log.isTraceEnabled() ) {
+            log.trace("Finding descriptor " + res );
+        }
+
+        if( searchedPaths.get( packageName ) != null ) {
+            return;
+        }
+        String descriptors=res + "/mbeans-descriptors.ser";
+
+        URL dURL=classLoader.getResource( descriptors );
+
+        if( dURL == null ) {
+            descriptors=res + "/mbeans-descriptors.xml";
+            dURL=classLoader.getResource( descriptors );
+        }
+        if( dURL == null ) {
+            return;
+        }
+
+        log.debug( "Found " + dURL);
+        searchedPaths.put( packageName,  dURL );
+        try {
+            if( descriptors.endsWith(".xml" ))
+                loadDescriptors("MbeansDescriptorsDigesterSource", dURL, null);
+            else
+                loadDescriptors("MbeansDescriptorsSerSource", dURL, null);
+            return;
+        } catch(Exception ex ) {
+            log.error("Error loading " + dURL);
+        }
+
+        return;
+    }
+
+    /**
+     * @param sourceType
+     * @param source
+     * @param param
+     * @throws Exception
+
+     */
+    private void loadDescriptors(String sourceType, Object source,
+            String param) throws Exception {
+        load(sourceType, source, param);
+    }
+
+    /** Lookup the component descriptor in the package and
+     * in the parent packages.
+     *
+     * @param beanClass
+     * @param type
+     */
+    private void findDescriptor(Class<?> beanClass, String type) {
+        if( type==null ) {
+            type=beanClass.getName();
+        }
+        ClassLoader classLoader=null;
+        if( beanClass!=null ) {
+            classLoader=beanClass.getClassLoader();
+        }
+        if( classLoader==null ) {
+            classLoader=Thread.currentThread().getContextClassLoader();
+        }
+        if( classLoader==null ) {
+            classLoader=this.getClass().getClassLoader();
+        }
+        
+        String className=type;
+        String pkg=className;
+        while( pkg.indexOf( ".") > 0 ) {
+            int lastComp=pkg.lastIndexOf( ".");
+            if( lastComp <= 0 ) return;
+            pkg=pkg.substring(0, lastComp);
+            if( searchedPaths.get( pkg ) != null ) {
+                return;
+            }
+            loadDescriptors(pkg, classLoader);
+        }
+        return;
+    }
+
+    private ModelerSource getModelerSource( String type )
+            throws Exception
+    {
+        if( type==null ) type="MbeansDescriptorsDigesterSource";
+        if( type.indexOf( ".") < 0 ) {
+            type="org.apache.tomcat.util.modeler.modules." + type;
+        }
+
+        Class<?> c = Class.forName(type);
+        ModelerSource ds=(ModelerSource)c.newInstance();
+        return ds;
+    }
+
+
+    // -------------------- Registration  --------------------
+    
+    public ObjectName preRegister(MBeanServer server,
+                                  ObjectName name) throws Exception 
+    {
+        this.server=server;
+        return name;
+    }
+
+    public void postRegister(Boolean registrationDone) {
+    }
+
+    public void preDeregister() throws Exception {
+    }
+
+    public void postDeregister() {
+    }
+
+
+    // -------------------- DEPRECATED METHODS  --------------------
+    // May still be used in tomcat 
+    // Never part of an official release
+    
+    public ManagedBean findManagedBean(Class<?> beanClass, String type)
+        throws Exception
+    {
+        return findManagedBean(null, beanClass, type);        
+    }
+    
+    /**
+     * Set the <code>MBeanServer</code> to be utilized for our
+     * registered management beans.
+     *
+     * @param server The new <code>MBeanServer</code> instance
+     */
+    public void setMBeanServer( MBeanServer server ) {
+        this.server=server;
+    }
+
+    public void resetMetadata() {
+        stop();
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/RegistryMBean.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/RegistryMBean.java
new file mode 100644
index 0000000..37e74eb
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/RegistryMBean.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.tomcat.util.modeler;
+
+
+import java.util.List;
+
+import javax.management.ObjectName;
+
+/**
+ * Interface for modeler MBeans.
+ * 
+ * This is the main entry point into modeler. It provides methods to create
+ * and manipulate model mbeans and simplify their use.
+ *
+ * Starting with version 1.1, this is no longer a singleton and the static
+ * methods are strongly deprecated. In a container environment we can expect
+ * different applications to use different registries.
+ * 
+ * @author Craig R. McClanahan
+ * @author Costin Manolache
+ * 
+ * @since 1.1
+ */
+public interface RegistryMBean {
+
+    /** 
+     * Load an extended mlet file. The source can be an URL, File or
+     * InputStream. 
+     * 
+     * All mbeans will be instantiated, registered and the attributes will be 
+     * set. The result is a list of ObjectNames.
+     *
+     * @param source InputStream or URL of the file
+     * @param cl ClassLoader to be used to load the mbeans, or null to use the
+     *        default JMX mechanism ( i.e. all registered loaders )
+     * @return List of ObjectName for the loaded mbeans
+     * @throws Exception
+     * 
+     * @since 1.1
+     */ 
+    public List<ObjectName> loadMBeans( Object source, ClassLoader cl )
+            throws Exception;
+
+    /** Invoke an operation on a set of mbeans. 
+     * 
+     * @param mbeans List of ObjectNames
+     * @param operation Operation to perform. Typically "init" "start" "stop" or "destroy"
+     * @param failFirst Behavior in case of exceptions - if false we'll ignore
+     *      errors
+     * @throws Exception
+     */ 
+    public void invoke( List<ObjectName> mbeans, String operation, boolean failFirst )
+            throws Exception;
+
+    /** Register a bean by creating a modeler mbean and adding it to the 
+     * MBeanServer.
+     * 
+     * If metadata is not loaded, we'll look up and read a file named
+     * "mbeans-descriptors.ser" or "mbeans-descriptors.xml" in the same package
+     * or parent.
+     *
+     * If the bean is an instance of DynamicMBean. it's metadata will be converted
+     * to a model mbean and we'll wrap it - so modeler services will be supported
+     *
+     * If the metadata is still not found, introspection will be used to extract
+     * it automatically. 
+     * 
+     * If an mbean is already registered under this name, it'll be first
+     * unregistered.
+     * 
+     * If the component implements MBeanRegistration, the methods will be called.
+     * If the method has a method "setRegistry" that takes a RegistryMBean as
+     * parameter, it'll be called with the current registry.
+     * 
+     *
+     * @param bean Object to be registered
+     * @param oname Name used for registration
+     * @param type The type of the mbean, as declared in mbeans-descriptors. If
+     * null, the name of the class will be used. This can be used as a hint or
+     * by subclasses.
+     *
+     * @since 1.1
+     */ 
+    public void registerComponent(Object bean, String oname, String type)
+           throws Exception;
+
+    /** Unregister a component. We'll first check if it is registered,
+     * and mask all errors. This is mostly a helper.
+     * 
+     * @param oname
+     * 
+     * @since 1.1
+     */ 
+    public void unregisterComponent( String oname );
+
+
+     /** Return an int ID for faster access. Will be used for notifications
+      * and for other operations we want to optimize. 
+      *
+      * @param domain Namespace 
+      * @param name  Type of the notification
+      * @return  An unique id for the domain:name combination
+      * @since 1.1
+      */
+    public int getId( String domain, String name);
+
+
+    /** Reset all metadata cached by this registry. Should be called 
+     * to support reloading. Existing mbeans will not be affected or modified.
+     * 
+     * It will be called automatically if the Registry is unregistered.
+     * @since 1.1
+     */ 
+    public void stop();
+
+    /** Load descriptors. The source can be a File, URL pointing to an
+     * mbeans-descriptors.xml.
+     * 
+     * Also ( experimental for now ) a ClassLoader - in which case META-INF/ will
+     * be used.
+     * 
+     * @param source
+     */ 
+    public void loadMetadata(Object source ) throws Exception;
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/mbeans-descriptors.dtd b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/mbeans-descriptors.dtd
new file mode 100644
index 0000000..8770725
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/mbeans-descriptors.dtd
@@ -0,0 +1,249 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+
+<!--
+     DTD for the Model MBeans Configuration File
+
+     To support validation of your configuration file, include the following
+     DOCTYPE element at the beginning (after the "xml" declaration):
+
+     <!DOCTYPE mbeans-descriptors PUBLIC
+      "-//Apache Software Foundation//DTD Model MBeans Configuration File"
+      "http://jakarta.apache.org/commons/dtds/mbeans-descriptors.dtd">
+
+     $Id: mbeans-descriptors.dtd,v 1.1 2011/06/28 21:08:21 rherrmann Exp $
+-->
+
+
+<!-- ========== Defined Types ============================================= -->
+
+
+<!-- A "Boolean" is the string representation of a boolean (true or false)
+     variable.
+-->
+<!ENTITY % Boolean "(true|false|yes|no)">
+
+
+<!-- A "ClassName" is the fully qualified name of a Java class that is
+     instantiated to provide the functionality of the enclosing element.
+-->
+<!ENTITY % ClassName "CDATA">
+
+
+<!-- A "MethodName" is the name of a constructor or method, which must
+     be legal according to the syntax requirements of the Java language.
+-->
+<!ENTITY % MethodName "CDATA">
+
+
+<!-- A "VariableName" is the name of a variable or parameter, which must
+     be legal according to the syntax requirements of the Java language.
+-->
+<!ENTITY % VariableName "CDATA">
+
+
+<!-- ========== Element Definitions ======================================= -->
+
+
+<!-- The "mbeans-descriptors" element is the root of the configuration file
+     hierarchy, and contains nested elements for all of the other
+     configuration settings.  Remaining element definitions are listed
+     in alphabetical order.
+-->
+<!ELEMENT mbeans-descriptors (mbean*)>
+<!ATTLIST mbeans-descriptors id          ID             #IMPLIED>
+
+
+<!-- The "attribute" element describes a JavaBeans property of an MBean.
+     The following attributes are supported:
+
+     description      Human-readable description of this attribute.
+
+     displayName      Display name of this attribute.
+
+     getMethod        Name of the property getter method, if it does
+                      not follow standard JavaBeans naming patterns.
+
+     is               Boolean value indicating whether or not this
+                      attribute is a boolean with an "is" getter method.
+                      By default, this is set to "false".
+
+     name             Name of this JavaBeans property, conforming to
+                      standard naming design patterns.
+
+     readable         Boolean value indicating whether or not this
+                      attribute is readable by management applications.
+                      By default, this is set to "true".
+
+     setMethod        Name of the property setter method, if it does
+                      not follow standard JavaBeans naming patterns.
+
+     type             Fully qualified Java class name of this attribute.
+
+     writeable        Boolean value indicating whether or not this
+                      attribute is writeable by management applications.
+                      By default, this is set to "true".
+-->
+<!ELEMENT attribute (descriptor?)>
+<!ATTLIST attribute         id           ID             #IMPLIED>
+<!ATTLIST attribute         description  CDATA          #IMPLIED>
+<!ATTLIST attribute         displayName  CDATA          #IMPLIED>
+<!ATTLIST attribute         getMethod    %MethodName;   #IMPLIED>
+<!ATTLIST attribute         is           %Boolean;      #IMPLIED>
+<!ATTLIST attribute         name         %VariableName; #IMPLIED>
+<!ATTLIST attribute         readable     %Boolean;      #IMPLIED>
+<!ATTLIST attribute         setMethod    %MethodName;   #IMPLIED>
+<!ATTLIST attribute         type         %ClassName;    #IMPLIED>
+<!ATTLIST attribute         writeable    %Boolean;      #IMPLIED>
+
+
+<!-- The "constructor" element describes a public constructor for the
+     underlying actual class.  It may contain nested "parameter" elements
+     for the various arguments to this constructor.  The following attributes
+     are supported:
+
+     displayName      Display name of this constructor.
+
+     name             Name of this constructor (by Java convention, this must
+                      be the same as the base class name).
+-->
+<!ELEMENT constructor (descriptor?, parameter*)>
+<!ATTLIST constructor       id           ID             #IMPLIED>
+<!ATTLIST constructor       displayName  CDATA          #IMPLIED>
+<!ATTLIST constructor       name         %VariableName; #IMPLIED>
+
+
+<!-- The "descriptor" element groups a set of descriptor fields whose
+     values will be included in the Descriptor for the corresponding
+     metatdata info classes.
+-->
+<!ELEMENT descriptor (field*)>
+<!ATTLIST descriptor        id           ID             #IMPLIED>
+
+
+<!-- The "field" element represents a single name/value pair that will
+     be included in the Descriptor corresponding to our enclosing
+     "descriptor" element.  The following attributes are supported:
+
+     name             Field name of the field to be included
+
+     value            Field value of the field to be included
+                      (will be stored as a String)
+-->
+<!ELEMENT field EMPTY>
+<!ATTLIST field             id           ID             #IMPLIED>
+<!ATTLIST field             name         CDATA          #REQUIRED>
+<!ATTLIST field             value        CDATA          #REQUIRED>
+
+
+
+<!-- The "mbean" element describes a particular JMX ModelMBean implementation,
+     including the information necessary to construct the corresponding
+     ModelMBeanInfo structures.  The following attributes are supported:
+
+     className        Fully qualified Java class name of the ModelMBean
+                      implementation class.  If not specified, the standard
+                      implementation provided by JMX will be utilized.
+
+     description      Human-readable description of this managed bean.
+
+     domain           The JMX MBeanServer domain in which the ModelMBean
+                      created by this managed bean should be registered,
+                      when creating its ObjectName.
+
+     group            Optional name of a "grouping classification" that can
+                      be used to select groups of similar MBean implementation
+                      classes.
+
+     name             Unique name of this MBean (normally corresponds to the
+                      base class name of the corresponding server component).
+
+     type             Fully qualified Java class name of the underlying
+                      managed resource implementation class.
+-->
+<!ELEMENT mbean (descriptor?, attribute*, constructor*, notification*, operation*)>
+<!ATTLIST mbean             id           ID             #IMPLIED>
+<!ATTLIST mbean             className    %ClassName;    #IMPLIED>
+<!ATTLIST mbean             description  CDATA          #IMPLIED>
+<!ATTLIST mbean             domain       CDATA          #IMPLIED>
+<!ATTLIST mbean             group        CDATA          #IMPLIED>
+<!ATTLIST mbean             name         %MethodName;   #IMPLIED>
+<!ATTLIST mbean             type         %ClassName;    #IMPLIED>
+
+
+<!-- The "notification" element describes the notification types that are
+     generated by a particular managed bean.  The following attributes
+     are supported:
+
+     description      Human-readable description of these notification events.
+
+     name             Name of this set of notification event types.
+-->
+<!ELEMENT notification (descriptor?, notification-type*)>
+<!ATTLIST notification      id           ID             #IMPLIED>
+<!ATTLIST notification      description  CDATA          #IMPLIED>
+<!ATTLIST notification      name         %VariableName; #IMPLIED>
+
+
+<!-- The nested content of the "notification-type" element is the event string
+     of an event that can be emitted by this MBean.
+-->
+<!ELEMENT notification-type (#PCDATA)>
+<!ATTLIST notification-type id           ID             #IMPLIED>
+
+
+<!-- The "operation" element describes a the signature of a public method
+     that is accessible to management applications.  The following attributes
+     are supported:
+
+     description      Human-readable description of this operation.
+
+     impact           Indication of the impact of this method:
+                      ACTION (write like), ACTION-INFO (write+read like)
+                      INFO (read like), or UNKNOWN.
+
+     name             Name of this public method.
+
+     returnType       Fully qualified Java class name of the return
+                      type of this method.
+-->
+<!ELEMENT operation   (descriptor?, parameter*)>
+<!ATTLIST operation         id           ID             #IMPLIED>
+<!ATTLIST operation         description  CDATA          #IMPLIED>
+<!ATTLIST operation         impact       CDATA          #IMPLIED>
+<!ATTLIST operation         name         %VariableName; #IMPLIED>
+<!ATTLIST operation         returnType   %ClassName;    #IMPLIED>
+
+
+<!-- The "parameter" element describes a single argument that will be passed
+     to a constructor or operation.  The following attributes are supported:
+
+     description      Human-readable description of this parameter.
+
+     name             Java language name of this parameter.
+
+     type             Fully qualified Java class name of this parameter.
+-->
+<!ELEMENT parameter EMPTY>
+<!ATTLIST parameter         id           ID             #IMPLIED>
+<!ATTLIST parameter         description  CDATA          #IMPLIED>
+<!ATTLIST parameter         name         %VariableName; #IMPLIED>
+<!ATTLIST parameter         type         %ClassName;    #IMPLIED>
+
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/package.html b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/package.html
new file mode 100644
index 0000000..d2acf34
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/modeler/package.html
@@ -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.
+-->
+<html>
+<head>
+<title>Package Documentation for COMMONS-MODELER</title>
+</head>
+<body bgcolor="white">
+<p>The <em>Modeler</em> component of the Commons project
+offers convenient support for configuring and instantiating Model MBeans
+(management beans), as described in the JMX Specification.  It is typically
+used within a server-based application that wants to expose management
+features via JMX.  See the
+<a href="http://java.sun.com/products/JavaManagement/download.html">
+JMX Specification (Version 1.1)</a> for more information about Model MBeans
+and other JMX concepts.</p>
+
+<p>Model MBeans are very powerful - and the JMX specification includes a
+mechanism to use a standard JMX-provided base class to satisfy many of the
+requirements, without having to create custom Model MBean implementation
+classes yourself.  However, one of the requirements in creating such a
+Model MBean is to create the corresponding metadata information (i.e. an
+implementation of the
+<code>javax.management.modelmbean.ModelMBeanInfo</code> interface and its
+corresponding subordinate interfaces).  Creating this information can be
+tedious and error prone.  The <em>Modeler</em> package makes the process
+much simpler, because the required information is constructed dynamically
+from an easy-to-understand XML description of the metadata.  Once you have
+the metadata defined, and registered at runtime in the provided
+<a href="Registry.html">Registry</a>, <em>Modeler</em> also supports
+convenient factory methods to instantiate new Model MBean instances for you.
+</p>
+
+<p>The steps required to use Modeler in your server-based application are
+described in detail below.  You can find some simple usage code in the unit
+tests that come with Modeler (in the <code>src/test</code> subdirectory of the
+source distribution), and much more complex usage code in Tomcat 4.1 (in the
+<code>org.apache.catalina.mbeans</code> package).</p>. More advanced uses can
+be found in Tomcat 5.
+
+
+<h3>1.  Acquire a JMX Implementation</h3>
+
+<p><em>Modeler</em> has been tested with different JMX implementations:
+<ul>
+<li>JMX Reference Implementation (version 1.0.1 or later) -
+    <a href="http://java.sun.com/products/JavaManagement/download.html">
+    http://java.sun.com/products/JavaManagement/download.html</a></li>
+<li>MX4J (version 1.1 or later) -
+    <a href="http://mx4j.sourceforge.net/">http://mx4j.sourceforge.net</a></li>
+<li>JBoss MX
+    <a href="http://www.jboss.org/">http://www.jboss.org</a></li>
+</ul>
+
+<p>After unpacking the release, you will need to ensure that the appropriate
+JAR file (<code>jmxri.jar</code> or <code>mx4j.jar</code>) is included on your
+compilation classpath, and in the classpath of your server application when it
+is executed.</p>
+
+
+<h3>2.  Create a Modeler Configuration File</h3>
+
+<p><em>Modeler</em> requires that you construct a configuration file that
+describes the metadata ultimately need to construct the
+<code>javax.management.modelmbean.ModelMBeanInfo</code> structure that is
+required by JMX.  Your XML file must conform to the
+<a href="../../../../../../mbeans-descriptors.dtd">mbeans-descriptors.dtd</a>
+DTD that defines the acceptable structure.</p>
+
+<p>Fundamentally, you will be constructing an <code>&lt;mbean&gt;</code>
+element for each type of Model MBean that a registry will know how to create.
+Nested within this element will be other elements describing the constructors,
+attributes, operations, and notifications associated with this MBean.  See
+the comments in the DTD for detailed information about the valid attributes
+and their meanings.</p>
+
+<p>A simple example configuration file might include the following components
+(abstracted from the real definitions found in Tomcat 4.1's use of Modeler):
+</p>
+<pre>
+
+  &lt;?xml version="1.0"?&gt;
+  &lt;!DOCTYPE mbeans-descriptors PUBLIC
+   "-//Apache Software Foundation//DTD Model MBeans Configuration File"
+   "http://jakarta.apache.org/commons/dtds/mbeans-descriptors.dtd"&gt;
+
+  &lt;mbeans-descriptors&gt;
+
+    &lt;!-- ... other MBean definitions ... --&gt;
+
+    &lt;mbean         name="Group"
+              className="org.apache.catalina.mbeans.GroupMBean"
+            description="Group from a user database"
+                 domain="Users"
+                  group="Group"
+                   type="org.apache.catalina.Group"&gt;
+
+      &lt;attribute   name="description"
+            description="Description of this group"
+                   type="java.lang.String"/&gt;
+
+      &lt;attribute   name="groupname"
+            description="Group name of this group"
+                   type="java.lang.String"/&gt;
+
+      &lt;attribute   name="roles"
+            description="MBean Names of roles for this group"
+                   type="java.lang.String[]"
+              writeable="false"/&gt;
+
+      &lt;attribute   name="users"
+            description="MBean Names of user members of this group"
+                   type="java.lang.String[]"
+              writeable="false"/&gt;
+
+      &lt;operation   name="addRole"
+            description="Add a new authorized role for this group"
+                 impact="ACTION"
+             returnType="void"&gt;
+        &lt;parameter name="role"
+            description="Role to be added"
+                   type="java.lang.String"/&gt;
+      &lt;/operation&gt;
+
+      &lt;operation   name="removeRole"
+            description="Remove an old authorized role for this group"
+                 impact="ACTION"
+             returnType="void"&gt;
+        &lt;parameter name="role"
+            description="Role to be removed"
+                   type="java.lang.String"/&gt;
+      &lt;/operation&gt;
+
+      &lt;operation   name="removeRoles"
+            description="Remove all authorized roles for this group"
+                 impact="ACTION"
+             returnType="void"&gt;
+      &lt;/operation&gt;
+
+    &lt;/mbean&gt;
+
+    &lt;!-- ... other MBean definitions ... --&gt;
+
+  &lt;/mbeans-descriptors&gt;
+
+</pre>
+
+<p>This MBean represents an instance of <em>org.apache.catalina.Group</em>,
+which is an entity representing a group of users (with a shared set of security
+roles that all users in the group inherit) in a user database.  This MBean
+advertises support for four attributes (description, groupname, roles, and
+users) that roughly correspond to JavaBean properties.  By default, attributes
+are assumed to have read/write access.  For this particular MBean, the roles
+and users attributes are read-only (<code>writeable="false"</code>).  Finally,
+this MBean supports three operations (addRole, removeRole, and
+removeRoles) that roughly correspond to JavaBean methods on the underlying
+component.</p>
+
+<p>In general, <em>Modeler</em> provides a standard ModelMBean implementation
+that simply passes on JMX calls on attributes and operations directly through
+to the managed component that the ModelMBean is associated with.  For special
+case requirements, you can define a subclass of
+<a href="BaseModelMBean.html">BaseModelMBean</a> that provides override
+methods for one or more of these attributes (i.e. the property getter and/or
+setter methods) and operations (i.e. direct method calls).
+
+<p>For this particular MBean, a custom BaseModelMBean implementation subclass
+is described (<code>org.apache.catalina.mbeans.GroupMBean</code>) is
+configured.  It was necessary in this particular case because several of the
+underlying Catalina component's methods deal with internal objects or arrays of
+objects, rather than just the Strings and primitives that are supported by all
+JMX clients.  Thus, the following method on the <code>Group</code> interface:
+</p>
+<pre>
+    public void addRole(Role role);
+</pre>
+<p>is represented, in the MBean, by an <code>addRole</code> method that takes
+a String argument representing the role name of the required role.  The MBean's
+implementation class acts as an adapter, and looks up the required Role
+object (by name) before calling the <code>addRole</code> method on the
+underlying <code>Group</code> instance within the Server.</p>
+
+
+<h3>3.  Create Modeler Registry at Startup Time</h3>
+
+<p>The metadata information, and the corresponding Model MBean factory, is
+represented at runtime in an instance of <a href="Registry.html">Registry</a>
+whose contents are initialized from the configuration file prepared as was
+described above.  Typically, such a file will be included in the JAR file
+containing the MBean implementation classes themselves, and loaded as follows:
+</p>
+<pre>
+    URL url= this.getClass().getResource
+      ("/com/mycompany/mypackage/mbeans-descriptors.xml");
+    Registry registry = Registry.getRegistry();
+    registry.loadMetadata(url);
+</pre>
+
+<p>Besides using the configuration file, it is possible to configure the
+registry metadata by hand, using the <code>addManagedBean()</code> and
+<code>removeManagedBean()</code> methods.  However, most users will find
+the standard support for loading a configuration file to be convenient
+and sufficient.</p>
+
+<p>Modeler will also look for a mbeans-descriptors.xml in the same package
+with the class beeing registered and in its parent. If no metadata is found,
+modeler will use a number of simple patterns, similar with the ones used by 
+ant, to determine a reasonable metadata</p>
+
+<p>In a future version we should also support xdoclet-based generation of the
+descriptors</p>
+
+
+<h3>4.  Instantiate Model MBeans As Needed</h3>
+
+<p>When your server application needs to instantiate a new MBean and register
+it with the corresponding <code>MBeanServer</code>, it can execute code like
+this:</p>
+
+<pre>
+  Group group = ... managed component instance ...;
+
+  MBeanServer mserver = registry.getMBeanServer();
+
+  String oname="myDomain:type=Group,name=myGroup";
+
+  registry.registerComponent( group, oname, "Group" );
+</pre>
+
+<p>After the Model MBean has been created and registered, it is accessible to
+JMX clients through the standard JMX client APIs.
+</p>
+
+</body>
+</html>
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/JSSEImplementation.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/JSSEImplementation.java
new file mode 100644
index 0000000..be2a5de
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/JSSEImplementation.java
@@ -0,0 +1,64 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.util.net.jsse;
+
+import java.net.Socket;
+
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+
+import org.apache.tomcat.util.net.AbstractEndpoint;
+import org.apache.tomcat.util.net.SSLImplementation;
+import org.apache.tomcat.util.net.SSLSupport;
+import org.apache.tomcat.util.net.SSLUtil;
+import org.apache.tomcat.util.net.ServerSocketFactory;
+
+/* JSSEImplementation:
+
+   Concrete implementation class for JSSE
+
+   @author EKR
+*/
+        
+public class JSSEImplementation extends SSLImplementation {
+
+    @Override
+    public String getImplementationName(){
+        return "JSSE";
+    }
+      
+    @Override
+    public ServerSocketFactory getServerSocketFactory(AbstractEndpoint endpoint)  {
+        return new JSSESocketFactory(endpoint);
+    } 
+
+    @Override
+    public SSLSupport getSSLSupport(Socket s) {
+        return new JSSESupport((SSLSocket) s);
+    }
+
+    @Override
+    public SSLSupport getSSLSupport(SSLSession session) {
+        return new JSSESupport(session);
+    }
+
+    @Override
+    public SSLUtil getSSLUtil(AbstractEndpoint endpoint) {
+        return new JSSESocketFactory(endpoint);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/JSSEKeyManager.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/JSSEKeyManager.java
new file mode 100644
index 0000000..a06a42a
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/JSSEKeyManager.java
@@ -0,0 +1,151 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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.jsse;
+
+import java.net.Socket;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.X509KeyManager;
+
+/**
+ * X509KeyManager which allows selection of a specific keypair and certificate
+ * chain (identified by their keystore alias name) to be used by the server to
+ * authenticate itself to SSL clients.
+ *
+ * @author Jan Luehe
+ */
+public final class JSSEKeyManager implements X509KeyManager {
+
+    private X509KeyManager delegate;
+    private String serverKeyAlias;
+
+    /**
+     * Constructor.
+     *
+     * @param mgr The X509KeyManager used as a delegate
+     * @param serverKeyAlias The alias name of the server's keypair and
+     * supporting certificate chain
+     */
+    public JSSEKeyManager(X509KeyManager mgr, String serverKeyAlias) {
+        this.delegate = mgr;
+        this.serverKeyAlias = serverKeyAlias;
+    }
+
+    /**
+     * Choose an alias to authenticate the client side of a secure socket,
+     * given the public key type and the list of certificate issuer authorities
+     * recognized by the peer (if any).
+     *
+     * @param keyType The key algorithm type name(s), ordered with the
+     * most-preferred key type first
+     * @param issuers The list of acceptable CA issuer subject names, or null
+     * if it does not matter which issuers are used
+     * @param socket The socket to be used for this connection. This parameter
+     * can be null, in which case this method will return the most generic
+     * alias to use
+     *
+     * @return The alias name for the desired key, or null if there are no
+     * matches
+     */
+    @Override
+    public String chooseClientAlias(String[] keyType, Principal[] issuers,
+                                    Socket socket) {
+        return delegate.chooseClientAlias(keyType, issuers, socket);
+    }
+
+    /**
+     * Returns this key manager's server key alias that was provided in the
+     * constructor.
+     *
+     * @param keyType The key algorithm type name (ignored)
+     * @param issuers The list of acceptable CA issuer subject names, or null
+     * if it does not matter which issuers are used (ignored)
+     * @param socket The socket to be used for this connection. This parameter
+     * can be null, in which case this method will return the most generic
+     * alias to use (ignored)
+     *
+     * @return Alias name for the desired key
+     */
+    @Override
+    public String chooseServerAlias(String keyType, Principal[] issuers,
+                                    Socket socket) {
+        return serverKeyAlias;
+    }
+
+    /**
+     * Returns the certificate chain associated with the given alias.
+     *
+     * @param alias The alias name
+     *
+     * @return Certificate chain (ordered with the user's certificate first
+     * and the root certificate authority last), or null if the alias can't be
+     * found
+     */
+    @Override
+    public X509Certificate[] getCertificateChain(String alias) {
+        return delegate.getCertificateChain(alias); 
+    }
+
+    /**
+     * Get the matching aliases for authenticating the client side of a secure
+     * socket, given the public key type and the list of certificate issuer
+     * authorities recognized by the peer (if any).
+     *
+     * @param keyType The key algorithm type name
+     * @param issuers The list of acceptable CA issuer subject names, or null
+     * if it does not matter which issuers are used
+     *
+     * @return Array of the matching alias names, or null if there were no
+     * matches
+     */
+    @Override
+    public String[] getClientAliases(String keyType, Principal[] issuers) {
+        return delegate.getClientAliases(keyType, issuers);
+    }
+
+    /**
+     * Get the matching aliases for authenticating the server side of a secure
+     * socket, given the public key type and the list of certificate issuer
+     * authorities recognized by the peer (if any).
+     *
+     * @param keyType The key algorithm type name
+     * @param issuers The list of acceptable CA issuer subject names, or null
+     * if it does not matter which issuers are used
+     *
+     * @return Array of the matching alias names, or null if there were no
+     * matches
+     */
+    @Override
+    public String[] getServerAliases(String keyType, Principal[] issuers) {
+        return delegate.getServerAliases(keyType, issuers);
+    }
+
+    /**
+     * Returns the key associated with the given alias.
+     *
+     * @param alias The alias name
+     *
+     * @return The requested key, or null if the alias can't be found
+     */
+    @Override
+    public PrivateKey getPrivateKey(String alias) {
+        return delegate.getPrivateKey(alias);
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/JSSESocketFactory.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/JSSESocketFactory.java
new file mode 100644
index 0000000..f9a5585
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/JSSESocketFactory.java
@@ -0,0 +1,840 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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.jsse;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CRL;
+import java.security.cert.CRLException;
+import java.security.cert.CertPathParameters;
+import java.security.cert.CertStore;
+import java.security.cert.CertStoreParameters;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CollectionCertStoreParameters;
+import java.security.cert.PKIXBuilderParameters;
+import java.security.cert.X509CertSelector;
+import java.util.Collection;
+import java.util.Locale;
+import java.util.Vector;
+
+import javax.net.ssl.CertPathTrustManagerParameters;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.ManagerFactoryParameters;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSessionContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509KeyManager;
+
+import org.apache.tomcat.util.net.AbstractEndpoint;
+import org.apache.tomcat.util.net.Constants;
+import org.apache.tomcat.util.net.SSLUtil;
+import org.apache.tomcat.util.net.ServerSocketFactory;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * SSL server socket factory. It <b>requires</b> a valid RSA key and
+ * JSSE.<br/>
+ * keytool -genkey -alias tomcat -keyalg RSA</br>
+ * Use "changeit" as password (this is the default we use).
+ * 
+ * @author Harish Prabandham
+ * @author Costin Manolache
+ * @author Stefan Freyr Stefansson
+ * @author EKR -- renamed to JSSESocketFactory
+ * @author Jan Luehe
+ * @author Bill Barker
+ */
+public class JSSESocketFactory implements ServerSocketFactory, SSLUtil {
+
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog(JSSESocketFactory.class);
+    private static final StringManager sm =
+        StringManager.getManager("org.apache.tomcat.util.net.jsse.res");
+
+    private static final boolean RFC_5746_SUPPORTED;
+
+    // Defaults - made public where re-used
+    private static final String defaultProtocol = "TLS";
+    private static final String defaultKeystoreType = "JKS";
+    private static final String defaultKeystoreFile
+        = System.getProperty("user.home") + "/.keystore";
+    private static final int defaultSessionCacheSize = 0;
+    private static final int defaultSessionTimeout = 86400;
+    private static final String ALLOW_ALL_SUPPORTED_CIPHERS = "ALL";
+    public static final String DEFAULT_KEY_PASS = "changeit";
+    
+    static {
+        boolean result = false;
+        SSLContext context;
+        try {
+            context = SSLContext.getInstance("TLS");
+            context.init(null, null, null);
+            SSLServerSocketFactory ssf = context.getServerSocketFactory();
+            String ciphers[] = ssf.getSupportedCipherSuites();
+            for (String cipher : ciphers) {
+                if ("TLS_EMPTY_RENEGOTIATION_INFO_SCSV".equals(cipher)) {
+                    result = true;
+                    break;
+                }
+            }
+        } catch (NoSuchAlgorithmException e) {
+            // Assume no RFC 5746 support
+        } catch (KeyManagementException e) {
+            // Assume no RFC 5746 support
+        }
+        RFC_5746_SUPPORTED = result;
+    }
+
+
+    private AbstractEndpoint endpoint;
+
+    protected SSLServerSocketFactory sslProxy = null;
+    protected String[] enabledCiphers;
+    protected boolean allowUnsafeLegacyRenegotiation = false;
+
+    /**
+     * Flag to state that we require client authentication.
+     */
+    protected boolean requireClientAuth = false;
+
+    /**
+     * Flag to state that we would like client authentication.
+     */
+    protected boolean wantClientAuth    = false;
+
+
+    public JSSESocketFactory (AbstractEndpoint endpoint) {
+        this.endpoint = endpoint;
+    }
+
+    @Override
+    public ServerSocket createSocket (int port)
+        throws IOException
+    {
+        init();
+        ServerSocket socket = sslProxy.createServerSocket(port);
+        initServerSocket(socket);
+        return socket;
+    }
+    
+    @Override
+    public ServerSocket createSocket (int port, int backlog)
+        throws IOException
+    {
+        init();
+        ServerSocket socket = sslProxy.createServerSocket(port, backlog);
+        initServerSocket(socket);
+        return socket;
+    }
+    
+    @Override
+    public ServerSocket createSocket (int port, int backlog,
+                                      InetAddress ifAddress)
+        throws IOException
+    {   
+        init();
+        ServerSocket socket = sslProxy.createServerSocket(port, backlog,
+                                                          ifAddress);
+        initServerSocket(socket);
+        return socket;
+    }
+    
+    @Override
+    public Socket acceptSocket(ServerSocket socket)
+        throws IOException
+    {
+        SSLSocket asock = null;
+        try {
+             asock = (SSLSocket)socket.accept();
+        } catch (SSLException e){
+          throw new SocketException("SSL handshake error" + e.toString());
+        }
+        return asock;
+    }
+    
+    @Override
+    public void handshake(Socket sock) throws IOException {
+        // We do getSession instead of startHandshake() so we can call this multiple times
+        SSLSession session = ((SSLSocket)sock).getSession();
+        if (session.getCipherSuite().equals("SSL_NULL_WITH_NULL_NULL"))
+            throw new IOException("SSL handshake failed. Ciper suite in SSL Session is SSL_NULL_WITH_NULL_NULL");
+
+        if (!allowUnsafeLegacyRenegotiation && !RFC_5746_SUPPORTED) {
+            // Prevent further handshakes by removing all cipher suites
+            ((SSLSocket) sock).setEnabledCipherSuites(new String[0]);
+        }
+    }
+
+    /*
+     * Determines the SSL cipher suites to be enabled.
+     *
+     * @param requestedCiphers Comma-separated list of requested ciphers
+     * @param supportedCiphers Array of supported ciphers
+     *
+     * @return Array of SSL cipher suites to be enabled, or null if none of the
+     * requested ciphers are supported
+     */
+    protected String[] getEnabledCiphers(String requestedCiphers,
+                                         String[] supportedCiphers) {
+
+        String[] result = null;
+
+        if (ALLOW_ALL_SUPPORTED_CIPHERS.equals(requestedCiphers)) {
+            return supportedCiphers;
+        }
+
+        if (requestedCiphers != null) {
+            Vector<String> vec = null;
+            String cipher = requestedCiphers;
+            int index = requestedCiphers.indexOf(',');
+            if (index != -1) {
+                int fromIndex = 0;
+                while (index != -1) {
+                    cipher =
+                        requestedCiphers.substring(fromIndex, index).trim();
+                    if (cipher.length() > 0) {
+                        /*
+                         * Check to see if the requested cipher is among the
+                         * supported ciphers, i.e., may be enabled
+                         */
+                        for (int i=0; supportedCiphers != null
+                                     && i<supportedCiphers.length; i++) {
+                            if (supportedCiphers[i].equals(cipher)) {
+                                if (vec == null) {
+                                    vec = new Vector<String>();
+                                }
+                                vec.addElement(cipher);
+                                break;
+                            }
+                        }
+                    }
+                    fromIndex = index+1;
+                    index = requestedCiphers.indexOf(',', fromIndex);
+                } // while
+                cipher = requestedCiphers.substring(fromIndex);
+            }
+
+            if (cipher != null) {
+                cipher = cipher.trim();
+                if (cipher.length() > 0) {
+                    /*
+                     * Check to see if the requested cipher is among the
+                     * supported ciphers, i.e., may be enabled
+                     */
+                    for (int i=0; supportedCiphers != null
+                                 && i<supportedCiphers.length; i++) {
+                        if (supportedCiphers[i].equals(cipher)) {
+                            if (vec == null) {
+                                vec = new Vector<String>();
+                            }
+                            vec.addElement(cipher);
+                            break;
+                        }
+                    }
+                }
+            }           
+
+            if (vec != null) {
+                result = new String[vec.size()];
+                vec.copyInto(result);
+            }
+        } else {
+            result = sslProxy.getDefaultCipherSuites();
+        }
+
+        return result;
+    }
+     
+    /*
+     * Gets the SSL server's keystore password.
+     */
+    protected String getKeystorePassword() {
+        String keyPass = endpoint.getKeyPass();
+        if (keyPass == null) {
+            keyPass = DEFAULT_KEY_PASS;
+        }
+        String keystorePass = endpoint.getKeystorePass();
+        if (keystorePass == null) {
+            keystorePass = keyPass;
+        }
+        return keystorePass;
+    }
+
+    /*
+     * Gets the SSL server's keystore.
+     */
+    protected KeyStore getKeystore(String type, String provider, String pass)
+            throws IOException {
+
+        String keystoreFile = endpoint.getKeystoreFile();
+        if (keystoreFile == null)
+            keystoreFile = defaultKeystoreFile;
+
+        return getStore(type, provider, keystoreFile, pass);
+    }
+
+    /*
+     * Gets the SSL server's truststore.
+     */
+    protected KeyStore getTrustStore(String keystoreType,
+            String keystoreProvider) throws IOException {
+        KeyStore trustStore = null;
+
+        String truststoreFile = endpoint.getTruststoreFile();
+        if(truststoreFile == null) {
+            truststoreFile = System.getProperty("javax.net.ssl.trustStore");
+        }
+        if(log.isDebugEnabled()) {
+            log.debug("Truststore = " + truststoreFile);
+        }
+
+        String truststorePassword = endpoint.getTruststorePass();
+        if( truststorePassword == null) {
+            truststorePassword =
+                System.getProperty("javax.net.ssl.trustStorePassword");
+        }
+        if(log.isDebugEnabled()) {
+            log.debug("TrustPass = " + truststorePassword);
+        }
+
+        String truststoreType = endpoint.getTruststoreType();
+        if( truststoreType == null) {
+            truststoreType = System.getProperty("javax.net.ssl.trustStoreType");
+        }
+        if(truststoreType == null) {
+            truststoreType = keystoreType;
+        }
+        if(log.isDebugEnabled()) {
+            log.debug("trustType = " + truststoreType);
+        }
+
+        String truststoreProvider = endpoint.getTruststoreProvider();
+        if( truststoreProvider == null) {
+            truststoreProvider =
+                System.getProperty("javax.net.ssl.trustStoreProvider");
+        }
+        if (truststoreProvider == null) {
+            truststoreProvider = keystoreProvider;
+        }
+        if(log.isDebugEnabled()) {
+            log.debug("trustProvider = " + truststoreProvider);
+        }
+
+        if (truststoreFile != null){
+            try {
+                trustStore = getStore(truststoreType, truststoreProvider,
+                        truststoreFile, truststorePassword);
+            } catch (IOException ioe) {
+                Throwable cause = ioe.getCause();
+                if (cause instanceof UnrecoverableKeyException) {
+                    // Log a warning we had a password issue
+                    log.warn(sm.getString("jsse.invalid_truststore_password"),
+                            cause);
+                    // Re-try
+                    trustStore = getStore(truststoreType, truststoreProvider,
+                            truststoreFile, null);
+                } else {
+                    // Something else went wrong - re-throw
+                    throw ioe;
+                }
+            }
+        }
+
+        return trustStore;
+    }
+
+    /*
+     * Gets the key- or truststore with the specified type, path, and password.
+     */
+    private KeyStore getStore(String type, String provider, String path,
+            String pass) throws IOException {
+
+        KeyStore ks = null;
+        InputStream istream = null;
+        try {
+            if (provider == null) {
+                ks = KeyStore.getInstance(type);
+            } else {
+                ks = KeyStore.getInstance(type, provider);
+            }
+            if(!("PKCS11".equalsIgnoreCase(type) ||
+                    "".equalsIgnoreCase(path))) {
+                File keyStoreFile = new File(path);
+                if (!keyStoreFile.isAbsolute()) {
+                    keyStoreFile = new File(System.getProperty(
+                            Constants.CATALINA_BASE_PROP), path);
+                }
+                istream = new FileInputStream(keyStoreFile);
+            }
+            
+            char[] storePass = null;
+            if (pass != null && !"".equals(pass)) {
+                storePass = pass.toCharArray(); 
+            }
+            ks.load(istream, storePass);
+        } catch (FileNotFoundException fnfe) {
+            log.error(sm.getString("jsse.keystore_load_failed", type, path,
+                    fnfe.getMessage()), fnfe);
+            throw fnfe;
+        } catch (IOException ioe) {
+            // May be expected when working with a trust store
+            // Re-throw. Caller will catch and log as required
+            throw ioe;
+        } catch(Exception ex) {
+            String msg = sm.getString("jsse.keystore_load_failed", type, path,
+                    ex.getMessage());
+            log.error(msg, ex);
+            throw new IOException(msg);
+        } finally {
+            if (istream != null) {
+                try {
+                    istream.close();
+                } catch (IOException ioe) {
+                    // Do nothing
+                }
+            }
+        }
+
+        return ks;
+    }
+
+    /**
+     * Reads the keystore and initializes the SSL socket factory.
+     */
+    void init() throws IOException {
+        try {
+
+            String clientAuthStr = endpoint.getClientAuth();
+            if("true".equalsIgnoreCase(clientAuthStr) ||
+               "yes".equalsIgnoreCase(clientAuthStr)) {
+                requireClientAuth = true;
+            } else if("want".equalsIgnoreCase(clientAuthStr)) {
+                wantClientAuth = true;
+            }
+
+            SSLContext context = createSSLContext();
+            context.init(getKeyManagers(), getTrustManagers(), null);
+
+            // Configure SSL session cache
+            SSLSessionContext sessionContext =
+                context.getServerSessionContext();
+            if (sessionContext != null) {
+                configureSessionContext(sessionContext);
+            }
+
+            // create proxy
+            sslProxy = context.getServerSocketFactory();
+
+            // Determine which cipher suites to enable
+            String requestedCiphers = endpoint.getCiphers();
+            enabledCiphers = getEnabledCiphers(requestedCiphers,
+                    sslProxy.getSupportedCipherSuites());
+
+            allowUnsafeLegacyRenegotiation = "true".equals(
+                    endpoint.getAllowUnsafeLegacyRenegotiation());
+            
+            // Check the SSL config is OK
+            checkConfig();
+
+        } catch(Exception e) {
+            if( e instanceof IOException )
+                throw (IOException)e;
+            throw new IOException(e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public SSLContext createSSLContext() throws Exception {
+
+        // SSL protocol variant (e.g., TLS, SSL v3, etc.)
+        String protocol = endpoint.getSslProtocol();
+        if (protocol == null) {
+            protocol = defaultProtocol;
+        }
+
+        SSLContext context = SSLContext.getInstance(protocol); 
+
+        return context;
+    }
+    
+    @Override
+    public KeyManager[] getKeyManagers() throws Exception {
+        String keystoreType = endpoint.getKeystoreType();
+        if (keystoreType == null) {
+            keystoreType = defaultKeystoreType;
+        }
+
+        String algorithm = endpoint.getAlgorithm();
+        if (algorithm == null) {
+            algorithm = KeyManagerFactory.getDefaultAlgorithm();
+        }
+
+        return getKeyManagers(keystoreType, endpoint.getKeystoreProvider(),
+                algorithm, endpoint.getKeyAlias());
+    }
+
+    @Override
+    public TrustManager[] getTrustManagers() throws Exception {
+        String keystoreType = endpoint.getKeystoreType();
+        if (keystoreType == null) {
+            keystoreType = defaultKeystoreType;
+        }
+
+        String algorithm = endpoint.getAlgorithm();
+        if (algorithm == null) {
+            algorithm = KeyManagerFactory.getDefaultAlgorithm();
+        }
+
+        return getTrustManagers(keystoreType, endpoint.getKeystoreProvider(),
+                algorithm);
+    }
+
+    @Override
+    public void configureSessionContext(SSLSessionContext sslSessionContext) {
+        int sessionCacheSize;
+        if (endpoint.getSessionCacheSize() != null) {
+            sessionCacheSize = Integer.parseInt(
+                    endpoint.getSessionCacheSize());
+        } else {
+            sessionCacheSize = defaultSessionCacheSize;
+        }
+        
+        int sessionTimeout;
+        if (endpoint.getSessionTimeout() != null) {
+            sessionTimeout = Integer.parseInt(endpoint.getSessionTimeout());
+        } else {
+            sessionTimeout = defaultSessionTimeout;
+        }
+
+        sslSessionContext.setSessionCacheSize(sessionCacheSize);
+        sslSessionContext.setSessionTimeout(sessionTimeout);
+    }
+
+    /**
+     * Gets the initialized key managers.
+     */
+    protected KeyManager[] getKeyManagers(String keystoreType,
+                                          String keystoreProvider,
+                                          String algorithm,
+                                          String keyAlias)
+                throws Exception {
+
+        KeyManager[] kms = null;
+
+        String keystorePass = getKeystorePassword();
+
+        KeyStore ks = getKeystore(keystoreType, keystoreProvider, keystorePass);
+        if (keyAlias != null && !ks.isKeyEntry(keyAlias)) {
+            throw new IOException(
+                    sm.getString("jsse.alias_no_key_entry", keyAlias));
+        }
+
+        KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
+        String keyPass = endpoint.getKeyPass();
+        if (keyPass == null) {
+            keyPass = keystorePass;
+        }
+        kmf.init(ks, keyPass.toCharArray());
+
+        kms = kmf.getKeyManagers();
+        if (keyAlias != null) {
+            String alias = keyAlias;
+            if (JSSESocketFactory.defaultKeystoreType.equals(keystoreType)) {
+                alias = alias.toLowerCase(Locale.ENGLISH);
+            }
+            for(int i=0; i<kms.length; i++) {
+                kms[i] = new JSSEKeyManager((X509KeyManager)kms[i], alias);
+            }
+        }
+
+        return kms;
+    }
+
+    /**
+     * Gets the initialized trust managers.
+     */
+    protected TrustManager[] getTrustManagers(String keystoreType,
+            String keystoreProvider, String algorithm)
+        throws Exception {
+        String crlf = endpoint.getCrlFile();
+        
+        String className = endpoint.getTrustManagerClassName();
+        if(className != null && className.length() > 0) {
+             ClassLoader classLoader = getClass().getClassLoader();
+             Class<?> clazz = classLoader.loadClass(className);
+             if(!(TrustManager.class.isAssignableFrom(clazz))){
+                throw new InstantiationException(sm.getString(
+                        "jsse.invalidTrustManagerClassName", className));
+             }
+             Object trustManagerObject = clazz.newInstance();
+             TrustManager trustManager = (TrustManager) trustManagerObject;
+             return new TrustManager[]{ trustManager };
+        }    
+
+        TrustManager[] tms = null;
+        
+        KeyStore trustStore = getTrustStore(keystoreType, keystoreProvider);
+        if (trustStore != null || endpoint.getTrustManagerClassName() != null) {
+            if (crlf == null) {
+                TrustManagerFactory tmf =
+                    TrustManagerFactory.getInstance(algorithm);
+                tmf.init(trustStore);
+                tms = tmf.getTrustManagers();
+            } else {
+                TrustManagerFactory tmf =
+                    TrustManagerFactory.getInstance(algorithm);
+                CertPathParameters params =
+                    getParameters(algorithm, crlf, trustStore);
+                ManagerFactoryParameters mfp =
+                    new CertPathTrustManagerParameters(params);
+                tmf.init(mfp);
+                tms = tmf.getTrustManagers();
+            }
+        }
+        
+        return tms;
+    }
+
+    /**
+     * Return the initialization parameters for the TrustManager.
+     * Currently, only the default <code>PKIX</code> is supported.
+     * 
+     * @param algorithm The algorithm to get parameters for.
+     * @param crlf The path to the CRL file.
+     * @param trustStore The configured TrustStore.
+     * @return The parameters including the CRLs and TrustStore.
+     */
+    protected CertPathParameters getParameters(String algorithm, 
+                                                String crlf, 
+                                                KeyStore trustStore)
+        throws Exception {
+        CertPathParameters params = null;
+        if("PKIX".equalsIgnoreCase(algorithm)) {
+            PKIXBuilderParameters xparams =
+                new PKIXBuilderParameters(trustStore, new X509CertSelector());
+            Collection<? extends CRL> crls = getCRLs(crlf);
+            CertStoreParameters csp = new CollectionCertStoreParameters(crls);
+            CertStore store = CertStore.getInstance("Collection", csp);
+            xparams.addCertStore(store);
+            xparams.setRevocationEnabled(true);
+            String trustLength = endpoint.getTrustMaxCertLength();
+            if(trustLength != null) {
+                try {
+                    xparams.setMaxPathLength(Integer.parseInt(trustLength));
+                } catch(Exception ex) {
+                    log.warn("Bad maxCertLength: "+trustLength);
+                }
+            }
+
+            params = xparams;
+        } else {
+            throw new CRLException("CRLs not supported for type: "+algorithm);
+        }
+        return params;
+    }
+
+
+    /**
+     * Load the collection of CRLs.
+     * 
+     */
+    protected Collection<? extends CRL> getCRLs(String crlf) 
+        throws IOException, CRLException, CertificateException {
+
+        File crlFile = new File(crlf);
+        if( !crlFile.isAbsolute() ) {
+            crlFile = new File(
+                    System.getProperty(Constants.CATALINA_BASE_PROP), crlf);
+        }
+        Collection<? extends CRL> crls = null;
+        InputStream is = null;
+        try {
+            CertificateFactory cf = CertificateFactory.getInstance("X.509");
+            is = new FileInputStream(crlFile);
+            crls = cf.generateCRLs(is);
+        } catch(IOException iex) {
+            throw iex;
+        } catch(CRLException crle) {
+            throw crle;
+        } catch(CertificateException ce) {
+            throw ce;
+        } finally { 
+            if(is != null) {
+                try{
+                    is.close();
+                } catch(Exception ex) {
+                    // Ignore
+                }
+            }
+        }
+        return crls;
+    }
+
+    /**
+     * Set the SSL protocol variants to be enabled.
+     * @param socket the SSLServerSocket.
+     * @param protocols the protocols to use.
+     */
+    protected void setEnabledProtocols(SSLServerSocket socket,
+            String []protocols){
+        if (protocols != null) {
+            socket.setEnabledProtocols(protocols);
+        }
+    }
+
+    /**
+     * Determines the SSL protocol variants to be enabled.
+     *
+     * @param socket The socket to get supported list from.
+     * @param requestedProtocols Array of requested protocol names all of which
+     *                           must be non-null and non-zero length
+     *
+     * @return Array of SSL protocol variants to be enabled, or null if none of
+     * the requested protocol variants are supported
+     */
+    protected String[] getEnabledProtocols(SSLServerSocket socket,
+                                           String[] requestedProtocols){
+        String[] supportedProtocols = socket.getSupportedProtocols();
+
+        String[] enabledProtocols = null;
+
+        if (requestedProtocols != null && requestedProtocols.length > 0) {
+            Vector<String> vec = null;
+            for (String protocol : requestedProtocols) {
+                /*
+                 * Check to see if the requested protocol is among the supported
+                 * protocols, i.e., may be enabled
+                 */
+                for (int i=0; supportedProtocols != null &&
+                        i < supportedProtocols.length; i++) {
+                    if (supportedProtocols[i].equals(protocol)) {
+                        if (vec == null) {
+                            vec = new Vector<String>();
+                        }
+                        vec.addElement(protocol);
+                        break;
+                    }
+                }
+            }
+
+            if (vec != null) {
+                enabledProtocols = new String[vec.size()];
+                vec.copyInto(enabledProtocols);
+            }
+        }
+
+        return enabledProtocols;
+    }
+
+    /**
+     * Configure Client authentication for this version of JSSE.  The
+     * JSSE included in Java 1.4 supports the 'want' value.  Prior
+     * versions of JSSE will treat 'want' as 'false'.
+     * @param socket the SSLServerSocket
+     */
+    protected void configureClientAuth(SSLServerSocket socket){
+        if (wantClientAuth){
+            socket.setWantClientAuth(wantClientAuth);
+        } else {
+            socket.setNeedClientAuth(requireClientAuth);
+        }
+    }
+
+    /**
+     * Configures the given SSL server socket with the requested cipher suites,
+     * protocol versions, and need for client authentication
+     */
+    private void initServerSocket(ServerSocket ssocket) {
+
+        SSLServerSocket socket = (SSLServerSocket) ssocket;
+
+        if (enabledCiphers != null) {
+            socket.setEnabledCipherSuites(enabledCiphers);
+        }
+
+        String[] requestedProtocols = endpoint.getSslEnabledProtocolsArray();
+        setEnabledProtocols(socket, getEnabledProtocols(socket, 
+                                                         requestedProtocols));
+
+        // we don't know if client auth is needed -
+        // after parsing the request we may re-handshake
+        configureClientAuth(socket);
+    }
+
+    /**
+     * Checks that the certificate is compatible with the enabled cipher suites.
+     * If we don't check now, the JIoEndpoint can enter a nasty logging loop.
+     * See bug 45528.
+     */
+    private void checkConfig() throws IOException {
+        // Create an unbound server socket
+        ServerSocket socket = sslProxy.createServerSocket();
+        initServerSocket(socket);
+
+        try {
+            // Set the timeout to 1ms as all we care about is if it throws an
+            // SSLException on accept. 
+            socket.setSoTimeout(1);
+
+            socket.accept();
+            // Will never get here - no client can connect to an unbound port
+        } catch (SSLException ssle) {
+            // SSL configuration is invalid. Possibly cert doesn't match ciphers
+            IOException ioe = new IOException(sm.getString(
+                    "jsse.invalid_ssl_conf", ssle.getMessage()));
+            ioe.initCause(ssle);
+            throw ioe;
+        } catch (Exception e) {
+            /*
+             * Possible ways of getting here
+             * socket.accept() throws a SecurityException
+             * socket.setSoTimeout() throws a SocketException
+             * socket.accept() throws some other exception (after a JDK change)
+             *      In these cases the test won't work so carry on - essentially
+             *      the behaviour before this patch
+             * socket.accept() throws a SocketTimeoutException
+             *      In this case all is well so carry on
+             */
+        } finally {
+            // Should be open here but just in case
+            if (!socket.isClosed()) {
+                socket.close();
+            }
+        }
+        
+    }
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/JSSESupport.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/JSSESupport.java
new file mode 100644
index 0000000..757b7b8
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/JSSESupport.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.net.jsse;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.SocketException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import javax.net.ssl.HandshakeCompletedEvent;
+import javax.net.ssl.HandshakeCompletedListener;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.security.cert.X509Certificate;
+
+import org.apache.tomcat.util.net.SSLSessionManager;
+import org.apache.tomcat.util.net.SSLSupport;
+
+/** JSSESupport
+
+   Concrete implementation class for JSSE
+   Support classes.
+
+   This will only work with JDK 1.2 and up since it
+   depends on JDK 1.2's certificate support
+
+   @author EKR
+   @author Craig R. McClanahan
+   @author Filip Hanik
+   Parts cribbed from JSSECertCompat       
+   Parts cribbed from CertificatesValve
+*/
+
+class JSSESupport implements SSLSupport, SSLSessionManager {
+    
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog(JSSESupport.class);
+    
+    private static final Map<SSLSession,Integer> keySizeCache =
+        new WeakHashMap<SSLSession, Integer>();
+
+    protected SSLSocket ssl;
+    protected SSLSession session;
+
+    Listener listener = new Listener();
+
+    JSSESupport(SSLSocket sock){
+        ssl=sock;
+        session = sock.getSession();
+        sock.addHandshakeCompletedListener(listener);
+    }
+    
+    JSSESupport(SSLSession session) {
+        this.session = session;
+    }
+
+    @Override
+    public String getCipherSuite() throws IOException {
+        // Look up the current SSLSession
+        if (session == null)
+            return null;
+        return session.getCipherSuite();
+    }
+
+    @Override
+    public Object[] getPeerCertificateChain() 
+        throws IOException {
+        return getPeerCertificateChain(false);
+    }
+
+    protected java.security.cert.X509Certificate [] getX509Certificates(
+            SSLSession session) {
+        Certificate [] certs=null;
+        try {
+            certs = session.getPeerCertificates();
+        } catch( Throwable t ) {
+            log.debug("Error getting client certs",t);
+            return null;
+        }
+        if( certs==null ) return null;
+        
+        java.security.cert.X509Certificate [] x509Certs = 
+            new java.security.cert.X509Certificate[certs.length];
+        for(int i=0; i < certs.length; i++) {
+            if (certs[i] instanceof java.security.cert.X509Certificate ) {
+                // always currently true with the JSSE 1.1.x
+                x509Certs[i] = (java.security.cert.X509Certificate) certs[i];
+            } else {
+                try {
+                    byte [] buffer = certs[i].getEncoded();
+                    CertificateFactory cf =
+                        CertificateFactory.getInstance("X.509");
+                    ByteArrayInputStream stream =
+                        new ByteArrayInputStream(buffer);
+                    x509Certs[i] = (java.security.cert.X509Certificate)
+                            cf.generateCertificate(stream);
+                } catch(Exception ex) { 
+                    log.info("Error translating cert " + certs[i], ex);
+                    return null;
+                }
+            }
+            if(log.isTraceEnabled())
+                log.trace("Cert #" + i + " = " + x509Certs[i]);
+        }
+        if(x509Certs.length < 1)
+            return null;
+        return x509Certs;
+    }
+
+    @Override
+    public Object[] getPeerCertificateChain(boolean force)
+        throws IOException {
+        // Look up the current SSLSession
+        if (session == null)
+            return null;
+
+        // Convert JSSE's certificate format to the ones we need
+        X509Certificate [] jsseCerts = null;
+        try {
+            jsseCerts = session.getPeerCertificateChain();
+        } catch(Exception bex) {
+            // ignore.
+        }
+        if (jsseCerts == null)
+            jsseCerts = new X509Certificate[0];
+        if(jsseCerts.length <= 0 && force && ssl != null) {
+            session.invalidate();
+            handShake();
+            session = ssl.getSession();
+        }
+        return getX509Certificates(session);
+    }
+
+    protected void handShake() throws IOException {
+        if( ssl.getWantClientAuth() ) {
+            log.debug("No client cert sent for want");
+        } else {
+            ssl.setNeedClientAuth(true);
+        }
+
+        if (ssl.getEnabledCipherSuites().length == 0) {
+            // Handshake is never going to be successful.
+            // Assume this is because handshakes are disabled
+            log.warn("SSL server initiated renegotiation is disabled, closing connection");
+            session.invalidate();
+            ssl.close();
+            return;
+        }
+
+        InputStream in = ssl.getInputStream();
+        int oldTimeout = ssl.getSoTimeout();
+        ssl.setSoTimeout(1000);
+        byte[] b = new byte[0];
+        listener.reset();
+        ssl.startHandshake();
+        int maxTries = 60; // 60 * 1000 = example 1 minute time out
+        for (int i = 0; i < maxTries; i++) {
+            if (log.isTraceEnabled())
+                log.trace("Reading for try #" + i);
+            try {
+                in.read(b);
+            } catch(SSLException sslex) {
+                log.info("SSL Error getting client Certs",sslex);
+                throw sslex;
+            } catch (IOException e) {
+                // ignore - presumably the timeout
+            }
+            if (listener.completed) {
+                break;
+            }
+        }
+        ssl.setSoTimeout(oldTimeout);
+        if (listener.completed == false) {
+            throw new SocketException("SSL Cert handshake timeout");
+        }
+
+    }
+
+    /**
+     * Copied from <code>org.apache.catalina.valves.CertificateValve</code>
+     */
+    @Override
+    public Integer getKeySize() 
+        throws IOException {
+        // Look up the current SSLSession
+        SSLSupport.CipherData c_aux[]=ciphers;
+        if (session == null)
+            return null;
+        
+        Integer keySize = null;
+        synchronized(keySizeCache) {
+            keySize = keySizeCache.get(session);
+        }
+        
+        if (keySize == null) {
+            int size = 0;
+            String cipherSuite = session.getCipherSuite();
+            for (int i = 0; i < c_aux.length; i++) {
+                if (cipherSuite.indexOf(c_aux[i].phrase) >= 0) {
+                    size = c_aux[i].keySize;
+                    break;
+                }
+            }
+            keySize = new Integer(size);
+            synchronized(keySizeCache) {
+                keySizeCache.put(session, keySize);
+            }
+        }
+        return keySize;
+    }
+
+    @Override
+    public String getSessionId()
+        throws IOException {
+        // Look up the current SSLSession
+        if (session == null)
+            return null;
+        // Expose ssl_session (getId)
+        byte [] ssl_session = session.getId();
+        if ( ssl_session == null) 
+            return null;
+        StringBuilder buf=new StringBuilder();
+        for(int x=0; x<ssl_session.length; x++) {
+            String digit=Integer.toHexString(ssl_session[x]);
+            if (digit.length()<2) buf.append('0');
+            if (digit.length()>2) digit=digit.substring(digit.length()-2);
+            buf.append(digit);
+        }
+        return buf.toString();
+    }
+
+
+    private static class Listener implements HandshakeCompletedListener {
+        volatile boolean completed = false;
+        @Override
+        public void handshakeCompleted(HandshakeCompletedEvent event) {
+            completed = true;
+        }
+        void reset() {
+            completed = false;
+        }
+    }
+
+    /**
+     * Invalidate the session this support object is associated with.
+     */
+    @Override
+    public void invalidateSession() {
+        session.invalidate();
+    }
+}
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/NioX509KeyManager.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/NioX509KeyManager.java
new file mode 100644
index 0000000..5a4559c
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/net/jsse/NioX509KeyManager.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.net.jsse;
+
+import java.net.Socket;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.X509ExtendedKeyManager;
+import javax.net.ssl.X509KeyManager;
+
+public class NioX509KeyManager extends X509ExtendedKeyManager {
+
+    private X509KeyManager delegate;
+    private String serverKeyAlias;
+
+    /**
+     * Constructor.
+     *
+     * @param mgr The X509KeyManager used as a delegate
+     * @param serverKeyAlias The alias name of the server's keypair and
+     * supporting certificate chain
+     */
+    public NioX509KeyManager(X509KeyManager mgr, String serverKeyAlias) {
+        this.delegate = mgr;
+        this.serverKeyAlias = serverKeyAlias;
+    }
+
+    @Override
+    public String chooseClientAlias(String[] keyType, Principal[] issuers,
+            Socket socket) {
+        return delegate.chooseClientAlias(keyType, issuers, socket);
+    }
+
+    @Override
+    public String chooseServerAlias(String keyType, Principal[] issuers,
+            Socket socket) {
+        if (serverKeyAlias != null) {
+            return serverKeyAlias;
+        }
+
+        return delegate.chooseServerAlias(keyType, issuers, socket);
+    }
+
+    @Override
+    public X509Certificate[] getCertificateChain(String alias) {
+        return delegate.getCertificateChain(alias);
+    }
+
+    @Override
+    public String[] getClientAliases(String keyType, Principal[] issuers) {
+        return delegate.getClientAliases(keyType, issuers);
+    }
+
+    @Override
+    public PrivateKey getPrivateKey(String alias) {
+        return delegate.getPrivateKey(alias);
+    }
+
+    @Override
+    public String[] getServerAliases(String keyType, Principal[] issuers) {
+        return delegate.getServerAliases(keyType, issuers);
+    }
+
+    @Override
+    public String chooseEngineServerAlias(String keyType, Principal[] issuers,
+            SSLEngine engine) {
+        if (serverKeyAlias!=null) {
+            return serverKeyAlias;
+        }
+
+        return super.chooseEngineServerAlias(keyType, issuers, engine);
+    }
+    
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/scan/Constants.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/scan/Constants.java
new file mode 100644
index 0000000..b84517d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/scan/Constants.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.scan;
+
+/**
+ * String constants for the scan package.
+ */
+public final class Constants {
+
+    public static final String Package = "org.apache.tomcat.util.scan";
+
+    /* System properties */
+    public static final String SKIP_JARS_PROPERTY =
+        "tomcat.util.scan.DefaultJarScanner.jarsToSkip";
+
+    /* Commons strings */
+    public static final String JAR_EXT = ".jar";
+    public static final String WEB_INF_LIB = "/WEB-INF/lib/";
+
+    /* Context attributes - used to pass short-cuts to Jasper */
+    public static final String MERGED_WEB_XML =
+        "org.apache.tomcat.util.scan.MergedWebXml";
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/scan/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/scan/LocalStrings.properties
new file mode 100644
index 0000000..ee7e22d
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/scan/LocalStrings.properties
@@ -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.
+
+jarScan.classloaderFail=Failed to scan [{0}] from classloader hierarchy
+jarScan.classloaderStart=Scanning for JARs in classloader hierarchy
+jarScan.classloaderJarScan=Scanning JAR [{0}] from classpath
+jarScan.classloaderJarNoScan=Not scanning JAR [{0}] from classpath
+jarScan.jarUrlStart=Scanning JAR at URL [{0}]
+jarScan.webinflibFail=Failed to scan JAR [{0}] from WEB-INF/lib
+jarScan.webinflibStart=Scanning WEB-INF/lib for JARs
+jarScan.webinflibJarScan=Scanning JAR [{0}] from WEB-INF/lib
+jarScan.webinflibJarNoScan=Not scanning JAR [{0}] from WEB-INF/lib
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/scan/StandardJarScanner.java b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/scan/StandardJarScanner.java
new file mode 100644
index 0000000..546b073
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/scan/StandardJarScanner.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 org.apache.tomcat.util.scan;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.JarURLConnection;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.net.URLConnection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import javax.servlet.ServletContext;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.JarScanner;
+import org.apache.tomcat.JarScannerCallback;
+import org.apache.tomcat.util.file.Matcher;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * The default {@link JarScanner} implementation scans the WEB-INF/lib directory
+ * followed by the provided classloader and then works up the classloader
+ * hierarchy. This implementation is sufficient to meet the requirements of the
+ * Servlet 3.0 specification as well as to provide a number of Tomcat specific
+ * extensions. The extensions are:
+ * <ul>
+ *   <li>Scanning the classloader hierarchy (enabled by default)</li>
+ *   <li>Testing all files to see if they are JARs (disabled by default)</li>
+ *   <li>Testing all directories to see if they are exploded JARs
+ *       (disabled by default)</li>
+ * </ul>
+ * All of the extensions may be controlled via configuration.
+ */
+public class StandardJarScanner implements JarScanner {
+
+    private static final Log log = LogFactory.getLog(StandardJarScanner.class);
+
+    private static final Set<String> defaultJarsToSkip = new HashSet<String>();
+    
+    /**
+     * The string resources for this package.
+     */
+    private static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    static {
+        String jarList = System.getProperty(Constants.SKIP_JARS_PROPERTY);
+        if (jarList != null) {
+            StringTokenizer tokenizer = new StringTokenizer(jarList, ",");
+            while (tokenizer.hasMoreElements()) {
+                defaultJarsToSkip.add(tokenizer.nextToken());
+            }
+        }
+    }
+
+    /**
+     * Controls the classpath scanning extension.
+     */
+    private boolean scanClassPath = true;
+    public boolean isScanClassPath() {
+        return scanClassPath;
+    }
+    public void setScanClassPath(boolean scanClassPath) {
+        this.scanClassPath = scanClassPath;
+    }
+
+    /**
+     * Controls the testing all files to see of they are JAR files extension.
+     */
+    private boolean scanAllFiles = false;
+    public boolean isScanAllFiles() {
+        return scanAllFiles;
+    }
+    public void setScanAllFiles(boolean scanAllFiles) {
+        this.scanAllFiles = scanAllFiles;
+    }
+
+    /**
+     * Controls the testing all directories to see of they are exploded JAR
+     * files extension.
+     */
+    private boolean scanAllDirectories = false;
+    public boolean isScanAllDirectories() {
+        return scanAllDirectories;
+    }
+    public void setScanAllDirectories(boolean scanAllDirectories) {
+        this.scanAllDirectories = scanAllDirectories;
+    }
+
+    /**
+     * Scan the provided ServletContext and classloader for JAR files. Each JAR
+     * file found will be passed to the callback handler to be processed.
+     *  
+     * @param context       The ServletContext - used to locate and access
+     *                      WEB-INF/lib
+     * @param classloader   The classloader - used to access JARs not in
+     *                      WEB-INF/lib
+     * @param callback      The handler to process any JARs found
+     * @param jarsToSkip    List of JARs to ignore. If this list is null, a
+     *                      default list will be read from the system property
+     *                      defined by {@link Constants#SKIP_JARS_PROPERTY} 
+     */
+    @Override
+    public void scan(ServletContext context, ClassLoader classloader,
+            JarScannerCallback callback, Set<String> jarsToSkip) {
+
+        if (log.isTraceEnabled()) {
+            log.trace(sm.getString("jarScan.webinflibStart"));
+        }
+
+        Set<String> ignoredJars;
+        if (jarsToSkip == null) {
+            ignoredJars = defaultJarsToSkip;
+        } else {
+            ignoredJars = jarsToSkip;
+        }
+        Set<String[]> ignoredJarsTokens = new HashSet<String[]>();
+        for (String pattern: ignoredJars) {
+            ignoredJarsTokens.add(Matcher.tokenizePathAsArray(pattern));
+        }
+
+        // Scan WEB-INF/lib
+        Set<String> dirList = context.getResourcePaths(Constants.WEB_INF_LIB);
+        if (dirList != null) {
+            Iterator<String> it = dirList.iterator();
+            while (it.hasNext()) {
+                String path = it.next();
+                if (path.endsWith(Constants.JAR_EXT) &&
+                    !Matcher.matchPath(ignoredJarsTokens,
+                        path.substring(path.lastIndexOf('/')+1))) {
+                    // Need to scan this JAR
+                    if (log.isDebugEnabled()) {
+                        log.debug(sm.getString("jarScan.webinflibJarScan", path));
+                    }
+                    URL url = null;
+                    try {
+                        url = context.getResource(path);
+                        process(callback, url);
+                    } catch (IOException e) {
+                        log.warn(sm.getString("jarScan.webinflibFail", url), e);
+                    }
+                } else {
+                    if (log.isTraceEnabled()) {
+                        log.trace(sm.getString("jarScan.webinflibJarNoScan", path));
+                    }
+                }
+            }
+        }
+        
+        // Scan the classpath
+        if (scanClassPath) {
+            if (log.isTraceEnabled()) {
+                log.trace(sm.getString("jarScan.classloaderStart"));
+            }
+
+            ClassLoader loader = 
+                Thread.currentThread().getContextClassLoader();
+            
+            while (loader != null) {
+                if (loader instanceof URLClassLoader) {
+                    URL[] urls = ((URLClassLoader) loader).getURLs();
+                    for (int i=0; i<urls.length; i++) {
+                        // Extract the jarName if there is one to be found
+                        String jarName = getJarName(urls[i]);
+                        
+                        // Skip JARs known not to be interesting and JARs
+                        // in WEB-INF/lib we have already scanned
+                        if (jarName != null &&
+                            !(Matcher.matchPath(ignoredJarsTokens, jarName) ||
+                                urls[i].toString().contains(
+                                        Constants.WEB_INF_LIB + jarName))) {
+                            if (log.isDebugEnabled()) {
+                                log.debug(sm.getString("jarScan.classloaderJarScan", urls[i]));
+                            }
+                            try {
+                                process(callback, urls[i]);
+                            } catch (IOException ioe) {
+                                log.warn(sm.getString(
+                                        "jarScan.classloaderFail",urls[i]), ioe);
+                            }
+                        } else {
+                            if (log.isTraceEnabled()) {
+                                log.trace(sm.getString("jarScan.classloaderJarNoScan", urls[i]));
+                            }
+                        }
+                    }
+                }
+                loader = loader.getParent();
+            }
+
+        }
+    }
+
+    /*
+     * Scan a URL for JARs with the optional extensions to look at all files
+     * and all directories.
+     */
+    private void process(JarScannerCallback callback, URL url)
+            throws IOException {
+
+        if (log.isTraceEnabled()) {
+            log.trace(sm.getString("jarScan.jarUrlStart", url));
+        }
+
+        URLConnection conn = url.openConnection();
+        if (conn instanceof JarURLConnection) {
+            callback.scan((JarURLConnection) conn);
+        } else {
+            String urlStr = url.toString();
+            if (urlStr.startsWith("file:") || urlStr.startsWith("jndi:")) {
+                if (urlStr.endsWith(Constants.JAR_EXT)) {
+                    URL jarURL = new URL("jar:" + urlStr + "!/");
+                    callback.scan((JarURLConnection) jarURL.openConnection());
+                } else {
+                    File f;
+                    try {
+                        f = new File(url.toURI());
+                        if (f.isFile() && scanAllFiles) {
+                            // Treat this file as a JAR
+                            URL jarURL = new URL("jar:" + urlStr + "!/");
+                            callback.scan((JarURLConnection) jarURL.openConnection());
+                        } else if (f.isDirectory() && scanAllDirectories) {
+                            File metainf = new File(f.getAbsoluteFile() +
+                                    File.separator + "META-INF");
+                            if (metainf.isDirectory()) {
+                                callback.scan(f);
+                            }
+                        }
+                    } catch (URISyntaxException e) {
+                        // Wrap the exception and re-throw
+                        IOException ioe = new IOException();
+                        ioe.initCause(e);
+                        throw ioe;
+                    }
+                }
+            }
+        }
+        
+    }
+
+    /*
+     * Extract the JAR name, if present, from a URL
+     */
+    private String getJarName(URL url) {
+        
+        String name = null;
+        
+        String path = url.getPath();
+        int end = path.indexOf(Constants.JAR_EXT);
+        if (end != -1) {
+            int start = path.lastIndexOf('/', end);
+            name = path.substring(start + 1, end + 4);
+        } else if (isScanAllDirectories()){
+            int start = path.lastIndexOf('/');
+            name = path.substring(start + 1);
+        }
+        
+        return name;
+    }
+
+}
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/scan/package.html b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/scan/package.html
new file mode 100644
index 0000000..826e42e
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/scan/package.html
@@ -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.
+-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+$Id: package.html,v 1.1 2011/06/28 21:08:25 rherrmann Exp $
+-->
+</head>
+<body bgcolor="white">
+<p>
+This package contains the common classes used to perform configuration scanning
+for Catalina and Jasper.
+</p>
+</body>
+</html>
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/res/LocalStrings.properties b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/res/LocalStrings.properties
new file mode 100644
index 0000000..b67afa5
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/res/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.
+
+threadPoolExecutor.threadStoppedToAvoidPotentialLeak=Stopping thread {0} to avoid potential memory leaks after a context was stopped.
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/res/LocalStrings_es.properties b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/res/LocalStrings_es.properties
new file mode 100644
index 0000000..09697dc
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/res/LocalStrings_es.properties
@@ -0,0 +1,15 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/res/LocalStrings_fr.properties b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/res/LocalStrings_fr.properties
new file mode 100644
index 0000000..09697dc
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/res/LocalStrings_fr.properties
@@ -0,0 +1,15 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
diff --git a/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/res/LocalStrings_ja.properties b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/res/LocalStrings_ja.properties
new file mode 100644
index 0000000..09697dc
--- /dev/null
+++ b/bundles/org.apache.tomcat/src/org/apache/tomcat/util/threads/res/LocalStrings_ja.properties
@@ -0,0 +1,15 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+